☰ Оглавление

Утилиты для Python-программиста

Анализ интерфейсов объектов

В Python утиная типизация поэтому лично мне иногда очень хочется посмотреть, какие методы поддерживаются разными объектами. Был написан такой скрипт:

#!/usr/bin/python2
# coding: utf-8


import sys
import os
import getopt


def ins_to_line(st, ins, pos):
    return st[:pos] + ins + st[pos + len(ins):]


def desc(tts, filter_f, pr_desc):
    q = {}
    for tt in tts:
        t = getattr(__builtins__, tt)
        for n in dir(t):
            if n in q:
                q[n].append(tt)
            else:
                q[n] = [tt]

    f = dict(filter(lambda x: filter_f(len(x[1])), q.items()))

    s = max(map(lambda x: len(x[0]), f.items()))
    ins = {}
    for i in tts:
        s += 2
        ins[i] = s
        s += len(i)

    ss = '. ' * (int(s/2)+1)
    b_line = ss[1:s+1], ss[0:s]

    pp = 0
    for n, a in sorted(f.items(), key=lambda x: x[0]):
        pp = 1 - pp
        if pr_desc:
            print('=' * 59)
        t = ins_to_line(b_line[pp], n, 0)
        for i in a:
            t = ins_to_line(t, i, ins[i])
        print(t)
        if pr_desc:
            print('_ ' * 30)
            print(getattr(getattr(__builtins__, a[0]), n).__doc__)


def usage():
    print((
        'Usage:\n'
        ' {0} -[hd1nN] type_name_1 type_name_2 ...\n'
        'Options:\n'
        ' Filters:\n'
        '  -1 print methods that present only in one cless\n'
        '  -n methods presented in not all clases\n'
        '  -N methods presented in all classes\n'
        '  -A all methods (default)\n'
        ' Detallisations:\n'
        '  -d print x.__doc__\n'
        '  -h this message and exit\n'
        'Example:\n'
        ' {0} -1 int bool'
    ).format(os.path.basename(sys.argv[0])))


def main():
    optlist, args = getopt.getopt(sys.argv[1:], 'hd1nN')
    args_l = len(args)
    if args_l == 0:
        usage()
        return
    number = lambda x: True # All
    print_description = False
    for k, v in optlist:
        if k == '-1':
            number = lambda x: x == 1
        elif k == '-n':
            number = lambda x: x < args_l
        elif k == '-N':
            number = lambda x: x == args_l
        elif k == '-A':
            pass
        elif k == '-d':
            print_description = True
        elif k == '-h':
            usage()
            return
        else:
            raise Exception('How?!')
    desc(args, number, print_description)


if __name__ == '__main__':
    main()

Код работает на обоих версиях: 2.x и 3.x.

Примеры использования:

Вывести методы, которые присутствую только в одном из перечисленных классов:

$ types_cmp.py -1 int str
__abs__ . . . . . int . . 
__and__. . . . . .int. . .
__bool__. . . . . int . . 
__ceil__ . . . . .int. . .
__contains__. . . . . .str
__divmod__ . . . .int. . .
...

Вывести методы, которые присутствуют строго во всех из перечисленных классов:

$ types_cmp.py -N int list dict
__class__ . . . . int .list .dict
__delattr__. . . .int. list. dict
__doc__ . . . . . int .list .dict
__eq__ . . . . . .int. list. dict
__format__. . . . int .list .dict
...

Вывести все методы с описаниями:

$ types_cmp.py -d dict
===========================================================
__class__ . . . . dict
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
type(object) -> the object's type
type(name, bases, dict) -> a new type
===========================================================
__contains__ . . .dict
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
D.__contains__(k) -> True if D has a key k, else False
...

Ну и конечно не забывайте про прямую проверку типов. В каких-то случаях она предпочтительнее:

if type(x) == str:
    print('It is string')

Специальный инструмент для отладки

Мне приходится сталкиваться с большими массивами неподдерживаемого кода, в котором надо разбираться. В конце концов я написал такой модуль:

import inspect


class magic_object:

    def __init__(o, c, f, a, k):
        o.h = c(*a, **k)
        o.f = f
        o.f("%s.__init__(*%s, **%s)" % (id(o.h), repr(a), repr(k)))

    def __getattr__(o, name):
        def d(*a, **k):
            r = getattr(o.h, name)(*a, **k)
            o.f("%s.%s(*%s, **%s) -> %s" % (id(o.h), name, repr(a), repr(k), repr(r)))
            return r
        return d


class magic_class:

    def __init__(o, c, f):
        o.c = c
        o.f = f

    def __call__(o, *a, **k):
        return magic_object(o.c, o.f, a, k)


def magic_printing_func(x):
    print "\033[44;34;1m*\033[0m %s" % x

def make_magic(the_class, printing_func=None):
    if printing_func is None:
        printing_func = magic_printing_func
#    globals()[the_class.__name__] = magic_class(the_class, printing_func)
    inspect.currentframe().f_back.f_globals[the_class.__name__] = magic_class(
        the_class, printing_func
    )

Пользоваться очень просто. Делаете из этого модуль, подключаете его и вызываете для любого класса магию. После этого всё, что происходит с объектами этого класса будет протоколироваться.

# coding: utf-8

from xxxx import make_magic

class T:
    def a(o, x, zoom):
        return 'ok'

# запускаем магию
make_magic(T) # <--- вот оно
# дальше весь код оставляем без изменений, магия работает

t = T()
t.a(1, zoom=9)

Вы получите подсвеченную отладку:

* 140015364812672.__init__(*(), **{})
* 140015364812672.a(*(1,), **{'zoom': 9}) -> 'ok'