Question

J'ai commencé à utiliser le protocole de description python de manière plus détaillée dans le code que j'ai écrit. En règle générale, la magie de recherche python par défaut correspond à ce que je veux, mais parfois, je souhaite obtenir l'objet descripteur lui-même au lieu des résultats de sa méthode __ get __ . Vouloir connaître le type de descripteur, ou l’état d’accès stocké dans le descripteur, ou une telle chose.

J'ai écrit le code ci-dessous pour parcourir les espaces de noms dans ce que je crois être le bon ordre, et renvoyer l'attribut brut, qu'il s'agisse ou non d'un descripteur. Je suis toutefois surpris que je ne trouve pas de fonction intégrée ni d’objet dans la bibliothèque standard pour ce faire. Je suppose que cela doit être là et que je ne l’ai pas remarqué ni recherché sur Google le terme de recherche approprié.

Y at-il une fonctionnalité quelque part dans la distribution python qui fait déjà ceci (ou quelque chose de similaire)?

Merci!

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

Éditer mer. 28 oct. 2009.

La réponse de Denis m'a donné une convention à utiliser dans mes classes de descripteur pour obtenir les objets de descripteur eux-mêmes. Mais j’avais toute une hiérarchie de classes de descripteurs et je ne voulais pas commencer toutes les __ obtenir __ par un passe-partout

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

Pour éviter cela, j'ai fait hériter de la racine de l'arborescence de classe de descripteur:

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)
Était-ce utile?

La solution

La plupart des descripteurs font leur travail lorsqu'ils sont utilisés en tant qu'attribut d'instance uniquement. Il est donc pratique de se retourner quand on y accède en 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

Cela vous permet d’obtenir le descripteur lui-même:

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

Notez que cela fonctionne également pour la plupart des descripteurs intégrés:

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

L'accès au descripteur peut être utile lorsque vous devez l'étendre dans la sous-classe, mais il existe un meilleure façon de le faire.

Autres conseils

La bibliothèque inspect fournit une fonction permettant de récupérer un attribut sans descripteur magique: inspect.getattr_static .

Documentation: https://docs.python.org /3/library/inspect.html#fetching-attributes-stically

(C'est une vieille question, mais je n'arrête pas de la rencontrer en essayant de me rappeler comment faire cela, alors je poste cette réponse pour pouvoir la retrouver!)

La méthode ci-dessus

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

est une excellente méthode lorsque vous contrôlez le code de la propriété, mais dans certains cas, par exemple lorsque la propriété fait partie d'une bibliothèque contrôlée par quelqu'un d'autre, une autre approche est utile. Cette approche alternative peut également s'avérer utile dans d'autres situations, telles que la mise en œuvre d'un mappage d'objet, la navigation dans un espace de noms comme décrit dans la question ou d'autres bibliothèques spécialisées.

Considérons une classe avec une propriété simple:

class ClassWithProp:

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

Lors de l'accès à partir de la classe d'objets conteneur dict , le "descripteur magique" est ignoré. Notez également que si nous affectons la propriété à une nouvelle variable de classe, celle-ci se comporte exactement comme l'original avec 'descripteur magique', mais si elle est affectée à une variable d'instance, la propriété se comporte comme tout objet normal et contourne également 'descripteur magique'.

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

Supposons que nous voulions obtenir le descripteur pour obj.prop type (obj) est C .

C.prop fonctionne généralement parce que le descripteur se retourne lui-même lorsqu'il est accédé via C (c'est-à-dire lié à C ). Mais C.prop peut déclencher un descripteur dans sa métaclasse. Si prop n'était pas présent dans obj , obj.prop générerait AttributeError tandis que C.prop pourrait ne pas. Il est donc préférable d'utiliser inspect.getattr_static (obj, 'prop') .

Si cela ne vous satisfait pas, voici une méthode spécifique à CPython (issue de _PyObject_GenericGetAttrWithDict dans Objets / objet.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') renvoie le descripteur de la même manière lorsque CPython l’utilise à obj.prop si obj est un objet habituel (pas une classe, par exemple).

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top