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

Joh*_*eer 9 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__(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")
Run Code Online (Sandbox Code Playgroud)

这是子类:

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)
Run Code Online (Sandbox Code Playgroud)

然后使用的是模型类:

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
Run Code Online (Sandbox Code Playgroud)

到目前为止,这么好,这个工作正如预期的那样.

我在这里唯一的问题是每次声明一个字段时我们必须给出一个字段名称.例如"country_code_2",对于country_code_2田地.

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

Mar*_*ers 21

有一种简单的方法,并且有一种困难的方法.

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

def __set_name__(self, owner, name):
    self.name = '_' + name
Run Code Online (Sandbox Code Playgroud)

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

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

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
Run Code Online (Sandbox Code Playgroud)

__set_name__()在字段上调用相同的方法,Python 3.6本身支持.然后使用它作为元类BaseModel:

class BaseModel(object, metaclass=BaseModelMeta):
    # Python 3
Run Code Online (Sandbox Code Playgroud)

要么

class BaseModel(object):
    __metaclass__ = BaseModelMeta
    # Python 2
Run Code Online (Sandbox Code Playgroud)

你也可以使用类装饰器为__set_name__你装饰的任何类调用,但这需要你装饰每个类.元类会自动通过继承层次结构传播.