同时使用 __setattr__ 和 python 类描述符

问题描述 投票:0回答:2

我正在编写一个 python 类,它使用

__setattr__
__getattr__
来提供自定义属性访问。

但是,某些属性无法以通用方式处理,因此我希望对这些属性使用描述符。

出现的问题是,对于描述符,描述符的

__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
2个回答
5
投票

更新

十多年后重新审视这个答案,我意识到我包含了一个食谱,但没有解释OP遇到的突出行为:在实例的

__get__
之前调用描述符的
__getattr__
,是的。但与
__setattr__
实际相反的是
__getattribute__
,而不是
__getattr__
。在
object
__getattribute__
内部,首先是属性搜索顺序的代码,以及负责调用描述符的
__get__
本身的代码。在检查任何可能的描述符
__getattr__
和实例的
__getattribute__
后,
__slots__
__dict__
本身中的代码调用,作为最后的手段。

原始答案:只做OP想要实现的目标

我想我会通过一种机制来自动标记哪些是 每个类中的描述符,并以调用的方式包装

__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

5
投票

可能的方法之一:

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):...
就足够了。

© www.soinside.com 2019 - 2024. All rights reserved.