如何在python中获取属性的setter方法的属性
Posted
技术标签:
【中文标题】如何在python中获取属性的setter方法的属性【英文标题】:how to get the attribute of setter method of property in python 【发布时间】:2019-02-01 03:48:12 【问题描述】:请考虑下面的代码
class DataMember():
def __init__(self, **args):
self.default = "required" : False , "type" : "string" , "length": -1
self.default.update(args)
def __call__(self , func):
#Here I want to set the attribute to method
setattr(func , "__dbattr__" , self.default)
def validate(obj , value):
#some other code
func(obj , value)
return validate
这是我装饰其他类属性的setter方法的装饰器方法,我想为该方法设置一些属性。但它不允许我。
我尝试如下
class User(DbObject):
def __init__(self):
super(User , self)
self._username = None
@property
def Name(self):
return self._username
@Name.setter
@DataMember(length=100)
def Name(self , value):
self._username = value
u = User()
u.Name = "usernameofapp"
print(u.Name)
print(u.Name.__dbattr__)
运行时出现以下错误
Traceback (most recent call last):
File "datatypevalidator.py", line 41, in <module>
print(u.Name.__dbattr__)
AttributeError: 'str' object has no attribute '__dbattr__'
我做错了什么,如何为setter方法设置一些属性。
【问题讨论】:
@LevZakharov,User类中会有更多的属性,可以有不同的DataMember装饰器参数,那么我将如何分别访问每个属性的属性,请解释一下,我的python有点低 您能否删除对DbObject
的依赖,以便实际运行该示例?
【参考方案1】:
好的,这里有三点混淆。对象标识、描述符协议和动态属性。
首先,您将__dbattr__
分配给func
。
def __call__(self , func):
func.__dbattr__ = self.default # you don't need setattr
def validate(obj , value):
func(obj , value)
return validate
但这是将属性分配给func
,然后它仅作为validate
的成员持有,而validate
又替换了类中的func
(这是装饰器最终所做的,用另一个函数替换一个函数) .因此,通过将这些数据放在 func
上,我们将无法访问它(当然没有一些严重的黑客攻击 __closure__
访问)。相反,我们应该将数据放在validate
。
def __call__(self , func):
def validate(obj , value):
# other code
func(obj , value)
validate.__dbattr__ = self.default
return validate
现在,u.Name.__dbattr__
有效吗?不,您仍然会遇到相同的错误,但现在可以访问数据。要找到它,我们需要了解 python 的descriptor protocol,它定义了属性的工作方式。
阅读链接的文章以获得完整的解释,但有效地,@property
通过使用 __get__
、__set__
和 __del__
方法创建一个附加类来工作,当您调用 inst.property
时,您实际上所做的是调用 @ 987654338@(inst.property = value --> __set__
和 del inst.property --> __del__
() 也类似。它们中的每一个依次调用 fget
、fset
和 fdel
方法,这些方法是对您在类中定义的方法的引用。
所以我们可以找到您的__dbattr__
,而不是u.Name
(这是User.Name.__get__(u, User)
的结果,而是User.Name.fset
方法本身的结果!如果您仔细考虑(很难),这是有道理的。这是方法你把它放在。你没有把它放在结果的价值上!
User.Name.fset.__dbattr__
Out[223]: 'length': 100, 'required': False, 'type': 'string'
是的,所以我们可以看到这个数据存在,但它不在我们想要的对象上。我们如何把它放到那个物体上?嗯,其实很简单。
def __call__(self , func):
def validate(obj , value):
# Set the attribute on the *value* we're going to pass to the setter
value.__dbattr__ = self.default
func(obj , value)
return validate
这仅在 setter 最终返回值时才有效,但在您的情况下它确实有效。
# Using a custom string class (will explain later)
from collections import UserString
u = User()
u.Name = UserString('hello')
u.Name # --> 'hello'
u.Name.__dbattr__ # -->'length': 100, 'required': False, 'type': 'string'
您可能想知道我为什么使用自定义字符串类。好吧,如果您使用基本字符串,您会看到问题
u.Name = 'hello'
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-238-1feeee60651f> in <module>()
----> 1 u.Name = 'hello'
<ipython-input-232-8529bc6984c8> in validate(obj, value)
6
7 def validate(obj , value):
----> 8 value.__dbattr__ = self.default
9 func(obj , value)
10 return validate
AttributeError: 'str' object has no attribute '__dbattr__'
str
对象,与 python 中的大多数内置类型一样,不允许像自定义 python 类那样进行随机属性分配(collections.UserString
是一个允许随机分配的字符串的 python 类包装器)。
因此,最终,您最初想要的使用内置字符串是不可能的,但使用自定义类可以做到。
【讨论】:
完美且解释清楚的答案。【参考方案2】:您需要在装饰器类的调用方法返回的 wrapper 函数上设置属性:
class DataMember():
def __init__(self, **args):
self.default = "required" : False , "type" : "string" , "length": -1
self.default.update(args)
def __call__(self , func):
#Here I want to set the attribute to method
def validate(obj , value):
#some other code
func(obj , value)
setattr(validate , "__dbattr__" , self.default)
return validate
class DbObject: pass
class User(DbObject):
def __init__(self):
super(User , self)
self._username = None
@property
def Name(self):
return self._username
@Name.setter
@DataMember(length=100)
def Name(self , value):
self._username = value
但请注意,它不是方法,因为类上有一个属性,它的实例只会返回一个字符串,即 getter 返回的字符串。要访问 setter,您必须通过类上的属性间接进行:
u = User()
u.Name = "usernameofapp"
print(u.Name)
print(User.Name.fset.__dbattr__)
哪个打印:
usernameofapp
'required': False, 'type': 'string', 'length': 100
【讨论】:
【参考方案3】:访问__dbattr__
有点棘手:
首先,您需要获取属性对象:
p = u.__class__.__dict__['Name']
然后取回setter函数对象,命名为validate
,在DataMember.__call__
内部定义:
setter_func = p.fset
然后从setter_func
的闭包中取回底层的User.Name(self , value)
函数对象:
ori_func = setter_func.__closure__[0].cell_contents
现在你可以访问__dbattr__
:
>>> ori_func.__dbattr__
'required': False, 'type': 'string', 'length': 100
但这有用吗?我不知道。正如其他答案所指出的那样,也许您可以在DataMember.__call__
返回的validate
函数对象上设置__dbattr__
。
【讨论】:
以上是关于如何在python中获取属性的setter方法的属性的主要内容,如果未能解决你的问题,请参考以下文章
Python使用@property装饰器--getter和setter方法变成属性