Domanda

Ho iniziato a utilizzare il protocollo descrittore python in modo più approfondito nel codice che ho scritto. In genere, la magia di ricerca di Python predefinita è ciò che voglio accadere, ma a volte sto trovando che voglio ottenere l'oggetto descrittore stesso invece i risultati del suo metodo __get__ . Volendo sapere il tipo di descrittore, o lo stato di accesso memorizzato nel descrittore, o qualcosa del genere.

Ho scritto il codice seguente per percorrere gli spazi dei nomi in quello che credo sia l'ordinamento corretto e restituire l'attributo grezzo indipendentemente dal fatto che si tratti di un descrittore o meno. Sono sorpreso però che non riesca a trovare una funzione integrata o qualcosa nella libreria standard per fare questo: immagino che debba essere lì e non l'ho notato o ho cercato su Google il termine di ricerca giusto.

C'è qualche funzionalità nella distribuzione di Python che già lo fa (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 ottobre 2009.

La risposta di Denis mi ha dato una convenzione da usare nelle mie classi di descrittori per ottenere gli oggetti descrittori stessi. Ma avevo un'intera gerarchia di classi di descrittori e non volevo iniziare ogni __get__ con una caldaia

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

Per evitare ciò, ho fatto ereditare la radice dell'albero delle classi descrittore da quanto segue:

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)
È stato utile?

Soluzione

La maggior parte dei descrittori fa il proprio lavoro quando si accede solo come attributo di istanza. Quindi è conveniente restituire se stesso quando si accede 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 ti permette di ottenere il 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 anche per (la maggior parte?) descrittori integrati:

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

L'accesso al descrittore potrebbe essere utile quando è necessario estenderlo nella sottoclasse, ma esiste un modo migliore per farlo.

Altri suggerimenti

La libreria inspect fornisce una funzione per recuperare un attributo senza alcuna magia descrittiva: inspect.getattr_static .

Documentazione: https://docs.python.org /3/library/inspect.html#fetching-attributes-statically

(Questa è una vecchia domanda, ma continuo a trovarla quando provo a ricordare come farlo, quindi sto postando questa risposta in modo da poterla trovare di nuovo!)

Il metodo sopra

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

È un ottimo metodo ogni volta che controlli il codice della proprietà, ma ci sono alcuni casi, come quando la proprietà fa parte di una libreria controllata da qualcun altro, in cui è utile un altro approccio. Questo approccio alternativo può essere utile anche in altre situazioni come l'implementazione della mappatura degli oggetti, lo spostamento di uno spazio dei nomi come descritto nella domanda o altre librerie specializzate.

Considera 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 dalla classe di oggetti contenitore dict , la "magia descrittore" viene ignorata. Si noti inoltre che se assegniamo la proprietà a una nuova variabile di classe, si comporta esattamente come l'originale con "descrittore magico", ma se assegnata a una variabile di istanza, la proprietà si comporta come qualsiasi oggetto normale e ignora anche "descrittore magico".

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

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

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

Se non sei soddisfatto, ecco un metodo specifico per CPython (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 allo stesso modo quando CPython lo usa in obj.prop se obj è un oggetto normale (non la classe, ad esempio).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top