Pergunta

Eu comecei a usar o protocolo de descritor de python mais extensivamente no código que eu tenho escrito. Normalmente, a magia do python padrão de pesquisa é o que eu quero que aconteça, mas às vezes eu estou achando eu quero começar o próprio objeto descritor vez os resultados de seu método __get__. Querendo saber o tipo de descritor, ou estado de acesso armazenados no descritor, ou coisa somesuch.

Eu escrevi o código abaixo para percorrer os namespaces em que eu acredito é a ordem correta, e retornar a matéria-atributo, independentemente de se tratar de um descritor ou não. Eu estou surpreso que embora eu não consigo encontrar uma função built-in ou algo na biblioteca padrão para fazer isso - eu acho que tem que estar lá e eu não ter notado ou pesquisei para o termo de pesquisa direita.

Existe funcionalidade em algum lugar na distribuição python que já faz isso (ou algo similar)?

Obrigado!

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

Editar Wed, 28 de outubro de 2009.

A resposta de Denis me deu uma convenção para usar em minhas aulas descritor para obter o descritor de objetos em si. Mas, eu tinha uma hierarquia de classe inteira de aulas descritor, e eu não queria começar a todas função __get__ com um clichê

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

Para evitar isso, eu fiz a raiz da árvore de herdar classe descritor a partir do seguinte:

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)
Foi útil?

Solução

A maioria dos descritores de fazer o seu trabalho quando acessado apenas como atributo de instância. Por isso é conveniente para si voltar quando ele é acessado por 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

Isso permite que você para obter-se descritor:

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

Note que isso funciona para (a maioria?) Built-in descritores demasiado:

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

Acessando descritor poderia ser útil quando você precisa medida em que na subclasse, mas há um melhor maneira para fazer isso.

Outras dicas

A biblioteca inspect fornece uma função para recuperar um atributo sem qualquer magia descritor:. inspect.getattr_static

Documentação: https://docs.python.org /3/library/inspect.html#fetching-attributes-statically

(Esta é uma questão de idade, mas eu continuar a frente quando tentando lembrar como fazer isso, então eu estou postando esta resposta para que eu possa encontrá-lo novamente!)

O método acima

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

é um ótimo método sempre que você controlar o código da propriedade, mas existem alguns casos, tais como quando a propriedade é parte de uma biblioteca controlada por outra pessoa, onde uma outra abordagem é útil. Esta abordagem alternativa pode também ser útil em outras situações como a implementação de mapeamento objeto, andando um nome-espaço como descrito na pergunta, ou outras bibliotecas especializadas.

Considere uma classe com uma propriedade simples:

class ClassWithProp:

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

Quando acessado a partir do recipiente objetos classe dict , a 'magia descritor' é ignorada. Note-se também que, se atribuir a propriedade a uma nova variável de classe, ele se comporta exatamente como o original com 'magia descritor', mas se atribuído a uma variável de instância, se comporta de propriedade como qualquer objeto normal, e também ignora 'mágica descritor'.

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

Vamos dizer que queremos obter o descritor para obj.prop onde type(obj) is C.

C.prop normalmente funciona porque o descritor geralmente retorna em si quando acessado via C (ou seja, obrigado a C). Mas C.prop pode desencadear um descritor em sua metaclasse. Se prop não estavam presentes no obj, obj.prop elevaria AttributeError enquanto C.prop talvez não. Por isso, é melhor usar inspect.getattr_static(obj, 'prop').

Se você não estiver satisfeito com isso, aqui está um método específico do CPython (de _PyObject_GenericGetAttrWithDict em 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') retorna o descritor da mesma forma quando CPython usa-lo em obj.prop se obj é um objeto normal (não de classe, por exemplo).

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top