Python装饰器用于修改当前作用域中的变量

4

目标:创建一个装饰器,可以修改其所使用的作用域。

如果它起作用:

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

您可以使用类装饰器(在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),而且元类是可以继承的。使用装饰器,子类必须分别应用装饰器(如果他们想设置规则属性)。
这两种解决方案都不考虑继承——在寻找标记为规则的方法时,它们不会查看父类,也不会查看父类的规则属性。如果需要,扩展其中任何一种以执行此操作并不困难。

Python 2.6 中没有类装饰器。 - Torsten Marek
是的,有的。在发布它们之前,我实际上测试了这两个代码片段。它们在Python 2.6中运行得很好。 - Thomas Wouters
如果您将类装饰器版本更改为使用inspect.getmembers,就可以处理继承 - 然后我可以删除我的答案,因为它大部分是重复的(不出所料;-),但您的答案更完整。 - Alex Martelli
不,你应该保留你的代码来处理这个问题;在这种情况下,我不喜欢使用 inspect.getmembers。 :) - Thomas Wouters

4
问题在于,在调用decorated装饰器时,还没有对象Blah:类对象是在类体执行完成后构建的。最简单的方法是让decorated将信息“存储在其他地方”,例如函数属性,然后通过最终步骤(类装饰器或元类)将该信息收集到所需的字典中。
类装饰器更简单,但它们不会被继承(因此它们不会来自父类),而元类是可以被继承的——因此如果您坚持要继承,那么就必须使用元类。最简单的方法是使用类装饰器和您在Q开头拥有的“list”变量,而不是后来拥有的“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 列表会被返回。如果按照你在问题的第二部分中的要求构建一个字典,只需要将上面代码中的 decorated.append(name) 改为 decorated[name] = value,当然还需要在类装饰器中将 decorated 初始化为空字典而不是空列表。

元类变种将使用元类的 __init__ 在类体构建完成后执行基本相同的后处理操作 -- 元类的 __init__ 会得到一个对应于类体的字典作为其最后一个参数(但你必须通过适当地处理任何基类的类似字典或列表来自己支持继承)。因此,在实践中,元类方法只比类装饰器略微复杂,但从概念上讲,大多数人都认为它更难理解。如果可以的话,我建议你坚持使用更简单的类装饰器。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接