ricerca dell'attributo python senza nessuna magia descrittiva?
-
06-07-2019 - |
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)
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).