【问题标题】:Assigning Descriptor attribute outside class在类外分配描述符属性
【发布时间】:2017-03-08 20:21:20
【问题描述】:

我有一个用于从数据存储延迟加载的描述符,如果数据存储不可用,它会返回默认值。描述符工作正常,除了我希望默认值是提供的类类型的实例,该类类型与定义描述符的类类型相同(树层次结构需要)。理想情况下我想这样做

from weakref import WeakKeyDictionary

class Descriptor(object):
    def __init__(self, classType=None):
        self.classType = classType
        self.values = WeakKeyDictionary()
    def __set_name__(self, owner, name):
        self.name = name
    def __get__(self, instance, owner):
        print("name:", self.name)
        if instance not in self.values:
            self.values[instance] = self.classType()
        return self.values[instance]
    def __set__(self, instance, value):
        self.values[instance] = value


class Item1(object):
    pass


class Item2(object):
    parent1 = Descriptor(Item1)
    parent2 = Descriptor(Item2)


item2 = Item2()
print(item2.parent1)
print(item2.parent2)

但显然 parent2 的描述符不能传递给它正在定义的类。因此,另一种方法是在定义类之后分配 parent2:

class Item2(object):
    parent1 = Descriptor(Item1)


Item2.parent2 = Descriptor(Item2)

但是,在这种情况下,创建构造函数时似乎没有调用 __set_name__ 方法,因为给出了以下错误:

name: parent1
<__main__.Item1 object at 0x021F4890>
Traceback (most recent call last):
  File "..\setNameTest.py", line 28, in <module>
    print(item2.parent2)
  File "..\setNameTest.py", line 10, in __get__
    print("name:", self.name)
AttributeError: 'Descriptor' object has no attribute 'name'

似乎可行的是在类中创建描述符后将 itemClass 值分配给 parent2,但在整个代码库中使用似乎很笨重:

class Item2(object):
    parent1 = Descriptor(Item1)
    parent2 = Descriptor()


Item2.__dict__['parent2'].classType = Item2

有没有更清洁的方法?

2017 年 3 月 9 日编辑: 根据https://stackoverflow.com/a/6995286/1834561,如果实例为 None,则可以通过从 Descriptor.__get__ 返回 self 来稍微清理一下此方法,即

    def __get__(self, instance, owner):
        if instance is None:
           return self
        print("name:", self.name)
        if instance not in self.values:
            self.values[instance] = self.classType()
        return self.values[instance]

现在可以在类定义之外分配classType

Item2.parent2.classType = Item2

【问题讨论】:

  • 您可以使用元类,但我不确定您的描述符能否正常工作...您想用 __set_name__ 方法做什么?
  • 我正在使用 _set_name_ 获取描述符分配的名称,并在与数据存储交互时使用该名称
  • 抱歉,这是 Python 36 的新功能

标签: python


【解决方案1】:

因此,当您需要在类级别范围内访问类本身时,您必须使用 元类。但是,我不熟悉新的__set_name__ dunder 方法,我无法让它与元类一起使用。然而,这里有另一种方式——旧方式——。首先,您必须将name 传递给您的Descriptor __init__

In [10]: from weakref import WeakKeyDictionary
    ...:
    ...: class Descriptor(object):
    ...:     def __init__(self, name, classType=None):
    ...:         self.classType = classType
    ...:         self.values = WeakKeyDictionary()
    ...:         self.name = name
    ...:     def __get__(self, instance, owner):
    ...:         print("name:", self.name)
    ...:         if instance not in self.values:
    ...:             self.values[instance] = self.classType()
    ...:         return self.values[instance]
    ...:     def __set__(self, instance, value):
    ...:         self.values[instance] = value
    ...:
    ...:
    ...: class Item1(object):
    ...:     pass
    ...:

现在,定义您的元类。这可能可以更好地封装,但它可以传达这个想法。再次注意,我将name 传递给Descriptor

In [11]: class ItemMeta(type):
    ...:     def __init__(cls, name, bases, dct):
    ...:         cls.parent1 = Descriptor('parent1', Item1)
    ...:         cls.parent2 = Descriptor('parent2', cls)
    ...:
    ...:

最后,使用 Python3 语法,我们使用元类创建 Item2

In [12]: class Item2(object, metaclass=ItemMeta):
    ...:     pass
    ...:

现在,它应该可以工作了:

In [13]: x = Item2()

In [14]: x.parent1
name: parent1
Out[14]: <__main__.Item1 at 0x1104db240>

In [15]: x.parent2
name: parent2
Out[15]: <__main__.Item2 at 0x1104dbc50>

【讨论】:

  • 不错的解决方案。我喜欢元类保留类定义之前和顶部的所有定义,但需要重述属性名称并不理想。
  • @jeshep 我同意,这不太理想。虽然,一方面,它符合“显式胜于隐式”。但有时,隐式很好。我仍然希望有一些方法可以使元类与__set_name__ 一起工作,但我只是没有足够地使用 py36。我知道一个用来模拟__set_name__ 功能的方法是使用元类,但在我的尝试中我无法成功地将这两种方法结合起来。不过,我对这向我揭示的数据模型的知识肯定存在一些差距。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多