2009-10-27 17 views
6

Ho iniziato a utilizzare il protocollo descrittore python in modo più esteso nel codice che ho scritto. In genere, la magia di ricerca python predefinita è ciò che voglio succedere, ma a volte sto cercando di ottenere l'oggetto descrittore invece dei risultati del suo metodo __get__. Volendo conoscere il tipo di descrittore o lo stato di accesso memorizzato nel descrittore o qualcosa di simile.ricerca attributi Python senza magici descrittori?

Ho scritto il codice di seguito per percorrere gli spazi dei nomi in quello che ritengo sia l'ordine corretto e restituire l'attributo raw indipendentemente dal fatto che si tratti di un descrittore o meno. Sono sorpreso però che non riesca a trovare una funzione built-in o qualcosa nella libreria standard per farlo - immagino che debba esserci e io non l'ho notato o cercato su google per il giusto termine di ricerca.

Esiste qualche funzionalità nella distribuzione python che lo fa già (o qualcosa di simile)?

Grazie!

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 

Modifica Mer 28 Ott, 2009.

risposta di Denis mi ha dato una convenzione da utilizzare nelle mie classi descrittori a farsi gli oggetti descrittore. Ma, ho avuto tutta una gerarchia di classi di classi descrittori, e non volevo iniziare ogni funzione__get__ con un testo standard

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

Per evitare questo, ho fatto la radice dell'albero di classe descrittore di ereditare da il seguente:

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

A volte si desidera l'oggetto descrittore? Ciò viola l'aspettativa principale dei descrittori: dovrebbero assomigliare a degli attributi. Perché rompere questa aspettativa fondamentale? Perché questo? Perché creare qualcosa di così complesso? –

+0

Quello che sto facendo non mi sembra * così * complesso, ma immagino si possa dire che sto sperimentando il design. Nel mio attuale caso particolare, ho un descrittore che restituisce la forza di un'arma in un gioco. Quel valore è una funzione dello stato del descrittore (forza dell'arma) e dell'istanza (salute della nave). Esistono diversi tipi di armi; di solito voglio solo il risultato del valore, ma in alcuni casi, ho bisogno di sapere che tipo di arma è - il tipo del descrittore. E se un descrittore ha metodi che non fanno parte del protocollo descrittore e vuoi chiamarli? –

risposta

11

la maggior parte dei descrittori di fare il loro lavoro quando vi si accede come esempio solo attribuire. Quindi è comodo per ritornare in sé quando è accessibile per la classe:

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

Questo consente di ottenere descrittore stesso:

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

Nota, che questo funziona per (la maggior parte?) Descrittori built-in anche:

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

descrittore di accesso potrebbe essere utile quando è necessario misura in sottoclasse, ma c'è un better way per fare questo.

+0

Non lo sapevo; buono a sapersi. Le funzioni stesse sarebbero un'eccezione un'eccezione a quel modello, ma forse l'unica. Dovrà colpire i descrittori incorporati. –

+0

Anche se non ha risposto alla mia domanda come chiesto esattamente, la tua risposta mi ha aiutato a risolvere il mio problema di fondo. Lo accetterò. –

+0

sono un'eccezione a questo modello (presumo che parli di metodi)? No, 'c.method' restituisce un metodo associato da una descrizione, mentre' C.method' restituisce un metodo non associato. È lo stesso schema. – u0b34a0f6ae

0

Il metodo

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

sopra è un ottimo metodo ogni volta che è possibile controllare il codice della proprietà, ma ci sono alcuni casi, ad esempio quando la proprietà è parte di una libreria controllata da qualcun altro, dove un altro l'approccio è utile Questo approccio alternativo può anche essere utile in altre situazioni, come l'implementazione della mappatura degli oggetti, il camminare nello spazio dei nomi come descritto nella domanda o in altre librerie specializzate.

consideri una classe con una proprietà semplice:

class ClassWithProp: 

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

Quando si accede dagli oggetti contenitore classe dict, bypassa il 'descrittore magico'.Si noti inoltre che se assegniamo la proprietà a una nuova variabile di classe, essa si comporta proprio come l'originale con "magia descrittore", ma se assegnata a una variabile di istanza, la proprietà si comporta come qualsiasi oggetto normale e ignora anche la "magia descrittore".

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

Diciamo che vogliamo ottenere il descrittore per obj.prop dove type(obj) is C.

C.prop di solito funziona perché il descrittore di solito restituisce se stesso quando si accede tramite C (ad esempio, associato a C). Ma C.prop può attivare un descrittore nel suo metaclasse. Se prop non fosse presente in obj, obj.prop aumenterebbe AttributeError mentre C.prop potrebbe non. Quindi è meglio usare inspect.getattr_static(obj, 'prop').

Se non siete soddisfatti con questo, ecco un metodo CPython-specifico (da _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') restituisce il descrittore nello stesso modo in cui CPython lo utilizza a obj.prop se obj è un oggetto usuale (non di classe, ad esempio).