2017-02-03 1 views
6

Das Deskriptor-Protokoll funktioniert gut, aber ich habe immer noch ein Problem, das ich gerne lösen würde.Wie kann ich den Attributnamen erhalten, wenn ich in Python mit dem Deskriptorprotokoll arbeite?

Ich habe einen Descriptor:

class Field(object): 
    def __init__(self, type_, name, value=None, required=False): 
     self.type = type_ 
     self.name = "_" + name 
     self.required = required 
     self._value = value 

    def __get__(self, instance, owner): 
     return getattr(instance, self.name, self.value) 

    def __set__(self, instance, value): 
     if value: 
      self._check(value) 
      setattr(instance, self.name, value) 
     else: 
      setattr(instance, self.name, None) 

    def __delete__(self, instance): 
     raise AttributeError("Can't delete attribute") 

    @property 
    def value(self): 
     return self._value 

    @value.setter 
    def value(self, value): 
     self._value = value if value else self.type() 

    @property 
    def _types(self): 
     raise NotImplementedError 

    def _check(self, value): 
     if not isinstance(value, tuple(self._types)): 
      raise TypeError("This is bad") 

Diese subclassed ist:

class CharField(Field): 
    def __init__(self, name, value=None, min_length=0, max_length=0, strip=False): 
     super(CharField, self).__init__(unicode, name, value=value) 
     self.min_length = min_length 
     self.max_length = max_length 
     self.strip = strip 

    @property 
    def _types(self): 
     return [unicode, str] 

    def __set__(self, instance, value): 
     if self.strip: 
      value = value.strip() 

     super(CharField, self).__set__(instance, value) 

Und dann verwendet, ist eine Modellklasse:

class Country(BaseModel): 
    name = CharField("name") 
    country_code_2 = CharField("country_code_2", min_length=2, max_length=2) 
    country_code_3 = CharField("country_code_3", min_length=3, max_length=3) 

    def __init__(self, name, country_code_2, country_code_3): 
     self.name = name 
     self.country_code_2 = country_code_2 
     self.country_code_3 = country_code_3 

So weit, so gut, das funktioniert, genau wie erwartet.

Das einzige Problem, das ich hier habe, ist, dass wir einen Feldnamen jedes Mal geben müssen, wenn ein Feld deklariert wird. z.B. "country_code_2" für das Feld country_code_2.

Wie wäre es möglich, den Attributnamen der Modellklasse zu erhalten und in der Feldklasse zu verwenden?

Antwort

9

Es gibt einen einfachen Weg, und es gibt einen harten Weg.

Der einfache Weg ist Python 3.6 (oder höher) zu verwenden, und geben Sie Ihrem Descriptor eine zusätzliche object.__set_name__() method:

def __set_name__(self, owner, name): 
    self.name = '_' + name 

Wenn eine Klasse erstellt wird, Python wird automatisch diese Methode auf alle Deskriptoren rufen Sie am Set die Klasse, übergibt das Klassenobjekt und den Attributnamen.

Für frühere Python-Versionen ist die beste nächste Option die Verwendung einer metaclass; Es wird für jede erstellte Unterklasse aufgerufen und dem Attributwert ein handliches Attribut für den Wörterbuchzuordnungsnamen zugewiesen (einschließlich Ihrer Deskriptorinstanzen). Anschließend können Sie diese Gelegenheit nutzen, diesen Namen zu dem Descriptor weitergeben müssen:

class BaseModelMeta(type): 
    def __new__(mcls, name, bases, attrs): 
     cls = super(BaseModelMeta, mcls).__new__(mcls, name, bases, attrs) 
     for attr, obj in attrs.items(): 
      if isinstance(obj, Field): 
       obj.__set_name__(cls, attr) 
     return cls 

Dies erfordert die gleichen __set_name__() Verfahren auf dem Gebiet, dass Python 3.6 nativ unterstützt. Dann verwenden Sie diese als die Metaklasse für BaseModel:

class BaseModel(object, metaclass=BaseModelMeta): 
    # Python 3 

oder

class BaseModel(object): 
    __metaclass__ = BaseModelMeta 
    # Python 2 

Sie können auch eine Klasse Dekorateur verwenden, um die __set_name__ Anrufe für jede Klasse tun Sie es mit dekorieren, aber das erfordert, dass Sie dekorieren jede Klasse. Eine Metaklasse wird stattdessen automatisch durch die Vererbungshierarchie weitergegeben.

+0

Ich habe nach der Implementierung Ihrer Vorschläge ein Update hinzugefügt. Könnten Sie sich das ansehen? –

+0

Sie verwenden 'six.with_metaclass()' nicht korrekt, es sollte als Basisklasse verwendet werden. Siehe http://stackoverflow.com/questions/18513821/python-metaclass-understanding-the-with-metaclass. Sie können stattdessen damit beginnen, es nur mit Python 2 oder 3 zu arbeiten, bevor Sie 'six' verwenden :-) –

+0

Ich habe' six.with_metaclass() 'durch' __metaclass__ = BaseModelMeta' ersetzt. (Ich benutze Python 2) Dies gibt den gleichen Fehler. –

0

Ich gehe durch dieses in meinem Buch, Python Descriptors, obwohl ich nicht zu einer zweiten Ausgabe aktualisiert habe, um die neue Funktion in 3.6 hinzuzufügen. Abgesehen davon ist es ein ziemlich umfassender Leitfaden für Deskriptoren, der 60 Seiten nur für eine Funktion benötigt.

Wie auch immer, eine Möglichkeit, den Namen ohne metaclasses mit dieser sehr einfachen Funktion zu erhalten:

def name_of(descriptor, instance): 
    attributes = set() 
    for cls in type(instance).__mro__: 
     # add all attributes from the class into `attributes` 
     # you can remove the if statement in the comprehension if you don't want to filter out attributes whose names start with '__' 
     attributes |= {attr for attr in dir(cls) if not attr.startswith('__')} 
    for attr in attributes: 
     if type(instance).__dict__[attr] is descriptor: 
      return attr 

jedes Mal Betrachtet man den Namen des Descriptor verwenden, die Instanz involviert ist, sollte dies nicht sein schwer zu verstehen, wie man es benutzt.Sie könnten auch einen Weg finden, den Namen zwischenzuspeichern, nachdem Sie ihn das erste Mal nachgeschlagen haben.

+0

Das ist nicht genug; Die Deskriptoren folgen dem MRO. –

+0

Ihr Kommentar war ziemlich schwer zu entziffern; Ich nehme an, Sie wollten sagen, dass die Funktion die Mitglieder von Superklassen nicht bekommt? Ich arbeite dran; Ich werde das in ein paar Minuten erledigen. –

+0

Die Bearbeitung ist beendet –

Verwandte Themen