2012-08-01 6 views
9

tl; dr: Wie funktionieren Eigenschaftsdekoratoren mit Funktionsdefinitionen auf Klassenebene, nicht mit Definitionen auf Modulebene?Warum ist die Eigenschaft Dekorator nur für Klassen definiert?

Ich habe Property Decorators auf einige Funktionen auf Modulebene angewendet und dachte, dass sie mir ermöglichen würden, die Methoden durch bloße Attributsuche aufzurufen.

Dies war besonders verlockend, weil ich eine Reihe von Konfigurationsfunktionen wurde definiert, wie get_port, get_hostname usw., die alle mit ihrem einfacheren, terse Eigenschaft Pendants ersetzt worden sein könnte: port, hostname usw.

So config.get_port() wäre nur die viel schöner config.port

ich war überrascht, als ich die folgende Zurückverfolgungs gefunden, was beweist, dass dies kein gangbarer Weg:

TypeError: int() argument must be a string or a number, not 'property' 

Ich wusste, dass ich einen Vorläufer für eigenschaftsähnliche Funktionalität auf Modulebene gesehen hatte, da ich es für das Schreiben von Shellbefehlen mit dem eleganten aber hacky pbs library verwendet hatte.

Der interessante Hack unten finden Sie in der pbs library source code. Es ermöglicht die Fähigkeit, Eigenschaften-ähnliche Attribut-Lookups auf Modulebene durchzuführen, aber es ist schrecklich, schrecklich hackish.

# this is a thin wrapper around THIS module (we patch sys.modules[__name__]). 
# this is in the case that the user does a "from pbs import whatever" 
# in other words, they only want to import certain programs, not the whole 
# system PATH worth of commands. in this case, we just proxy the 
# import lookup to our Environment class 
class SelfWrapper(ModuleType): 
    def __init__(self, self_module): 
     # this is super ugly to have to copy attributes like this, 
     # but it seems to be the only way to make reload() behave 
     # nicely. if i make these attributes dynamic lookups in 
     # __getattr__, reload sometimes chokes in weird ways... 
     for attr in ["__builtins__", "__doc__", "__name__", "__package__"]: 
      setattr(self, attr, getattr(self_module, attr)) 

     self.self_module = self_module 
     self.env = Environment(globals()) 

    def __getattr__(self, name): 
     return self.env[name] 

Unten ist der Code zum Einfügen dieser Klasse in den Import-Namespace. Es patcht tatsächlich sys.modules direkt!

# we're being run as a stand-alone script, fire up a REPL 
if __name__ == "__main__": 
    globs = globals() 
    f_globals = {} 
    for k in ["__builtins__", "__doc__", "__name__", "__package__"]: 
     f_globals[k] = globs[k] 
    env = Environment(f_globals) 
    run_repl(env) 

# we're being imported from somewhere 
else: 
    self = sys.modules[__name__] 
    sys.modules[__name__] = SelfWrapper(self) 

Nun, da ich gesehen habe, was Längen pbs durchlaufen hat, frage ich mich nach links, warum diese Einrichtung von Python nicht in die Sprache direkt eingebaut ist. Insbesondere der Dekorateur property scheint ein natürlicher Ort zu sein, um eine solche Funktionalität hinzuzufügen.

Gibt es einen besonderen Grund oder eine Motivation dafür, warum dies nicht direkt eingebaut ist?

+0

Zum leichteren Finden: Es gibt jetzt ein Modul namens mprop, das dies für Sie tut https://pypi.python.org/pypi/mprop – AbdealiJK

Antwort

7

Diese auf eine Kombination von zwei Faktoren ab: erstens, dass Objekte sind implementiert mit der descriptor protocol, und zweitens Module sind immer Instanzen einer bestimmten Klasse und nicht als instanziierbare Klassen.

Dieser Teil des Deskriptor-Protokolls ist in object.__getattribute__ implementiert (der entsprechende Code ist PyObject_GenericGetAttr ab Zeile 1319).Die Lookup-Regeln gehen wie folgt aus:

  1. Suche durch die Klasse mro für eine Art Wörterbuch, das name
  2. hat Ist die erste passende Element ein Datendeskriptor ist, seine __get__ rufen und zurück ihr Ergebnis
  3. Wenn der Name im Beispiel Wörterbuch wird, kehrt der zugehörige Wert
  4. Wenn es einen passenden Eintrag aus der Klasse Wörterbücher und es war ein nicht-Daten-Descriptor, seine __get__ nennen und das Ergebnis
  5. Wenn ther zurückkehren e war aus der Klasse der Wörterbücher ein passender Artikel, 3
  6. raise AttributeError

Der Schlüssel dazu bei Nummer ist es zurückkehren - wenn name im Instanz Wörterbuch gefunden wird (wie es mit Modulen wird), dann wird sein Wert einfach zurückgegeben - es wird nicht auf Deskriptorität getestet, und sein wird nicht aufgerufen. Dies führt zu dieser Situation (mit Python 3):

>>> class F: 
... def __getattribute__(self, attr): 
...  print('hi') 
...  return object.__getattribute__(self, attr) 
... 
>>> f = F() 
>>> f.blah = property(lambda: 5) 
>>> f.blah 
hi 
<property object at 0xbfa1b0> 

Sie können sehen, dass .__getattribute__ist aufgerufen wird, aber ist nichtf.blah als Beschreiber zu behandeln.

Es ist wahrscheinlich, dass der Grund dafür, dass die Regeln auf diese Weise strukturiert sind, ein expliziter Kompromiss zwischen der Nützlichkeit des Erlaubens von Deskriptoren für Instanzen (und daher in Modulen) und der zusätzlichen Code-Komplexität ist.

+0

Das ist wirklich hilfreich, danke! – mvanveen

+1

Was unterscheidet einen Datendeskriptor von einem Nicht-Datendeskriptor? – mvanveen

+2

@mvanveen aus dem Link 'descriptor protocol' in der Antwort: "Wenn ein Objekt sowohl '__get __()' als auch '__set __()' definiert, wird es als Datendeskriptor betrachtet. Es werden Deskriptoren aufgerufen, die nur '__get __()' definieren Nicht-Datendeskriptoren ". – lvc

0

Eigenschaften sind ein spezifisches Merkmal für Klassen (speziell neue Klassen). Daher kann der Eigenschaftsdecorator nur auf Klassenmethoden angewendet werden.

Ein neuer Stil-Klasse ist eine, die von dem Objekt stammt, dh Klasse Foo (object):

Weitere Informationen: Can modules have properties the same way that objects can?

+4

Was Sie sagen, ist wahr, aber es ist wichtig, explizit zu erwähnen, dass Module keine Klassen sind . Sie sind Instanzen (vom Typ "Modul"). – BrenBarn

+0

Danke, das hilft sehr, aber ich bin immer noch verwirrt! Ich verstehe, dass ein Modul eine Instanz von 'ModuleType' ist, aber ich habe immer noch Schwierigkeiten, die Motivation dafür zu verstehen, warum sich' Eigentum' so verhält. Das Lesen von [this] (http://www.cafepy.com/article/python_types_and_objects/python_types_and_objects.html#object-type-example) erweist sich als hilfreich, aber die Tatsache, dass ich 'ModuleType' ableiten kann, macht es machbar ... Es ist die Tatsache, dass Sie 'sys.modules 'direkt patchen müssen und diese Unterklasse selbst machen, die hackish scheint. – mvanveen