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

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

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

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 сховані в замиканні, і не засмічують простір імен класу. А тепер також від того, що функція виявляється має доступ на запис, до простору імен на рівень вище (останній рядок). Ну хіба це не прекрасно?

Advertisements

Written by bunyk

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

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

Tagged with ,

Відповідей: 9

Subscribe to comments with RSS.

  1. Ну, і патч виявленої баги просто смішний:


    1          temp = field.data
    2          field.data /= field._price
    3
    –        super(SumForAdViewsRange, self).__call__(form, field)
    4 –        field.data = temp
    5 +        try:
    6 +            super(SumForAdViewsRange, self).__call__(form, field)
    7 +        finally:
    8 +            field.data = temp
    9

    bunyk

    Квітень 23, 2012 at 10:22

  2. Це запрацювало, але все одно некльово, тому що використовує ім’я базового класу. Заманливо було б використати super(), але я не знаю, як це зробити на етапі створення класу. Можливо метакласи б підійшли, або ж навіть і декоратори, проте я поки мало смислю в обох цих розділах.

    class BaseWatcher(object):
        @classmethod
        def watch(cls, name):
            setattr(cls, name, watched_attribute(name))
    
    class Foo(BaseWatcher):
        BaseWatcher.watch('x')
    

    Andriy Senkovych

    Квітень 23, 2012 at 16:14

    • Привіт незнайомому українському пітоноводу. 🙂
      Хороший варіант.

      P.S. Код з відступами краще вставляти в тег <pre>, або [sourcecode language="python"]. Я злегка ваш коментар поправив.

      bunyk

      Квітень 23, 2012 at 18:47

    • Придумав!

      def watch(name):
          sys._getframe(1).f_locals[name] = watched_attribute(name)
      
      class Foo(object):
          watch('x')
      

      bunyk

      Квітень 23, 2012 at 19:01

      • Вітаю з вирішеною задачкою! Непогано було б в статті якесь оновлення написати 😉 Так би мовити, для грядущих поколінь.

        Та, вітання взаємні! Нарешті згадав пароль до свого вордпресівського профілю(p.s. то мої коментарі вище).

        Jolly Roger

        Квітень 23, 2012 at 20:05

  3. І до речі. там де я написав sys._getframe().f_back.f_back, можна писати sys._getframe(2). Параметр показує назовні скількох фреймів потрібно вийти. Треба було просто читати документацію до sys, а не сорс inspect. 🙂

    bunyk

    Квітень 23, 2012 at 19:09

  4. […] з Інтроспектором та іншими утилітами для дебагу це вже тягне на якусь […]


Залишити відповідь

Заповніть поля нижче або авторизуйтесь клікнувши по іконці

Лого WordPress.com

Ви коментуєте, використовуючи свій обліковий запис WordPress.com. Log Out / Змінити )

Twitter picture

Ви коментуєте, використовуючи свій обліковий запис Twitter. Log Out / Змінити )

Facebook photo

Ви коментуєте, використовуючи свій обліковий запис Facebook. Log Out / Змінити )

Google+ photo

Ви коментуєте, використовуючи свій обліковий запис Google+. Log Out / Змінити )

З’єднання з %s

%d блогерам подобається це: