Python Decorator для изменения переменной в текущей области видимости - PullRequest
4 голосов
/ 26 апреля 2010

Цель: создать декоратор, который может изменять область, в которой он используется.

Если это сработало:

class Blah(): # or perhaps class Blah(ParentClassWhichMakesThisPossible)

    def one(self):
        pass

    @decorated
    def two(self):
        pass

>>> Blah.decorated
["two"]

Почему? По сути, я хочу написать классы, которые могут поддерживать специальные словари методов, чтобы можно было получать списки доступных методов различных типов для каждого класса. эээ .....

Я хочу сделать это:

class RuleClass(ParentClass):
    @rule
    def blah(self):
        pass

    @rule
    def kapow(self):
        pass

    def shazam(self):

class OtherRuleClass(ParentClass):
    @rule
    def foo(self):
        pass

    def bar(self):
        pass

>>> RuleClass.rules.keys()
["blah", "kapow"]
>>> OtherRuleClass.rules.keys()
["foo"]

Ответы [ 2 ]

9 голосов
/ 26 апреля 2010

Вы можете делать что хотите с декоратором классов (в Python 2.6) или метаклассом. Класс декоратора версии:

def rule(f):
    f.rule = True
    return f

def getRules(cls):
    cls.rules = {}
    for attr, value in cls.__dict__.iteritems():
        if getattr(value, 'rule', False):
            cls.rules[attr] = value
    return cls

@getRules
class RuleClass:
    @rule
    def foo(self):
        pass

Версия метакласса будет:

def rule(f):
    f.rule = True
    return f

class RuleType(type):
    def __init__(self, name, bases, attrs):
        self.rules = {}
        for attr, value in attrs.iteritems():
            if getattr(value, 'rule', False):
                self.rules[attr] = value
        super(RuleType, self).__init__(name, bases, attrs)

class RuleBase(object):
    __metaclass__ = RuleType

class RuleClass(RuleBase):
    @rule
    def foo(self):
        pass

Обратите внимание, что ни один из них не делает то, что вы просите (изменить пространство имен вызывающего), потому что это хрупко, сложно и часто невозможно. Вместо этого они оба обрабатывают класс - через декоратор класса или метод __init__ метакласса - проверяя все атрибуты и заполняя атрибут rules. Разница между ними заключается в том, что решение метакласса работает в Python 2.5 и более ранних версиях (до 2.2) и что метакласс наследуется. С помощью декоратора каждый из подклассов должен применять декоратор индивидуально (если они хотят установить атрибут rules.)

Оба решения не учитывают наследование - они не смотрят на родительский класс при поиске методов, помеченных как правила, и не смотрят на атрибут родительского класса rules. Это тоже не сложно продлить, если вы этого хотите.

4 голосов
/ 26 апреля 2010

Проблема в том, что во время вызова декоратора decorated там есть нет объекта Blah еще: объект класса создается после , когда тело класса завершает выполнение. Простейшим является decorated сохранение информации «где-то еще», например атрибут функции, затем последний проход (декоратор класса или метакласс) собирает эту информацию в словарь по вашему желанию.

Декораторы классов проще, но они не наследуются (поэтому они не будут исходить от родительского класса), тогда как метаклассы наследуются - так что если вы настаиваете на наследовании, метакласс это должно быть. Проще всего, с декоратором класса и вариантом «list», который у вас есть в начале вашего Q, а не с вариантом «dict», который у вас будет позже:

import inspect

def classdecorator(aclass):
  decorated = []
  for name, value in inspect.getmembers(aclass, inspect.ismethod):
    if hasattr(value, '_decorated'):
      decorated.append(name)
      del value._decorated
  aclass.decorated = decorated
  return aclass

def decorated(afun):
  afun._decorated = True
  return afun

сейчас

@classdecorator
class Blah(object):

    def one(self):
        pass

    @decorated
    def two(self):
        pass

дает вам список Blah.decorated, который вы запрашиваете в первой части вашего Q. Вместо этого, когда вы запрашиваете во второй части вашего Q, создаете дикт, просто означает изменение decorated.append(name) на decorated[name] = value в приведенном выше коде. и, конечно же, инициализация decorated в декораторе класса пустым диктом, а не пустым списком.

Вариант метакласса будет использовать метакласс __init__ для выполнения, по существу, той же постобработки после построения тела класса - метакласс __init__ получает в качестве последнего аргумента диктовку, соответствующую телу класса (но вы Вам придется самостоятельно поддерживать наследование, соответствующим образом обращаясь с аналогичным диктатом или списком любого базового класса). Таким образом, метаклассовый подход на практике «несколько» сложнее, чем декоратор классов, но концептуально он кажется гораздо более сложным для большинства людей. Я дам все детали для метакласса, если они вам понадобятся, но я бы порекомендовал придерживаться более простого декоратора классов, если это возможно.

...