2016-03-16 14 views
20

Ab Python 3.4 gibt es einen Deskriptor namens DynamicClassAttribute. In der Dokumentation:Was ist ein DynamicClassAttribute und wie verwende ich es?

types.DynamicClassAttribute(fget=None, fset=None, fdel=None, doc=None)

Strecke Zugriff auf eine Klasse __getattr__ zuzuschreiben.

Dies ist ein Deskriptor, der verwendet wird, um Attribute zu definieren, die beim Zugriff über eine Instanz und über eine Klasse unterschiedlich agieren. Der Instanzenzugriff bleibt normal, aber der Zugriff auf ein Attribut über eine Klasse wird an die Methode __getattr__ der Klasse weitergeleitet. Dies geschieht durch Erhöhung AttributeError.

Dadurch können Eigenschaften für eine Instanz aktiviert sein und virtuelle Attribute für die Klasse mit demselben Namen haben (siehe Enum für ein Beispiel).

Neu in Version 3.4.

Es ist offenbar in den enum module verwendet:

# DynamicClassAttribute is used to provide access to the `name` and 
# `value` properties of enum members while keeping some measure of 
# protection from modification, while still allowing for an enumeration 
# to have members named `name` and `value`. This works because enumeration 
# members are not set directly on the enum class -- __getattr__ is 
# used to look them up. 

@DynamicClassAttribute 
def name(self): 
    """The name of the Enum member.""" 
    return self._name_ 

@DynamicClassAttribute 
def value(self): 
    """The value of the Enum member.""" 
    return self._value_ 

Ich weiß, dass enums are a little special, aber ich verstehe nicht, wie dies die DynamicClassAttribute bezieht. Was bedeutet es, dass diese Attribute dynamische sind, wie unterscheidet sich das von einer normalen Eigenschaft, und wie verwende ich eine DynamicClassAttribute zu meinem Vorteil?

Antwort

12

Neue Version:

ich mit der vorherige Antwort ein wenig enttäuscht war, damit ich es ein wenig neu zu schreiben entschieden:

Zuerst hat einen Blick auf the source code of DynamicClassAttribute und Sie werden wahrscheinlich feststellen, dass es sehr aussieht ähnlich wie der normale property. Mit Ausnahme der __get__ -Methode:

def __get__(self, instance, ownerclass=None): 
    if instance is None: 
     # Here is the difference, the normal property just does: return self 
     if self.__isabstractmethod__: 
      return self 
     raise AttributeError() 
    elif self.fget is None: 
     raise AttributeError("unreadable attribute") 
    return self.fget(instance) 

Also, was das bedeutet, ist, dass wenn Sie ein DynamicClassAttribute zugreifen möchten (also nicht abstrakt ist) auf die Klasse wirft er einen AttributeError statt self zurückzukehren. Für Instanzen if instance: wertet True aus und die ist identisch mit property.__get__.

Für normale Klassen, die in ein löst nur sichtbarAttributeError beim Aufruf des Attribut:

from types import DynamicClassAttribute 
class Fun(): 
    @DynamicClassAttribute 
    def has_fun(self): 
     return False 
Fun.has_fun 

Attribute - Traceback (jüngste Aufforderung zuletzt) ​​

, dass nicht für sich selbst ist sehr hilfreich, bis Sie sich die Prozedur "Class attribute lookup" anschauen wenn mit metaclass es (I fand ein schönes Bild davon in this blog). Weil für den Fall, dass ein Attribut eine löst und diese Klasse hat eine Metaklasse Python sieht die metaclass.__getattr__ Methode und sieht, ob das das Attribut auflösen kann.Um dies mit einem minimalen Beispiel zu illustrieren:

Und hier kommt der "dynamische" Teil. Wenn Sie die dynprop auf der Klasse aufrufen wird es in der Meta-Suche und zurück die Meta dynprop:

Fun.dynprop 

die druckt:

search in meta 
'Meta' 

So aufgerufen wir die metaclass.__getattr__ und kehrte das ursprüngliche Attribut (das war definiert mit dem gleichen Namen wie die neue Eigenschaft).

Während Instanzen für die dynprop des Fun -instance zurückgegeben:

Fun('Not-Meta').dynprop 

wir das überschriebene Attribut erhalten:

'Not-Meta' 

aus Meine Schlussfolgerung ist, dass DynamicClassAttribute wichtig ist, wenn Sie wollen Damit Unterklassen ein Attribut mit dem gleichen Namen erhalten, das in der Metaklasse verwendet wird. Sie werden es bei Instanzen spiegeln, aber es ist immer noch zugänglich, wenn Sie es für die Klasse aufrufen.

