python-3.x - python3在元类中实现数据描述符
问题描述
在元类中实现数据描述符的正确方法是什么?在下面的(简单的)示例中,我希望始终在设置所需的值之前附加一个问号:
class AddQDesc:
def __init__ (self, name):
self.name = name
def __get__ (self, instance, owner=None):
obj = instance if instance != None else owner
return obj.__dict__[self.name]
def __set__ (self, instance, value):
# What should go here ?
#setattr(instance, self.name, "{}?".format(value)) <- This gives me recursion error
#instance.__dict__[self.name] = "{}?".format(value) <- This gives me proxymapping error
pass
class Meta (type):
var = AddQDesc("var")
class C (metaclass=Meta):
var = 5
print(C.var)
C.var = 1
print(C.var)
首先,当我将 var 初始化为 5 时,似乎没有使用描述符。我也可以在这里应用描述符协议吗?(设为“5?”)其次,在 __set__ 方法中应该如何更新值?更新 __dict__ 给我"TypeError: 'mappingproxy' object does not support item assignment"并使用 setattr 给我"RecursionError: maximum recursion depth exceeded while calling a Python object"。
解决方案
正如我在问题中评论的那样,这很棘手 - 因为 Python 代码无法__dict__
直接更改类的属性 - 必须调用setattr
并让 Python 设置类属性 - 并且,setattr
将“看到”元类中的描述符, 并调用它__set__
而不是修改类__dict__
本身的值。所以,你得到一个无限递归循环。
因此,如果你真的要求descriptor所代理的属性在class'dict中同名“活”,你就不得不求助于:在设置值的时候,暂时从metaclass中移除descriptor,调用setattr
set值,然后恢复它。
此外,如果您希望通过描述符处理类主体中设置的值,则必须在setattr
创建类后设置它们 -type.__new__
在构建初始类时不会检查描述符__dict__
。
from threading import Lock
class AddQDesc:
def __init__ (self, name):
self.name = name
self.lock = Lock()
def __get__ (self, instance, owner=None):
obj = instance if instance != None else owner
return obj.__dict__[self.name]
def __set__ (self, instance, value):
owner_metaclass = type(instance)
with self.lock:
# Temporarily remove the descriptor to avoid recursion problems
try:
# Note that a metaclass-inheritance hierarchy, where
# the descriptor might be placed in a superclass
# of the instance's metaclass, is not handled here.
delattr(owner_metaclass, self.name)
setattr(instance, self.name, value + 1)
finally:
setattr(owner_metaclass, self.name, self)
class Meta (type):
def __new__(mcls, name, bases, namespace):
post_init = {}
for key, value in list(namespace.items()):
if isinstance(getattr(mcls, key, None), AddQDesc):
post_init[key] = value
del namespace[key]
cls = super().__new__(mcls, name, bases, namespace)
for key, value in post_init.items():
setattr(cls, key, value)
return cls
var = AddQDesc("var")
class C (metaclass=Meta):
var = 5
print(C.var)
C.var = 1
print(C.var)
如果您不需要将值保存在类中' __dict__
,我建议将其存储在其他地方-例如描述符实例中的字典,将类作为键就足够了-并且不会那么奇怪。
class AddQDesc:
def __init__ (self, name):
self.name = name
self.storage = {}
def __get__ (self, instance, owner):
if not instance: return self
return self.storage[instance]
def __set__ (self, instance, value):
self.storage[instance] = value + 1
推荐阅读
- drupal - drupal8:如何安装模块?
- react-native - 如何在 React Navigation V5 中为每次启动启动启动画面?
- excel - 格式化单元格以根据行号引用另一个工作表
- angular - 当 auth Guard 发送用户登录或注册时,Angular 9 和 Ionic 重定向到上一页
- django - Django 3 设置 django_language cookie
- laravel - laravel 表单搜索数据库中不可用的数据
- android - 如何以编程方式为工具栏内的所有图标设置 colorControlHighlight (波纹颜色)?
- python - Python:解析连接的 JSON
- java - Google App Engine MemcacheService.delete(key, millisNoReAdd) 并不总是支持并发推杆
- machine-learning - LSTM 中的 NumHiddenUnits