关于属性:为python类使用__setattr__和descriptors

Using both __setattr__ and descriptors for a python class

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

但是,有些属性不能用一般的方式处理,所以我希望对它们使用描述符。

出现了一个问题,对于描述符来说,描述符的__get__将以实例__getattr__的形式被调用,但是当分配给属性时,__setattr__将以描述符__set__的形式被调用。

一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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

印刷品:

1
2
3
4
5
6
MyObj.__setattr__ for bar
MyObj.__getattr__ for bar
baz
MyObj.__setattr__ for foo
MyDesc.__get__
None

描述符的__set__从未被调用。

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

是否有一种建议的方法可以让分配给属性使用描述符(如果可用),否则使用__setattr__


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

这可以通过一个元类(和一个__setattr__的修饰器)很容易实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

可能的方法之一:

1
2
3
4
5
6
7
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__

另一种方式:

1
2
3
4
5
6
7
8
9
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):...就足够了。