2009-10-27 10 views
6

Ich habe begonnen, das Python-Deskriptor-Protokoll ausführlicher in dem Code zu verwenden, den ich geschrieben habe. Normalerweise ist die Standard-Python-Lookup-Magie das, was ich möchte, aber manchmal finde ich, dass ich das Deskriptor-Objekt selbst anstelle der Ergebnisse seiner -Methode bekommen möchte. Der Typ des Deskriptors oder der Zugriffszustand, der im Deskriptor gespeichert ist, oder dergleichen.Python-Attribut-Lookup ohne jegliche Beschreibungszauber?

Ich schrieb den Code unten, um die Namespaces in was ich glaube, ist die richtige Reihenfolge zu gehen, und das Attribut Raw zurückgegeben, unabhängig davon, ob es ein Deskriptor ist oder nicht. Ich bin jedoch überrascht, dass ich dafür keine eingebaute Funktion oder etwas in der Standardbibliothek finden kann - ich denke, es muss da sein und ich habe es einfach nicht bemerkt oder für den richtigen Suchbegriff gegoogelt.

Gibt es irgendwo in der Python-Distribution Funktionalität, die das (oder etwas Ähnliches) schon macht?

Danke!

from inspect import isdatadescriptor 

def namespaces(obj): 
    obj_dict = None 
    if hasattr(obj, '__dict__'): 
     obj_dict = object.__getattribute__(obj, '__dict__') 

    obj_class = type(obj) 
    return obj_dict, [t.__dict__ for t in obj_class.__mro__] 

def getattr_raw(obj, name): 
    # get an attribute in the same resolution order one would normally, 
    # but do not call __get__ on the attribute even if it has one 
    obj_dict, class_dicts = namespaces(obj) 

    # look for a data descriptor in class hierarchy; it takes priority over 
    # the obj's dict if it exists 
    for d in class_dicts: 
     if name in d and isdatadescriptor(d[name]): 
      return d[name] 

    # look for the attribute in the object's dictionary 
    if obj_dict and name in obj_dict: 
     return obj_dict[name] 

    # look for the attribute anywhere in the class hierarchy 
    for d in class_dicts: 
     if name in d: 
      return d[name] 

    raise AttributeError 

bearbeiten Mi, 28. Oktober 2009.

Denis Antwort gab mir eine Konvention in meiner Descriptor Klassen verwenden die Descriptor-Objekte selbst zu bekommen. Aber ich hatte eine ganze Klassenhierarchie von Descriptor-Klassen, und ich wollte nicht jede__get__ Funktion mit einem vorformulierten beginnen

def __get__(self, instance, instance_type): 
    if instance is None: 
     return self 
    ... 

dies zu vermeiden, habe ich die Wurzel des Baumes Descriptor-Klasse erbt von die folgenden:

def decorate_get(original_get): 
    def decorated_get(self, instance, instance_type): 
     if instance is None: 
      return self 
     return original_get(self, instance, instance_type) 
    return decorated_get 

class InstanceOnlyDescriptor(object): 
    """All __get__ functions are automatically wrapped with a decorator which 
    causes them to only be applied to instances. If __get__ is called on a 
    class, the decorator returns the descriptor itself, and the decorated 
    __get__ is not called. 
    """ 
    class __metaclass__(type): 
     def __new__(cls, name, bases, attrs): 
      if '__get__' in attrs: 
       attrs['__get__'] = decorate_get(attrs['__get__']) 
      return type.__new__(cls, name, bases, attrs) 
+0

Manchmal möchten Sie das Deskriptor-Objekt? Das verletzt die Kernerwartung für Deskriptoren: Sie sollen wie Attribute aussehen. Warum diese fundamentale Erwartung brechen? Warum das tun? Warum etwas so komplexes erstellen? –

+0

Was ich mache, fühlt sich für mich * nicht komplex an, aber ich denke, man könnte sagen, ich experimentiere mit dem Design. In meinem aktuellen, speziellen Fall habe ich einen Deskriptor, der die Stärke einer Waffe in einem Spiel zurückgibt. Dieser Wert ist eine Funktion des Zustands des Deskriptors (Stärke der Waffe) und der Instanz (Gesundheit des Schiffes). Es gibt verschiedene Arten von Waffen; Normalerweise möchte ich nur den Wert, aber in einigen Fällen muss ich wissen, welche Art von Waffe es ist - die Art des Deskriptors. Und was, wenn ein Deskriptor Methoden hat, die nicht Teil des Deskriptor-Protokolls sind, und Sie sie aufrufen wollen? –

Antwort

11

Die meisten Deskriptoren erledigen ihre Arbeit, wenn nur als Instanzattribut zugegriffen wird. So ist es praktisch, sich zurück, wenn es für die Klasse zugegriffen wird:

class FixedValueProperty(object): 
    def __init__(self, value): 
     self.value = value 
    def __get__(self, inst, cls): 
     if inst is None: 
      return self 
     return self.value 

