描述符协议工作正常,但我仍有一个问题需要解决。
我有一个描述符:
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 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
到目前为止,非常好,这与预期的一样。
我在这里唯一的问题是每次声明一个字段时我们必须给出一个字段名称。例如"country_code_2"
字段的country_code_2
。
如何获取模型类的属性名称并在字段类中使用它?
答案 0 :(得分:17)
有一种简单的方法,而且有一种困难的方法。
简单的方法是使用Python 3.6(或更新版本),并为描述符添加额外的object.__set_name__()
method:
def __set_name__(self, owner, name):
self.name = '_' + name
创建类时,Python会自动在您在类上设置的任何描述符上调用该方法,并传入类对象和属性名称。
对于早期的Python版本,最好的下一个选项是使用metaclass;它将被创建的每个子类调用,并给出一个方便的字典映射属性名称到属性值(包括你描述符实例)。然后,您可以使用此机会将该名称传递给描述符:
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
这在字段上调用相同的__set_name__()
方法,Python 3.6本身支持。然后将其用作BaseModel
的元类:
class BaseModel(object, metaclass=BaseModelMeta):
# Python 3
或
class BaseModel(object):
__metaclass__ = BaseModelMeta
# Python 2
您还可以使用类装饰器为您装饰它的任何类进行__set_name__
调用,但这需要您装饰每个类。元类通过继承层次结构自动传播。
答案 1 :(得分:0)
我在我的书 Python Descriptors 中详细介绍了这一点,但我还没有更新到第二版以在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
考虑到每次使用描述符的名称时,都涉及实例,这不应该太难以弄清楚如何使用。一旦你第一次查看它,你也可以找到一种方法来缓存这个名字。