如何装饰class并使用描述符访问属性?
How to decorate a class and use descriptors to access properties?
我正在努力掌握(开始 ;))以了解如何在 Python 上正确使用装饰器和描述符 3. 我想出了一个想法,我想弄清楚如何对其进行编码.
我希望能够创建一个 class A 装饰某些 "function" B 或 "class" B 允许我在删除 A 上的属性后创建 A 的实例成为某种类型的组件并在 A __init__
魔法函数上赋值。例如:
componentized 确定 "function B" 或 "class B" 允许我声明一个 class Vector。我声明 x 和 y 是一个 component(float) 像这样:
@componentized
class Vector:
x = component(float)
y = component(float)
def __init__ (self, x, y):
self.x = x
self.y = y
我的想法是能够做到这一点:
v = Vector(1,2)
v.x #returns 1
但主要目标是我想为每个标记的 component(float) 属性:
执行此操作
v.xy #returns a tuple (1,2)
v.xy = (3,4) #assigns to x the value 3 and y the value 4
我的想法是创建一个装饰器 @componentized 来覆盖 __getattr__
和 __setattr__
魔法方法。排序如下:
def componentized(cls):
class Wrapper(object):
def __init__(self, *args):
self.wrapped = cls(*args)
def __getattr__(self, name):
print("Getting :", name)
if(len(name) == 1):
return getattr(self.wrapped, name)
t = []
for x in name:
t.append(getattr(self.wrapped, x))
return tuple(t)
@componentized
class Vector(object):
def __init__(self, x, y):
self.x = x
self.y = y
它有点奏效,但我不认为我完全理解发生了什么。因为当我尝试分配并覆盖 __setattr__
魔术方法时,即使在我实例化 class 时它也会被调用。在下面的例子中两次:
vector = Vector(1,2)
vector.x = 1
我怎样才能实现那种行为?
提前致谢!如果需要更多信息,请随时询问!
编辑:
根据@Diego 的回答,我设法做到了:
def componentized(cls):
class wrappedClass(object):
def __init__(self, *args, **kwargs):
t = cls(*args,**kwargs)
self.wrappedInstance = t
def __getattr__(self, item):
if(len(item) == 1):
return self.wrappedInstance.__getattribute__(item)
else:
return tuple(self.wrappedInstance.__getattribute__(char) for char in item)
def __setattr__(self, attributeName, value):
if isinstance(value, tuple):
for char, val in zip(attributeName, value):
self.wrappedInstance.__setattr__(char, val)
elif isinstance(value, int): #EMPHASIS HERE
for char in attributeName:
self.wrappedInstance.__setattr__(char, value)
else:
object.__setattr__(self, attributeName, value)
return wrappedClass
并且有一个 class Vector 这样的:
@componentized
class Vector:
def __init__ (self, x, y):
self.x = x
self.y = y
有点像我想要的,但我仍然不知道如何实现:
x = component(float)
y = component(float)
在 Vector class 中以某种方式订阅 x 和 y键入 float,所以当我在代码上执行 #EMPHASIS LINE(在我对特定类型进行硬编码的行中)时,我可以检查是否有人正在为 [=77= 赋值]x and/or y 对于 Vector 的实例是我定义它的类型:
x = component(float)
所以我尝试了这个(一个组件(描述符)class):
class component(object):
def __init__(self, t, initval=None):
self.val = initval
self.type = t
def __get__(self, obj, objtype):
return self.val
def __set__(self, obj, val):
self.val = val
像描述符一样使用组件,但我无法设法解决该类型。我试图做一个数组来保存 val 和 type,但不知道如何从装饰器 中获取它__setattr__ 方法。
你能给我指出正确的方向吗?
PS:希望你们明白我想做什么,并帮助我。提前致谢
解决方案
好吧,使用@Diego 的回答(我将接受)和一些解决方法来满足我的个人需求,我设法做到了这一点:
装饰器(组件化)
def componentized(cls):
class wrappedClass(object):
def __init__(self, *args):
self.wrappedInstance = cls(*args)
def __getattr__(self, name):
#Checking if we only request for a single char named value
#and return the value using getattr() for the wrappedInstance instance
#If not, then we return a tuple getting every wrappedInstance attribute
if(len(name) == 1):
return getattr(self.wrappedInstance, name)
else:
return tuple(getattr(self.wrappedInstance, char) for char in name)
def __setattr__(self, attributeName, value):
try:
#We check if there is not an instance created on the wrappedClass __dict__
#Meaning we are initializing the class
if len(self.__dict__) == 0:
self.__dict__[attributeName] = value
elif isinstance(value, tuple): # We get a Tuple assign
self.__checkMultipleAssign(attributeName)
for char, val in zip(attributeName, value):
setattr(self.wrappedInstance, char, val)
else:
#We get a value assign to every component
self.__checkMultipleAssign(attributeName)
for char in attributeName:
setattr(self.wrappedInstance, char, value)
except Exception as e:
print(e)
def __checkMultipleAssign(self, attributeName):
#With this we avoid assigning multiple values to the same property like this
# instance.xx = (2,3) => Exception
for i in range(0,len(attributeName)):
for j in range(i+1,len(attributeName)):
if attributeName[i] == attributeName[j]:
raise Exception("Multiple component assignment not allowed")
return wrappedClass
组件(描述符class)
class component(object):
def __init__(self, t):
self.type = t #We store the type
self.value = None #We set an initial value to None
def __get__(self, obj, objtype):
return self.value #Return the value
def __set__(self, obj, value):
try:
#We check whether the type of the component is diferent to the assigned value type and raise an exeption
if self.type != type(value):
raise Exception("Type \"{}\" do not match \"{}\".\n\t--Assignation never happened".format(type(value), self.type))
except Exception as e:
print(e)
else:
#If the type match we set the value
self.value = value
(代码注释不言自明)
通过这个设计我可以实现我想要的(上面解释过)
谢谢大家的帮助。
FWIW,我通过滥用 __slots__
做了类似的事情。我创建了一个 Abstract Base Class,它读取 subclass 的插槽,然后将其用于酸洗(使用 __getstate__
和 __setstate__
)。您可以使用 get-set-attr 做一些类似的事情,但您仍然需要处理 class 的实际属性与您想要用作 get/set 属性的属性。
上一个回答:
为什么不直接使用 @property
decorator?请参阅文档中的第三个示例。您可以通过首先将 attr 名称更改为不同的名称和 private(如 _x
)来应用它,然后使用实际名称 x
作为 属性.
class Vector(object):
def __init__(self, x, y):
self._x = x
self._y = y
@property
def x(self):
return self._x
@x.setter
def x(self, value):
self._x = value
@property
def xy(self):
return (self._x, self._y) # returns a tuple
@xy.setter
def xy(self, value):
self._x, self._y = value # splits out `value` to _x and _y
如果您希望每个属性都自动发生这种情况,那么您将需要使用元class,正如@kasramvd 评论的那样。如果您没有很多这样的不同 class 想要执行此操作的地方或许多属性,可能不值得付出努力。
我认为有一种最简单的方法可以实现该行为:重载 __getattr__
和 __setattr__
函数。
获得vector.xy:
class Vector:
...
def __getattr__(self, item):
return tuple(object.__getattribute__(self, char) for char in item)
仅当 "normal" 种访问属性的方法失败时才调用 __getattr__
函数,如 Python documentation 中所述。
因此,当 python 找不到 vector.xy
时,会调用 __getattr__
方法,我们 return 每个值(即 x 和 y)的元组。
我们使用 object.__getattribute__
来避免无限递归。
设置vector.abc:
def __setattr__(self, key, value):
if isinstance(value, tuple) and len(key) == len(value):
for char, val in zip(key, value):
object.__setattr__(self, char, val)
else:
object.__setattr__(self, key, value)
与__getattr__
不同,__setattr__
方法总是被调用,所以只有当我们要设置的项目与值的元组长度相同时,我们才分别设置每个值。
>>> vector = Vector(4, 2)
>>> vector.x
4
>>> vector.xy
(4, 2)
>>> vector.xyz = 1, 2, 3
>>> vector.xyxyxyzzz
(1, 2, 1, 2, 1, 2, 3, 3, 3)
唯一的缺点是,如果你真的想像这样分配一个元组(假设你有一个名为 size
的属性):
vector.size = (1, 2, 3, 4)
那么s,i,z,e将分别赋值,这显然不是你想要的!
我正在努力掌握(开始 ;))以了解如何在 Python 上正确使用装饰器和描述符 3. 我想出了一个想法,我想弄清楚如何对其进行编码.
我希望能够创建一个 class A 装饰某些 "function" B 或 "class" B 允许我在删除 A 上的属性后创建 A 的实例成为某种类型的组件并在 A __init__
魔法函数上赋值。例如:
componentized 确定 "function B" 或 "class B" 允许我声明一个 class Vector。我声明 x 和 y 是一个 component(float) 像这样:
@componentized
class Vector:
x = component(float)
y = component(float)
def __init__ (self, x, y):
self.x = x
self.y = y
我的想法是能够做到这一点:
v = Vector(1,2)
v.x #returns 1
但主要目标是我想为每个标记的 component(float) 属性:
执行此操作v.xy #returns a tuple (1,2)
v.xy = (3,4) #assigns to x the value 3 and y the value 4
我的想法是创建一个装饰器 @componentized 来覆盖 __getattr__
和 __setattr__
魔法方法。排序如下:
def componentized(cls):
class Wrapper(object):
def __init__(self, *args):
self.wrapped = cls(*args)
def __getattr__(self, name):
print("Getting :", name)
if(len(name) == 1):
return getattr(self.wrapped, name)
t = []
for x in name:
t.append(getattr(self.wrapped, x))
return tuple(t)
@componentized
class Vector(object):
def __init__(self, x, y):
self.x = x
self.y = y
它有点奏效,但我不认为我完全理解发生了什么。因为当我尝试分配并覆盖 __setattr__
魔术方法时,即使在我实例化 class 时它也会被调用。在下面的例子中两次:
vector = Vector(1,2)
vector.x = 1
我怎样才能实现那种行为?
提前致谢!如果需要更多信息,请随时询问!
编辑:
根据@Diego 的回答,我设法做到了:
def componentized(cls):
class wrappedClass(object):
def __init__(self, *args, **kwargs):
t = cls(*args,**kwargs)
self.wrappedInstance = t
def __getattr__(self, item):
if(len(item) == 1):
return self.wrappedInstance.__getattribute__(item)
else:
return tuple(self.wrappedInstance.__getattribute__(char) for char in item)
def __setattr__(self, attributeName, value):
if isinstance(value, tuple):
for char, val in zip(attributeName, value):
self.wrappedInstance.__setattr__(char, val)
elif isinstance(value, int): #EMPHASIS HERE
for char in attributeName:
self.wrappedInstance.__setattr__(char, value)
else:
object.__setattr__(self, attributeName, value)
return wrappedClass
并且有一个 class Vector 这样的:
@componentized
class Vector:
def __init__ (self, x, y):
self.x = x
self.y = y
有点像我想要的,但我仍然不知道如何实现:
x = component(float)
y = component(float)
在 Vector class 中以某种方式订阅 x 和 y键入 float,所以当我在代码上执行 #EMPHASIS LINE(在我对特定类型进行硬编码的行中)时,我可以检查是否有人正在为 [=77= 赋值]x and/or y 对于 Vector 的实例是我定义它的类型:
x = component(float)
所以我尝试了这个(一个组件(描述符)class):
class component(object):
def __init__(self, t, initval=None):
self.val = initval
self.type = t
def __get__(self, obj, objtype):
return self.val
def __set__(self, obj, val):
self.val = val
像描述符一样使用组件,但我无法设法解决该类型。我试图做一个数组来保存 val 和 type,但不知道如何从装饰器 中获取它__setattr__ 方法。
你能给我指出正确的方向吗?
PS:希望你们明白我想做什么,并帮助我。提前致谢
解决方案
好吧,使用@Diego 的回答(我将接受)和一些解决方法来满足我的个人需求,我设法做到了这一点:
装饰器(组件化)
def componentized(cls):
class wrappedClass(object):
def __init__(self, *args):
self.wrappedInstance = cls(*args)
def __getattr__(self, name):
#Checking if we only request for a single char named value
#and return the value using getattr() for the wrappedInstance instance
#If not, then we return a tuple getting every wrappedInstance attribute
if(len(name) == 1):
return getattr(self.wrappedInstance, name)
else:
return tuple(getattr(self.wrappedInstance, char) for char in name)
def __setattr__(self, attributeName, value):
try:
#We check if there is not an instance created on the wrappedClass __dict__
#Meaning we are initializing the class
if len(self.__dict__) == 0:
self.__dict__[attributeName] = value
elif isinstance(value, tuple): # We get a Tuple assign
self.__checkMultipleAssign(attributeName)
for char, val in zip(attributeName, value):
setattr(self.wrappedInstance, char, val)
else:
#We get a value assign to every component
self.__checkMultipleAssign(attributeName)
for char in attributeName:
setattr(self.wrappedInstance, char, value)
except Exception as e:
print(e)
def __checkMultipleAssign(self, attributeName):
#With this we avoid assigning multiple values to the same property like this
# instance.xx = (2,3) => Exception
for i in range(0,len(attributeName)):
for j in range(i+1,len(attributeName)):
if attributeName[i] == attributeName[j]:
raise Exception("Multiple component assignment not allowed")
return wrappedClass
组件(描述符class)
class component(object):
def __init__(self, t):
self.type = t #We store the type
self.value = None #We set an initial value to None
def __get__(self, obj, objtype):
return self.value #Return the value
def __set__(self, obj, value):
try:
#We check whether the type of the component is diferent to the assigned value type and raise an exeption
if self.type != type(value):
raise Exception("Type \"{}\" do not match \"{}\".\n\t--Assignation never happened".format(type(value), self.type))
except Exception as e:
print(e)
else:
#If the type match we set the value
self.value = value
(代码注释不言自明)
通过这个设计我可以实现我想要的(上面解释过) 谢谢大家的帮助。
FWIW,我通过滥用 __slots__
做了类似的事情。我创建了一个 Abstract Base Class,它读取 subclass 的插槽,然后将其用于酸洗(使用 __getstate__
和 __setstate__
)。您可以使用 get-set-attr 做一些类似的事情,但您仍然需要处理 class 的实际属性与您想要用作 get/set 属性的属性。
上一个回答:
为什么不直接使用 @property
decorator?请参阅文档中的第三个示例。您可以通过首先将 attr 名称更改为不同的名称和 private(如 _x
)来应用它,然后使用实际名称 x
作为 属性.
class Vector(object):
def __init__(self, x, y):
self._x = x
self._y = y
@property
def x(self):
return self._x
@x.setter
def x(self, value):
self._x = value
@property
def xy(self):
return (self._x, self._y) # returns a tuple
@xy.setter
def xy(self, value):
self._x, self._y = value # splits out `value` to _x and _y
如果您希望每个属性都自动发生这种情况,那么您将需要使用元class,正如@kasramvd 评论的那样。如果您没有很多这样的不同 class 想要执行此操作的地方或许多属性,可能不值得付出努力。
我认为有一种最简单的方法可以实现该行为:重载 __getattr__
和 __setattr__
函数。
获得vector.xy:
class Vector:
...
def __getattr__(self, item):
return tuple(object.__getattribute__(self, char) for char in item)
仅当 "normal" 种访问属性的方法失败时才调用 __getattr__
函数,如 Python documentation 中所述。
因此,当 python 找不到 vector.xy
时,会调用 __getattr__
方法,我们 return 每个值(即 x 和 y)的元组。
我们使用 object.__getattribute__
来避免无限递归。
设置vector.abc:
def __setattr__(self, key, value):
if isinstance(value, tuple) and len(key) == len(value):
for char, val in zip(key, value):
object.__setattr__(self, char, val)
else:
object.__setattr__(self, key, value)
与__getattr__
不同,__setattr__
方法总是被调用,所以只有当我们要设置的项目与值的元组长度相同时,我们才分别设置每个值。
>>> vector = Vector(4, 2)
>>> vector.x
4
>>> vector.xy
(4, 2)
>>> vector.xyz = 1, 2, 3
>>> vector.xyxyxyzzz
(1, 2, 1, 2, 1, 2, 3, 3, 3)
唯一的缺点是,如果你真的想像这样分配一个元组(假设你有一个名为 size
的属性):
vector.size = (1, 2, 3, 4)
那么s,i,z,e将分别赋值,这显然不是你想要的!