Auf diese Weise können Sie Descriptor bekommen selbst:

>>> class C(object): 
...  prop = FixedValueProperty('abc') 
... 
>>> o = C() 
>>> o.prop 
'abc' 
>>> C.prop 
<__main__.FixedValueProperty object at 0xb7eb290c> 
>>> C.prop.value 
'abc' 
>>> type(o).prop.value 
'abc' 

Beachten Sie, dass dies funktioniert für (die meisten?) Einbau-Deskriptoren auch:

>>> class C(object): 
...  @property 
...  def prop(self): 
...   return 'abc' 
... 
>>> C.prop 
<property object at 0xb7eb0b6c> 
>>> C.prop.fget 
<function prop at 0xb7ea36f4> 

Zugriff Descriptor könnte nützlich sein, wenn Sie soweit benötigen sie in der Unterklasse, aber es gibt eine better way dies zu tun.

+0

Das war mir nicht klar; gut zu wissen. Funktionen selbst wären eine Ausnahme, eine Ausnahme zu diesem Muster, aber vielleicht auch die einzige. Ich muss auf die eingebauten Deskriptoren stoßen. –

+0

Obwohl es meine Frage nicht genau beantwortet hat, hat Ihre Antwort mir geholfen, mein zugrunde liegendes Problem zu lösen. Ich werde es akzeptieren. –

+0

sind Funktionen eine Ausnahme zu diesem Muster (ich nehme an, Sie sprechen über Methoden)? Nein, "c.method" gibt eine gebundene Methode aus einer Beschreibung zurück, während "C.method" eine ungebundene Methode zurückgibt. Es ist das gleiche Muster. – u0b34a0f6ae

0

Das obige Verfahren

class FixedValueProperty(object): 
    def __init__(self, value): 
     self.value = value 
    def __get__(self, inst, cls): 
     if inst is None: 
      return self 
     return self.value 

ist eine großartige Methode, wenn Sie den Code der Immobilie kontrollieren, aber es gibt einige Fälle, wie zum Beispiel, wenn die Eigenschaft Teil einer Bibliothek ist von jemand anderem kontrolliert, wo ein anderer Ansatz ist nützlich. Dieser alternative Ansatz kann auch in anderen Situationen nützlich sein, z. B. beim Implementieren der Objektzuordnung, beim Gehen eines Namensraums, wie in der Frage beschrieben, oder bei anderen spezialisierten Bibliotheken.

Betrachten wir eine Klasse mit einer einfachen Eigenschaft:

class ClassWithProp: 

    @property 
    def value(self): 
     return 3 
>>>test=ClassWithProp() 
>>>test.value 
3 
>>>test.__class__.__dict__.['value'] 
<property object at 0x00000216A39D0778> 

Wenn der Container-Objekte zugegriffen Klasse dict, die ‚Deskriptor Magie‘ umgangen wird.Beachten Sie auch, dass wenn wir die Eigenschaft einer neuen Klassenvariablen zuweisen, verhält sie sich genau wie das Original mit 'descriptor magic', aber wenn sie einer Instanzvariablen zugewiesen ist, verhält sich die Eigenschaft wie jedes normale Objekt und umgeht auch 'descriptor magic'.

>>> test.__class__.classvar = test.__class__.__dict__['value'] 
>>> test.classvar 
3 
>>> test.instvar = test.__class__.__dict__['value'] 
>>> test.instvar 
<property object at 0x00000216A39D0778> 
0

Nehmen wir an, wir wollen den Deskriptor für obj.prop wo type(obj) is C bekommen.

C.prop funktioniert in der Regel, weil der Deskriptor normalerweise selbst zurückkehrt, wenn über C zugegriffen wird (d. H. An C gebunden). Aber C.prop kann einen Deskriptor in seiner Metaklasse auslösen. Wenn prop in obj nicht vorhanden wäre, würde obj.propAttributeError erhöhen, während C.prop möglicherweise nicht. Also ist es besser, inspect.getattr_static(obj, 'prop') zu verwenden.

Wenn Sie damit nicht zufrieden sind, ist hier eine CPython-spezifische Methode (von _PyObject_GenericGetAttrWithDict in Objects/object.c):

import ctypes, _ctypes 

_PyType_Lookup = ctypes.pythonapi._PyType_Lookup 
_PyType_Lookup.argtypes = (ctypes.py_object, ctypes.py_object) 
_PyType_Lookup.restype = ctypes.c_void_p 

def type_lookup(ty, name): 
    """look for a name through the MRO of a type.""" 
    if not isinstance(ty, type): 
     raise TypeError('ty must be a type') 

    result = _PyType_Lookup(ty, name) 
    if result is None: 
     raise AttributeError(name) 

    return _ctypes.PyObj_FromPtr(result) 

type_lookup(type(obj), 'prop') gibt den Deskriptor in der gleichen Art und Weise, wenn CPython es bei obj.prop verwendet, wenn obj a übliches Objekt (z. B. keine Klasse).

Verwandte Themen