在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')
噢,我没有意识到这是在创建类时,谢谢。是的,在这种情况下,手动设置名称就可以了。