Ich ging in das Verhalten von Enum in der alten Version, so ließ ich es hier in:

alten Version

Die DynamicClassAttribute ist nur nützlich (ich bin nicht wirklich sicher, in diesem Punkt), wenn Sie vermuten, dass Namenskonflikte zwischen einem für eine Unterklasse festgelegten Attribut und einer Eigenschaft für die Basisklasse auftreten können.

Sie werden zumindest einige Grundlagen über metaclasses müssen wissen, denn dies ist nicht ohne metaclasses (eine schöne Erklärung, wie Klassenattribute genannt werden, können in this blog post gefunden werden) arbeiten, da das Attribut Lookup ist leicht verschiedene mit Metaklassen.

Angenommen, Sie haben:

class Funny(type): 
    dynprop = 'Very important meta attribute, do not override' 

class Fun(metaclass=Funny): 
    def __init__(self, value): 
     self._stub = value 

    @property 
    def dynprop(self): 
     return 'Haha, overridden it with {}'.format(self._stub) 

und dann rufen:

Fun.dynprop 

Immobilie 0x1b3d9fd19a8

und auf der Instanz erhalten wir:

Fun(2).dynprop 

'Haha, es mit 2 außer Kraft gesetzt'

schlecht ... es verloren gegangen ist. Aber warten wir können die metaclass spezielle Lookup verwenden: Lassen Sie uns ein __getattr__ implementieren (Fallback) und implementieren Sie die dynprop als DynamicClassAttribute.Denn nach ihm documentation ist das ist ihr Zweck - zum __getattr__ zu Rückfall, wenn es auf die Klasse genannt:

from types import DynamicClassAttribute 

class Funny(type): 
    def __getattr__(self, value): 
     print('search in meta') 
     return Funny.dynprop 

    dynprop = 'Meta' 

class Fun(metaclass=Funny): 
    def __init__(self, value): 
     self._dynprop = value 

    @DynamicClassAttribute 
    def dynprop(self): 
     return self._dynprop 

jetzt greifen wir auf die Klasse-Attribut:

Fun.dynprop 

die druckt:

search in meta 
'Meta' 

So haben wir die metaclass.__getattr__ aufgerufen und das ursprüngliche Attribut (das mit dem gleichen Namen wie die neue Eigenschaft definiert wurde) zurückgegeben.

Und für Instanzen:

Fun('Not-Meta').dynprop 

erhalten wir das überschriebene Attribut:

'Not-Meta' 

Nun, das ist nicht so schlimm bedenkt, dass wir metaclasses zu vorher definierten aber überschriebenen Attribute verwenden, ohne eine Instanz umleiten kann. Dieses Beispiel ist das Gegenteil, der mit Enum gemacht wird, in dem Sie Attribute der Unterklasse definieren:

from enum import Enum 

class Fun(Enum): 
    name = 'me' 
    age = 28 
    hair = 'brown' 

und diese danach definiert Attribute standardmäßig zugreifen möchten.

Fun.name 
# <Fun.name: 'me'> 

aber Sie wollen auch die name Attribut Zugriff ermöglichen, die als DynamicClassAttribute definiert wurde (die zurückgibt, welcher Name die Variable tatsächlich):

Fun('me').name 
# 'name' 

weil sonst wie konnten Sie Zugriff auf den Namen 28 ?

Fun.hair.age 
# <Fun.age: 28> 
# BUT: 
Fun.hair.name 
# returns 'hair' 

Den Unterschied sehen? Warum gibt der zweite nicht <Fun.name: 'me'> zurück? Das ist wegen dieser Verwendung von DynamicClassAttribute. Sie können also die ursprüngliche Eigenschaft überschreiben, aber später wieder freigeben. Dieses Verhalten ist das Gegenteil meines Beispiels und erfordert mindestens die Verwendung von __new__ und __prepare__. Aber dafür müssen Sie wissen, wie das genau funktioniert und in vielen Blogs und Stackoverflow-Antworten erklärt wird, die es viel besser erklären können, als ich kann, also werde ich es überspringen, in so viel Tiefe zu gehen (und ich bin mir nicht sicher, ob Ich könnte es in kurzer Zeit lösen).

tatsächliche Anwendungsfälle könnten spärlich, aber bestimmten Zeitpunkt eine von einigen propably denken kann ...

Sehr schöne Diskussion über die Dokumentation von DynamicClassAttribute: "we added it because we needed it"