【问题标题】:Using both __setattr__ and descriptors for a python class对 python 类同时使用 __setattr__ 和描述符
【发布时间】:2012-02-06 14:06:41
【问题描述】:

我正在编写一个使用__setattr____getattr__ 提供自定义属性访问的python 类。

但是,有些属性不能以通用方式处理,所以我希望对这些属性使用描述符。

出现一个问题,对于描述符,描述符的__get__ 将被调用以支持实例__getattr__,但是当分配给一个属性时,__setattr__ 将被调用以支持描述符__set__ .

一个例子:

class MyDesc(object):

    def __init__(self):
        self.val = None

    def __get__(self, instance, owner):
        print "MyDesc.__get__"
        return self.val

    def __set__(self, instance, value):
        print "MyDesc.__set__"
        self.val = value

class MyObj(object):

    foo = MyDesc()

    def __init__(self, bar):
        object.__setattr__(self, 'names', dict(
            bar=bar,
        ))
        object.__setattr__(self, 'new_names', dict())

    def __setattr__(self, name, value):
        print "MyObj.__setattr__ for %s" % name
        self.new_names[name] = value

    def __getattr__(self, name):
        print "MyObj.__getattr__ for %s" % name

        if name in self.new_names:
            return self.new_names[name]

        if name in self.names:
            return self.names[name]

        raise AttributeError(name)

if __name__ == "__main__":
    o = MyObj('bar-init')

    o.bar = 'baz'
    print o.bar

    o.foo = 'quux'
    print o.foo

打印:

MyObj.__setattr__ for bar
MyObj.__getattr__ for bar
baz
MyObj.__setattr__ for foo
MyDesc.__get__
None

描述符的__set__ 永远不会被调用。

由于__setattr__ 定义不只是覆盖有限名称集的行为,因此没有明确的地方可以遵从object.__setattr__

是否有推荐的方法来分配属性使用描述符(如果可用),否则使用__setattr__

【问题讨论】:

    标签: python attributes setter descriptor


    【解决方案1】:

    我想我会通过一种机制来自动标记哪些是 每个类中的描述符,并以它调用的方式包装__setattr__ 对象对这些名称的正常行为。

    这可以通过元类(以及__setattr__ 的装饰器)轻松实现

    def setattr_deco(setattr_func):
        def setattr_wrapper(self, attr, value):
            if attr in self._descriptors:
                return object.__setattr__(self, attr, value)
            return setattr_func(self, attr, value)
        return setattr_wrapper
    
    class MiscSetattr(type):
        def __new__(metacls, name, bases, dct):
            descriptors = set()
            for key, obj in dct.items():
                if key == "__setattr__":
                    dct[key] = setattr_deco(obj)
                elif hasattr(obj, "__get__"):
                    descriptors.add(key)
            dct["_descriptors"] = descriptors
            return type.__new__(metacls, name, bases, dct)
    
    # and use MiscSetattr as metaclass for your classes
    

    【讨论】:

      【解决方案2】:

      一种可能的方式:

      def __setattr__(self, name, value):
          print "MyObj.__setattr__ for %s" % name
          for cls in self.__class__.__mro__ + (self, ):
              if name in cls.__dict__:
                  return object.__setattr__(self, name, value)
          print 'New name', name, value
          self.new_names[name] = value
      

      它检查名称是否已经在类、基类或实例中定义,然后调用object.__setattr__,它将执行描述符__set__

      另一种方式:

      def __setattr__(self, name, value):
          print "MyObj.__setattr__ for %s" % name
          try:
              object.__getattribute__(self, name)
          except AttributeError:
              print 'New name', name, value
              self.new_names[name] = value
          else:
              object.__setattr__(self, name, value)
      

      但它会调用描述符的__get__

      附言

      我不确定是否需要检查所有__mro__ 成员,因为MyObj 将包含__dict__ 中的继承类成员。

      也许for cls in (self.__class__, self):... 就足够了。

      【讨论】:

      • 你的第二种方式行不通。在我的示例中,您可以在尚不存在的对象上设置新属性,而此方法将不允许这样做。
      • @SpoonMeiser,我认为它和你的一样。看看ideone.com/HMBcc。也许我错过了什么。
      • 如果我设置了o.quux = 3,那么o.__dict__['quux'] 将被设置而不是o.new_names['quux']
      • @SpoonMeiser,我无法重现。你的 Python 版本是什么? ideone.com/XXAR5
      • 有一个问题,如果__get__ 有时可能会引发 AttributeError
      猜你喜欢
      • 2021-04-08
      • 1970-01-01
      • 2017-11-30
      • 1970-01-01
      • 1970-01-01
      • 2021-10-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多