Блоґ одного кібера

Історія хвороби контуженого інформаційним вибухом

Posts Tagged ‘зневадження

Інтроспектор: “Хто присвоїв атрибуту це значення?”

with 9 comments

Ану признавайтесь падлюки! 🙂

Іноді в процесі зневадження з’являється питання “Хто й коли присвоює цьому атрибуту неправильне значення?”. Покрокове проходження через тисячі рядків коду нудно і занадто складно. Що робити?

Елементарно, Ватсон! В Python надзвичайна здатність до інтроспекції.

Візьмімо для прикладу таку програму:

from watch_attribute import watched_attribute
import random

def change_here(x):
    x.x = random.random()

class Foo(object):
    add_watched_attribute('x')

foo = Foo()
foo.x = 2

for i in range(2):
    foo.x = foo.x * 2
    change_here(foo)

y = foo.x

Програма притягнута за вуха, суто щоб потестувати watched_attribute – функцію недавно написана мною, яка змушує атрибут доповідати про всі дії які з ним роблять. Результат роботи програми виглядає наприклад так:

/home/bunyk/progs/test_watch_attribute.py:13 foo.x = 2
x set to 2
/home/bunyk/progs/test_watch_attribute.py:16     foo.x = foo.x * 2
x is 2
/home/bunyk/progs/test_watch_attribute.py:16     foo.x = foo.x * 2
x set to 4
/home/bunyk/progs/test_watch_attribute.py:7     x.x = random.random()
x set to 0.288351784619
/home/bunyk/progs/test_watch_attribute.py:16     foo.x = foo.x * 2
x is 0.288351784619
/home/bunyk/progs/test_watch_attribute.py:16     foo.x = foo.x * 2
x set to 0.576703569239
/home/bunyk/progs/test_watch_attribute.py:7     x.x = random.random()
x set to 0.300510362936
/home/bunyk/progs/test_watch_attribute.py:19 y = foo.x
x is 0.300510362936

З прикладу незрозуміло нащо це треба, тому я покажу як я використав це на роботі. Отак:

('uaprom/forms/wt_fields.py', 1583) 'sets data to' 90000L
('wtforms/form.py', 133) 'gets data' 90000L
('wtforms/form.py', 133) 'gets data' 90000L
('uaprom/forms/wt_fields.py', 1583) 'sets data to' 90000L
('uaprom/forms/wt_fields.py', 1596) 'sets data to' 501L
('uaprom/forms/wt_fields.py', 1634) 'gets data' 501L
('uaprom/forms/wt_fields.py', 1635) 'gets data' 501L
('uaprom/forms/wt_fields.py', 1635) 'sets data to' 9018.0
('uaprom/forms/wt_forms.py', 931) 'gets data' 9018.0
('uaprom/forms/wt_validators.py', 721) 'gets data' 9018.0
('uaprom/forms/wt_validators.py', 722) 'gets data' 9018.0
('uaprom/forms/wt_validators.py', 722) 'sets data to' 501.0 <-- от хто винен!
('wtforms/validators.py', 150) 'gets data' 501.0
('uaprom/forms/wt_fields.py', 1629) 'gets data' 501.0

Звичайно треба ламати руки тому хто змінює значення полів в валідаторах, але я живим не дамся! Приклад нам показує що коли атрибут читають і пишуть 14 разів в 5 різних файлах, то перечитування коду чи навіть трасування програми не допоможе знайти проблему так швидко як такий лог. Бо поки залізеш в один файл, розпечатаєш там значення, поки в інший…

Ну, а зараз час подарувати вам код модуля watched_attribute:

import sys
import inspect

def from_where_called():
    info = inspect.getframeinfo(sys._getframe(2))
    # інформація про фрейм на два вище за поточний
    # (функція яка викликала функцію що викликала оцю функцію)
    code = info.code_context[0] if info.code_context else ''
    return '%s:%s %s' % (info.filename, info.lineno, code)

def add_watched_attribute(name):  
    def attr_watch_get(self):
        value = getattr(self, '_' + name, 'unset')
        print from_where_called(), name, 'is', value
        return value

    def attr_watch_set(self, value):
        print from_where_called(), name, 'set to', value
        setattr(self, '_' + name, value)

    sys._getframe(1).f_locals[name] = property(attr_watch_get, attr_watch_set)

Від чого я в цьому коді прусь, так це від того, що getter і setter для property сховані в замиканні, і не засмічують простір імен класу. А тепер також від того, що функція виявляється має доступ на запис, до простору імен на рівень вище (останній рядок). Ну хіба це не прекрасно?

Written by bunyk

Квітень 23, 2012 at 04:00

Оприлюднено в Кодерство

Tagged with ,