Python 数据类和属性装饰器
我一直在阅读Python3.7的dataclass作为namedtuples的替代品(我通常在必须将数据分组到结构中时使用)。我想知道dataclass是否与属性装饰器兼容,以便为dataclass的数据元素定义getter和setter函数。如果是这样的话,这是在什么地方描述的吗?或者是否有可用的示例 它确实有效:Python 数据类和属性装饰器,python,python-3.7,python-dataclasses,Python,Python 3.7,Python Dataclasses,我一直在阅读Python3.7的dataclass作为namedtuples的替代品(我通常在必须将数据分组到结构中时使用)。我想知道dataclass是否与属性装饰器兼容,以便为dataclass的数据元素定义getter和setter函数。如果是这样的话,这是在什么地方描述的吗?或者是否有可用的示例 它确实有效: from dataclasses import dataclass @dataclass class Test: _name: str="schbell" @p
from dataclasses import dataclass
@dataclass
class Test:
_name: str="schbell"
@property
def name(self) -> str:
return self._name
@name.setter
def name(self, v: str) -> None:
self._name = v
t = Test()
print(t.name) # schbell
t.name = "flirp"
print(t.name) # flirp
print(t) # Test(_name='flirp')
其实,为何不可以呢?最后,您得到的只是一个很好的旧类,派生自类型:
print(type(t)) # <class '__main__.Test'>
print(type(Test)) # <class 'type'>
print(type(t))#
打印(类型(测试))#
也许这就是为什么没有特别提到财产的原因。但是,本文提到了众所周知的Python类特性的一般可用性:
因为数据类使用普通的类定义语法,所以您是自由的
要使用继承、元类、docstring和用户定义的方法,
类工厂和其他Python类特性
目前,我找到的最佳方法是在单独的子类中按属性覆盖dataclass字段
从数据类导入数据类,字段
@数据类
A类:
x:int=0
A类(_A):
@财产
def x(自)->int:
返回自我
@x、 塞特
def x(自身,值:int):
self._x=值
该类的行为类似于常规数据类。并将正确定义\uuuu repr\uuuu
和\uuuu init\uuuu
字段(A(x=4)
),而不是A(\ux=4)
。缺点是属性不能是只读的
,尝试用同名的属性
覆盖wheels数据类属性。
但是,@属性
会覆盖默认的字段
,从而导致意外行为
从数据类导入数据类,字段
@数据类
A类:
x:int
#与:`x=property(x)#覆盖任何字段()信息相同`
@财产
def x(自)->int:
返回自我
@x、 塞特
def x(自身,值:int):
self._x=值
A()#`A(x=)`Oups
打印(A.。uuuu数据类_uu字段_uuu)#{'x':字段(name='x',type=,default=,init=True,repr=True}
解决这个问题的一种方法是,在调用dataclass元类之后,覆盖类定义之外的字段,同时避免继承
@dataclass
class A:
x: int
def x_getter(self):
return self._x
def x_setter(self, value):
self._x = value
A.x = property(x_getter)
A.x = A.x.setter(x_setter)
print(A(x=1))
print(A()) # missing 1 required positional argument: 'x'
通过创建一些自定义元类并设置一些
字段(metadata={'setter':\ux\usetter,'getter':\ux\ugetter}),可能会自动覆盖此内容。
一些包装可能很好:
#做你他妈想公开许可的事
#第2版,2004年12月
#
#版权所有(C)2020徐思远
#
#每个人都被允许逐字复制和分发或修改
#此许可证文档的副本,并允许在
#随着名字的改变。
#
#你他妈的想做什么就做什么
#复制、分发和修改的条款和条件
#
#你他妈的想干什么就干什么。
从数据类导入数据类,字段
缺少=对象()
__all_uuu=['property_字段','property_数据类']
类属性\u字段:
定义初始化(self,fget=None,fset=None,fdel=None,doc=None,**kwargs):
self.field=字段(**kwargs)
self.property=属性(fget、fset、fdel、doc)
def吸气剂(自身,fget):
self.property=self.property.getter(fget)
回归自我
def设定器(自身、fset):
self.property=self.property.setter(fset)
回归自我
def删除器(自身、fdel):
self.property=self.property.deleter(fdel)
回归自我
def属性_数据类(cls=缺失,/,**kwargs):
如果缺少cls:
返回lambda cls:property_数据类(cls,**kwargs)
记住={}
对于k in dir(cls):
如果isinstance(getattr(cls,k),属性字段):
记住[k]=getattr(cls,k).property
setattr(cls,k,getattr(cls,k).field)
结果=数据类(**kwargs)(cls)
对于k,p在.items()中:
设置属性(结果,k,p)
返回结果
您可以这样使用它:
@property\u数据类
B类:
x:int=property\u字段(默认工厂=int)
@x、 吸气剂
def x(自我):
返回自我
@x、 塞特
def x(自身,值):
self._x=值
根据上面的想法,我创建了一个类装饰函数resolve\u abc\u prop
,它创建了一个新类,其中包含建议的getter和setter函数
作者@shmee
def resolve_abc_prop(cls):
def gen_抽象_属性():
“”“在超类中搜索抽象属性”“”
对于cls中的obj类。mro:
对于键,类\u obj.\u dict\u.items()中的值:
如果是实例(值、属性)和值。\u isabstractmethod\uuuuu:
屈服键、屈服值
abstract\u prop=dict(gen\u abstract\u properties())
def gen_get_set_属性():
“”对于每个匹配的数据和抽象属性对,
创建getter和setter方法“”
对于cls中的obj类。mro:
如果类对象中的“\uuuuu数据类\uu字段”\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
对于键,类对象中的值。字段():
如果输入抽象属性:
def get_func(自身,键=键):
返回getattr(self,f''.{key}')
def set_func(self,val,key=key):
返回setattr(self,f'{key}',val)
屈服键,属性(get_func,set_func)
get\u set\u properties=dict(gen\u get\u set\u properties())
new_cls=类型(
cls.\uuuuuu名称\uuuuuuuuu,
cls.\uuuuMRO\uuuuuuuuuuu,
{**cls.\uuuuu dict\uuuuuu,**get\u set\u properties},
)
返回新的\u cls
这里我们定义一个数据类AData
和一个mixinAOpMixin
实现操作
关于数据
从数据类导入数据类,字段,替换
来自abc i
from dataclasses import dataclass, field
@dataclass
class Test:
name: str
_name: str = field(init=False, repr=False)
@property
def name(self) -> str:
return self._name
@name.setter
def name(self, name: str) -> None:
self._name = name
my_test = Test(name='foo')
my_test.name = 'bar'
my_test.name('foobar')
print(my_test.name)
from dataclasses import dataclass, field
@dataclass
class Test:
name: str
_name: str = field(init=False, repr=False, default='baz')
@property
def name(self) -> str:
return self._name
@name.setter
def name(self, value: str) -> None:
if type(value) is property:
# initial value not specified, use default
value = Test._name
self._name = value
def main():
obj = Test(name='foo')
print(obj) # displays: Test(name='foo')
obj = Test()
obj.name = 'bar'
print(obj) # displays: Test(name='bar')
obj = Test()
print(obj) # displays: Test(name='baz')
if __name__ == '__main__':
main()
from dataclasses import dataclass
@dataclass
class Test:
name: str = 'foo'
@property
def _name(self):
return self._my_str_rev[::-1]
@_name.setter
def _name(self, value):
self._my_str_rev = value[::-1]
# --- has to be called at module level ---
Test.name = Test._name
def main():
obj = Test()
print(obj) # displays: Test(name='foo')
obj = Test()
obj.name = 'baz'
print(obj) # displays: Test(name='baz')
obj = Test(name='bar')
print(obj) # displays: Test(name='bar')
if __name__ == '__main__':
main()
Test(name='schbell')
schbell
{'name': 'not-schbell', '_name': 'not-schbell'}
Test(name='llebhcs')
llebhcs
{'name': 'llebhcs', '_name': 'llebhcs'}
from dataclasses import dataclass, InitVar, field, asdict
@dataclass
class D:
a: float = 10. # Normal attribut with a default value
b: InitVar[float] = 20. # init-only attribute with a default value
c: float = field(init=False) # an attribute that will be defined in __post_init__
def __post_init__(self, b):
if not isinstance(getattr(D, "a", False), property):
print('setting `a` to property')
self._a = self.a
D.a = property(D._get_a, D._set_a)
print('setting `c`')
self.c = self.a + b
self.d = 50.
def _get_a(self):
print('in the getter')
return self._a
def _set_a(self, val):
print('in the setter')
self._a = val
if __name__ == "__main__":
d1 = D()
print(asdict(d1))
print('\n')
d2 = D()
print(asdict(d2))
setting `a` to property
setting `c`
in the getter
in the getter
{'a': 10.0, 'c': 30.0}
in the setter
setting `c`
in the getter
in the getter
{'a': 10.0, 'c': 30.0}
from dataclasses import dataclass
@dataclass
class Person:
name: str = property
@name
def name(self) -> str:
return self._name
@name.setter
def name(self, value) -> None:
self._name = value
def __post_init__(self) -> None:
if isinstance(self.name, property):
self.name = 'Default'
print(Person().name) # Prints: 'Default'
print(Person('Joel').name) # Prints: 'Joel'
print(repr(Person('Jane'))) # Prints: Person(name='Jane')
@dataclass
def SomeData:
uid: str
_uid: ClassVar[str]
@property
def uid(self) -> str:
return self._uid
@uid.setter
def uid(self, uid: str) -> None:
self._uid = uid
@dataclass
class Test:
x: int = 1
def __setattr__(self, prop, val):
if prop == "x":
self._check_x(val)
super().__setattr__(prop, val)
@staticmethod
def _check_x(x):
if x <= 0:
raise ValueError("x must be greater than or equal to zero")