简体   繁体   English

如何在python中获取属性setter方法的属性

[英]how to get the attribute of setter method of property in python

Please consider the below code 请考虑以下代码

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

This is my decorator method to decorate the setter method of property of other class, I want to set some attribute to the method. 这是我的装饰器方法来装饰其他类的属性的setter方法,我想为方法设置一些属性。 but it doesn't allow me. 但它不允许我。

I tried as below 我尝试如下

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__)

Got the below error when ran this 运行此时出现以下错误

Traceback (most recent call last):
File "datatypevalidator.py", line 41, in <module>
print(u.Name.__dbattr__)
AttributeError: 'str' object has no attribute '__dbattr__'

What am I doing wrong, and how can I set some attribute to setter method. 我做错了什么,如何为setter方法设置一些属性。

OK so there are three points of confusion here. 好的,这里有三点困惑。 Object identity, descriptor protocols and dynamic attributes. 对象标识,描述符协议和动态属性。

First off, you are assigning __dbattr__ to func . 首先,您将__dbattr__分配给func

def __call__(self , func): 
    func.__dbattr__ = self.default  # you don't need setattr
    def validate(obj , value):
        func(obj , value)
    return validate

But this is assigning the attribute to func , which is then only held as a member of validate which in turn replaces func in the class (this is what decorators do ultimately, replace one function with another). 但这是将属性func ,然后只将其作为validate的成员保存,而后者又替换了类中的func (这是装饰者最终做的,将一个函数替换为另一个函数)。 So by placing this data on func , we lose access to it (well without some serious hacky __closure__ access). 因此,通过将此数据放在func ,我们将失去对它的访问权限(没有一些严重的hacky __closure__访问权限)。 Instead, we should put the data on validate . 相反,我们应该将数据放在validate

def __call__(self , func): 
    def validate(obj , value):
        # other code
        func(obj , value)
    validate.__dbattr__ = self.default
    return validate

Now, does u.Name.__dbattr__ work? 现在, u.Name.__dbattr__工作吗? No, you still get the same error, but the data is now accessible. 不,您仍然会收到相同的错误,但现在可以访问数据了。 To find it, we need to understand python's descriptor protocol which defines how properties work. 要找到它,我们需要了解python的描述符协议 ,它定义了属性的工作方式。

Read the linked article for a full explination but effectively, @property works by making an additional class with __get__ , __set__ and __del__ methods which when you call inst.property what you actually do is call inst.__class__.property.__get__(inst, inst.__class__) (and similar for inst.property = value --> __set__ and del inst.property --> __del__ (). Each of these in turn call the fget , fset and fdel methods which are references to the methods you defined in the class. 阅读文章链接为一个完整的explination但有效, @property作品通过与附加类__get____set____del__方法,当你调用inst.property你真正要做的是呼吁inst.__class__.property.__get__(inst, inst.__class__) inst.property = value --> __set__ inst.__class__.property.__get__(inst, inst.__class__) (类似于inst.property = value --> __set__del inst.property --> __del__ ()。其中​​每一个依次调用fgetfsetfdel方法,这些方法是对你定义的方法的引用。班级。

So we can find your __dbattr__ not on u.Name (which is the result of the User.Name.__get__(u, User) but on the User.Name.fset method itself! If you think about it (hard), this makes sense. This is the method you put it on. You didn't put it on the value of the result! 所以我们可以找到你的__dbattr__不是u.Name (这是User.Name.__get__(u, User)但是在User.Name.fset方法本身!如果你考虑它(硬),这使得感觉。这是你把它放在上面的方法 。你没有把它放在结果的价值上!

User.Name.fset.__dbattr__
Out[223]: {'length': 100, 'required': False, 'type': 'string'}

Right, so we can see this data exists, but it's not on the object we want. 是的,所以我们可以看到这些数据存在,但它不在我们想要的对象上。 How do we get it onto that object? 我们如何将它放到那个对象上? Well, it's actually quite simple. 嗯,实际上非常简单。

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

This only works if ultimately the setter returns the value, but in your case it does. 这只有在最终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'}

You're probably wondering why I used a custom string class. 您可能想知道为什么我使用自定义字符串类。 Well if you use a basic string, you'll see the issue 好吧,如果您使用基本字符串,您将看到问题

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 objects, like most in-built types in python, do not allow random attribute assignment like custom python classes ( collections.UserString is a python class wrapper around string that does allow random assignment). 与python中的大多数内置类型一样, str对象不允许随机属性赋值,如自定义python类( collections.UserString是一个python类包装器,它允许随机分配字符串)。

So ultimately, if what you originally wanted was ultimately impossible. 所以最终,如果你最初想要的东西最终是不可能的。 However using a custom string class makes it so. 但是,使用自定义字符串类就可以了。

access __dbattr__ is a bit tricky: 访问__dbattr__有点棘手:

first, you need get the property object: 首先,您需要获取属性对象:

p = u.__class__.__dict__['Name']

then get back the setter function object, named validate , which is defined inside DataMember.__call__ : 然后返回名为validate的setter函数对象,该对象在DataMember.__call__定义:

setter_func = p.fset

then get back the underlying User.Name(self , value) function object from the closure of setter_func : 然后从setter_func的闭包中获取底层的User.Name(self , value)函数对象:

ori_func = setter_func.__closure__[0].cell_contents

now you could access __dbattr__ : 现在你可以访问__dbattr__

>>> ori_func.__dbattr__
{'required': False, 'type': 'string', 'length': 100}

but is that useful? 但这有用吗? I don't know. 我不知道。 maybe you could just set __dbattr__ on the validate function object that returned by DataMember.__call__ , as other answers have pointed out. 也许你可以在DataMember.__call__返回的validate函数对象上设置__dbattr__ ,正如其他答案所指出的那样。

You need to set the attribute on the wrapper function that is being returned by your decorator class's call method: 您需要在decorator类的调用方法返回的包装函数上设置该属性:

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

But note, it is not a method, since there is a property on the class, it instances will only ever return a string, the one returned by the getter. 但是请注意,它不是一个方法,因为类上有一个属性,它实例只会返回一个字符串,即getter返回的字符串。 To access the setter, you have to do it indirectly through the property, which is on the class: 要访问setter,您必须通过属性间接地执行它,该属性在类上:

u = User()
u.Name = "usernameofapp"
print(u.Name)
print(User.Name.fset.__dbattr__)

which prints: 打印:

usernameofapp
{'required': False, 'type': 'string', 'length': 100}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM