对 python 类同时使用 __setattr__ 和描述符

发布于 2025-01-02 11:52:21 字数 1634 浏览 0 评论 0原文

我正在编写一个 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

prints:

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

描述符的 __set__ 从未被调用。

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

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

I'm writing a python class that uses __setattr__ and __getattr__ to provide custom attribute access.

However, some attributes can't be handled in a generic way, so I was hoping to use descriptors for those.

A problem arises in that for a descriptor, the descriptor's __get__ will be invoked in favour of the instances __getattr__, but when assigning to an attribute, __setattr__ will be invoked in favour of the descriptors __set__.

An example:

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

prints:

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

The descriptor's __set__ is never called.

Since the __setattr__ definition isn't just overriding behaviour for a limited set of names, there's no clear place that it can defer to object.__setattr__

Is there a recommended way to have assigning to attributes use the descriptor, if available, and __setattr__ otherwise?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

毅然前行 2025-01-09 11:52:21

更新

10多年后重新审视这个答案,我意识到我包含了一个食谱,但没有解释OP遇到的突出行为:在一个descritpor的__get__之前被调用实例的 __getattr__ ,是的。但与 __setattr__ 实际相反的是 __getattribute__,而不是 __getattr__。在 object__getattribute__ 中,首先是属性搜索顺序的代码,以及负责调用描述符的 __get__ 的代码本身。在检查任何可能的描述符、__slots__ 和实例的 __dict__ 之后,__getattr____getattribute__ 本身中的代码调用,作为最后的手段。

原始答案:只做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

update

Revisiting this answer more than 10 years later, I realized that I included a recipe, but did not explain the outstanding behavior the OP met: a descritpor's __get__ is called before an instance's __getattr__, yes. But the actual converse to __setattr__ is __getattribute__, not __getattr__. Inside object's __getattribute__ is where is the code with the search order for attributes in first place, and the code responsible for calling a descriptor's __get__ itself. __getattr__ is called by the code in __getattribute__ itself as a last resort, after inspecting any possible descriptors, __slots__, and the instance's __dict__.

original answer: just do what the OP wants to achieve

I think I'd approach this by having a mechanism to automatically mark which are the
descriptors in each class, and wrap the __setattr__ in a way that it'd call
object's normal behavior for those names.

This can be easily achieved with a metaclass (and a decorator for __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
时光暖心i 2025-01-09 11:52:21

可能的方法之一:

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__

PS

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

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

One of possible ways:

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

It checks if name already defined in class, base classes or instance and then it calls object.__setattr__ which will execute descriptor __set__.

Another way:

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)

But it will call descriptor's __get__.

P.S.

I'm not sure about need to check all __mro__ members since MyObj will contain inherited class members in __dict__.

Maybe for cls in (self.__class__, self):... will be enough.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文