如何使用接受名称列表作为参数的装饰器向类添加属性?

7 投票
2 回答
6560 浏览
提问于 2025-04-17 12:14

我想通过一个装饰器给一个类添加很多虚拟属性,像这样:

def addAttrs(attr_names):
  def deco(cls):
    for attr_name in attr_names:
      def getAttr(self):
        return getattr(self, "_" + attr_name)
      def setAttr(self, value):
        setattr(self, "_" + attr_name, value)
      prop = property(getAttr, setAttr)
      setattr(cls, attr_name, prop)
      setattr(cls, "_" + attr_name, None) # Default value for that attribute
    return cls
  return deco

@addAttrs(['x', 'y'])
class MyClass(object):
  pass

不幸的是,这个装饰器似乎保留了attr_name的引用,而不是它的内容。因此,MyClass.xMyClass.y都访问MyClass._y

a = MyClass()
a.x = 5
print a._x, a._y
>>> None, 5
a.y = 8
print a._x, a._y
>>> None, 8

我需要做什么才能得到预期的效果呢?

2 个回答

3

Python不支持块级作用域,只支持函数级作用域。这意味着在循环中定义的任何变量,循环外部也能访问,并且它的值是循环结束时的最后一个值。如果你想得到你想要的结果,就需要在循环中使用闭包:

def addAttrs(attr_names):
  def deco(cls):
    for attr_name in attr_names:
      def closure(attr):
        def getAttr(self):
          return getattr(self, "_" + attr)
        def setAttr(self, value):
          setattr(self, "_" + attr, value)
        prop = property(getAttr, setAttr)
        setattr(cls, attr, prop)
        setattr(cls, "_" + attr, None)
      closure(attr_name)
    return cls
  return deco

通过使用闭包closure,在getAttrsetAttr中赋值的属性会被正确地作用域控制。

编辑:修正了缩进问题

14

你差不多就要成功了。只差一点小问题。当你创建内部函数的时候,要把当前的 attr_name 的值绑定到获取器和设置器函数里:

def addAttrs(attr_names):
  def deco(cls):
    for attr_name in attr_names:
      def getAttr(self, attr_name=attr_name):
        return getattr(self, "_" + attr_name)
      def setAttr(self, value, attr_name=attr_name):
        setattr(self, "_" + attr_name, value)
      prop = property(getAttr, setAttr)
      setattr(cls, attr_name, prop)
      setattr(cls, "_" + attr_name, None) # Default value for that attribute
    return cls
  return deco

@addAttrs(['x', 'y'])
class MyClass(object):
  pass

这样就能得到你想要的结果:

>>> a = MyClass()
>>> a.x = 5
>>> print a._x, a._y
5 None
>>> a.y = 8
>>> print a._x, a._y
5 8

希望这能帮到你。祝你装饰愉快 :-)

撰写回答