在Python中使用描述符协议时,如何获取属性名?

在Python中使用描述符协议时,如何获取属性名?,python,descriptor,python-descriptors,Python,Descriptor,Python Descriptors,描述符协议工作正常,但我仍有一个问题需要解决 我有一个描述符: class Field(object): def __init__(self, type_, name, value=None, required=False): self.type = type_ self.name = "_" + name self.required = required self._value = value def __get

描述符协议工作正常,但我仍有一个问题需要解决

我有一个描述符:

class Field(object):
    def __init__(self, type_, name, value=None, required=False):
        self.type = type_
        self.name = "_" + name
        self.required = required
        self._value = value

    def __get__(self, instance, owner):
        return getattr(instance, self.name, self.value)

    def __set__(self, instance, value):
        if value:
            self._check(value)
            setattr(instance, self.name, value)
        else:
            setattr(instance, self.name, None)

    def __delete__(self, instance):
        raise AttributeError("Can't delete attribute")

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, value):
        self._value = value if value else self.type()

    @property
    def _types(self):
        raise NotImplementedError

    def _check(self, value):
        if not isinstance(value, tuple(self._types)):
            raise TypeError("This is bad")
class BaseModelMeta(type):
    def __new__(mcls, name, bases, attrs):
        cls = super(BaseModelMeta, mcls).__new__(mcls, name, bases, attrs)
        for attr, obj in attrs.items():
            if isinstance(obj, Field):
                obj.__set_name__(cls, attr)
        return cls
这是一个子类:

class CharField(Field):
    def __init__(self, name, value=None, min_length=0, max_length=0, strip=False):
        super(CharField, self).__init__(unicode, name, value=value)
        self.min_length = min_length
        self.max_length = max_length
        self.strip = strip

    @property
    def _types(self):
        return [unicode, str]

    def __set__(self, instance, value):
        if self.strip:
            value = value.strip()

        super(CharField, self).__set__(instance, value)
然后使用一个模型类:

class Country(BaseModel):
    name = CharField("name")
    country_code_2 = CharField("country_code_2", min_length=2, max_length=2)
    country_code_3 = CharField("country_code_3", min_length=3, max_length=3)

    def __init__(self, name, country_code_2, country_code_3):
        self.name = name
        self.country_code_2 = country_code_2
        self.country_code_3 = country_code_3
到目前为止,一切都很好,正如预期的那样

我在这里遇到的唯一问题是,每次声明一个字段时,我们都必须给出一个字段名。e、 g.
国家/地区代码2
字段的
国家/地区代码2


如何获取模型类的属性名并在field类中使用它?

有一种简单的方法,也有一种困难的方法

简单的方法是使用Python 3.6(或更高版本),并为描述符提供一个额外的:

当创建一个类时,Python会自动对您在该类上设置的任何描述符调用该方法,并传入类对象和属性名

对于早期的Python版本,下一个最佳选择是使用;将为创建的每个子类调用它,并提供一个方便的字典,将属性名称映射到属性值(包括描述符实例)。然后,您可以利用此机会将该名称传递给描述符:

class Field(object):
    def __init__(self, type_, name, value=None, required=False):
        self.type = type_
        self.name = "_" + name
        self.required = required
        self._value = value

    def __get__(self, instance, owner):
        return getattr(instance, self.name, self.value)

    def __set__(self, instance, value):
        if value:
            self._check(value)
            setattr(instance, self.name, value)
        else:
            setattr(instance, self.name, None)

    def __delete__(self, instance):
        raise AttributeError("Can't delete attribute")

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, value):
        self._value = value if value else self.type()

    @property
    def _types(self):
        raise NotImplementedError

    def _check(self, value):
        if not isinstance(value, tuple(self._types)):
            raise TypeError("This is bad")
class BaseModelMeta(type):
    def __new__(mcls, name, bases, attrs):
        cls = super(BaseModelMeta, mcls).__new__(mcls, name, bases, attrs)
        for attr, obj in attrs.items():
            if isinstance(obj, Field):
                obj.__set_name__(cls, attr)
        return cls
这将调用与Python3.6本机支持的字段上相同的
\uuuu set\u name\uuu()
方法。然后将其用作
BaseModel
的元类:

class BaseModel(object, metaclass=BaseModelMeta):
    # Python 3


您还可以使用类装饰器为任何装饰它的类执行
\uuu set\u name\uuuu
调用,但这要求您装饰每个类。元类会自动通过继承层次结构进行传播。

我在书中介绍了这一点,尽管我还没有更新到第二版,以便在3.6中添加新功能。除此之外,它是一个关于描述符的相当全面的指南,仅在一个特性上就花费了60页

无论如何,不使用元类获取名称的方法是使用以下非常简单的函数:

def name_of(descriptor, instance):
    attributes = set()
    for cls in type(instance).__mro__:
        # add all attributes from the class into `attributes`
        # you can remove the if statement in the comprehension if you don't want to filter out attributes whose names start with '__'
        attributes |= {attr for attr in dir(cls) if not attr.startswith('__')}
    for attr in attributes:
        if type(instance).__dict__[attr] is descriptor:
            return attr

考虑到每次使用描述符的名称时都会涉及到实例,这应该不太难理解如何使用。您还可以在第一次查找名称时找到一种缓存名称的方法。

这还不够;描述词跟在MRO后面。你的评论很难理解;我想你的意思是说函数没有得到超类的成员?我正在努力;我将在几分钟内完成这项工作。编辑已完成。幸运的是,在类属性(如
MyClass.aDescriptor=MyDescriptor()
)上未调用
\u set\u name\u
,这在动态添加描述符时会很有帮助。当然,您仍然可以手动调用它-只是觉得没有必要。@aaaaaa:
\uuuu set\u name\uuuu
仅在创建类时调用,因此不,它不会仅在setitem上启动。您可以给
MyDescriptor()
一个可选的
name
参数,用于设置与
\uuuu set\u name\uuuuu
相同的值
MyClass.aDescriptor=MyDescriptor('aDescriptor')
噢,我没有意识到这是在创建类时,谢谢。是的,在这种情况下,手动设置名称就可以了。