Frage wie man das Attribut der Setter-Methode der Eigenschaft in Python bekommt


Bitte beachten Sie den folgenden 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

Dies ist meine Decorator-Methode, um die Setter-Methode der Eigenschaft anderer Klassen zu dekorieren. Ich möchte ein Attribut für die Methode setzen. aber es erlaubt mir nicht.

Ich habe es wie folgt versucht

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

Habe den folgenden Fehler erhalten, als ich das ausgeführt habe

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

Was mache ich falsch, und wie kann ich ein Attribut auf Setter-Methode festlegen.


6
2017-08-26 10:07


Ursprung


Antworten:


OK, also gibt es drei Punkte der Verwirrung hier. Objektidentität, Deskriptorprotokolle und dynamische Attribute.

Als erstes weisen Sie zu __dbattr__ zu func.

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

Aber das weist dem Attribut zu func, die dann nur als Mitglied von validate was wiederum ersetzt func in der Klasse (das ist, was Dekoratoren letztlich tun, ersetzen eine Funktion durch eine andere). Also, indem Sie diese Daten platzieren func, wir verlieren den Zugang dazu (auch ohne ernsthaften Hacky __closure__ Zugriff). Stattdessen sollten wir die Daten bereitstellen validate.

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

Nun, tut es u.Name.__dbattr__ Arbeit? Nein, Sie erhalten immer noch den gleichen Fehler, aber die Daten sind jetzt zugänglich. Um es zu finden, müssen wir Pythons verstehen Deskriptorprotokoll was definiert, wie Eigenschaften funktionieren.

Lesen Sie den verlinkten Artikel für eine vollständige Erkundung, aber effektiv, @property arbeitet mit einer zusätzlichen Klasse mit __get__, __set__ und __del__ Methoden, die, wenn Sie anrufen inst.property was Sie tatsächlich tun, ist anzurufen inst.__class__.property.__get__(inst, inst.__class__) (und ähnlich für inst.property = value --> __set__ und del inst.property --> __del__(). Jeder von diesen ruft seinerseits an fget, fset und fdel Methoden, die Verweise auf die Methoden sind, die Sie in der Klasse definiert haben.

So können wir Ihre finden __dbattr__ nicht auf u.Name (was das Ergebnis der User.Name.__get__(u, User) aber auf der User.Name.fset Methode selbst! Wenn Sie darüber nachdenken (schwer), macht das Sinn. Dies ist das Methode du ziehst es an. Sie haben es nicht auf den Wert des Ergebnisses gesetzt!

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

Richtig, also können wir sehen, dass diese Daten existieren, aber es ist nicht auf dem Objekt, das wir wollen. Wie bekommen wir es auf dieses Objekt? Nun, es ist eigentlich ganz einfach.

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

Dies funktioniert nur, wenn der Setter letztendlich den Wert zurückgibt, in Ihrem Fall jedoch.

# 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'}

Sie wundern sich wahrscheinlich, warum ich eine benutzerdefinierte Zeichenklasse verwendet habe. Wenn Sie eine einfache Zeichenfolge verwenden, wird das Problem angezeigt

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 Objekte, wie die meisten eingebauten Typen in Python, erlauben keine zufällige Attributierung wie benutzerdefinierte Python-Klassen (collections.UserString ist ein Python-Wrapper um String, der zufällige Zuweisung erlaubt).

Also letztendlich, wenn das, was Sie ursprünglich wollten, letztendlich unmöglich war. Die Verwendung einer benutzerdefinierten Zeichenfolgenklasse macht dies jedoch möglich.


7
2017-08-28 11:47



Sie müssen das Attribut auf dem festlegen Verpackung Funktion, die von der Aufrufmethode Ihrer Decorator-Klasse zurückgegeben wird:

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

Beachten Sie jedoch, dass es sich hierbei nicht um eine Methode handelt. Da es eine Eigenschaft für die Klasse gibt, geben Instanzen nur eine Zeichenfolge zurück, die vom Getter zurückgegeben wird. Um auf den Setter zuzugreifen, müssen Sie dies indirekt über die Eigenschaft tun, die sich auf der Klasse befindet:

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

welches druckt:

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

4
2017-08-28 11:49



Zugriff __dbattr__ ist ein bisschen schwierig:

Zuerst müssen Sie das Property-Objekt abrufen:

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

dann zurück das Setter-Funktion-Objekt, benannt validate, die im Inneren definiert ist DataMember.__call__:

setter_func = p.fset

dann hol dir den Basiswert zurück User.Name(self , value) Funktionsobjekt aus der Schließung von setter_func:

ori_func = setter_func.__closure__[0].cell_contents

Jetzt könnten Sie zugreifen __dbattr__:

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

aber ist das nützlich? Ich weiß es nicht. vielleicht könntest du dich einfach festlegen __dbattr__ auf der validate Funktionsobjekt, das von zurückgegeben wurde DataMember.__call__, wie andere Antworten darauf hingewiesen haben.


4
2017-08-28 11:47