2009-10-27 10 views
6

Zacząłem intensywniej używać protokołu deskryptora python w kodzie, który pisałem. Zazwyczaj domyślna magia wyszukiwania pythona jest tym, co chcę, ale czasami odkrywam, że chcę uzyskać sam obiekt deskryptora zamiast wyników jego metody __get__. Chcąc poznać typ deskryptora lub stan dostępu zapisany w deskryptorze lub coś takiego.Wyszukiwanie atrybutów python bez magii deskryptorów?

Napisałem poniższy kod, aby przejść do przestrzeni nazw w tym, co uważam za poprawne uporządkowanie, i zwrócić atrybut raw, niezależnie od tego, czy jest to deskryptor, czy nie. Jestem jednak zaskoczony, że nie mogę znaleźć wbudowanej funkcji lub czegoś w standardowej bibliotece, aby to zrobić - myślę, że to musi tam być i po prostu nie zauważyłem tego ani nie wybrałem go w wyszukiwarce.

Czy istnieje funkcja gdzieś w dystrybucji Pythona, która już to robi (lub coś podobnego)?

Dzięki!

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 

Edit Wed, 28 października 2009.

odpowiedź Denisa dał mi konwencję do użycia w moich klasach deskryptorów, aby uzyskać deskryptor sprzeciwia się. Ale miałem całą klasę hierarchię klas deskryptora, a ja nie chciałem rozpocząć każdy__get__ funkcję z boilerplate

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

Aby tego uniknąć, zrobiłem korzeniem drzewa klasy dziedziczą z deskryptora następujące:

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

Czasami chcesz obiekt deskryptora? To narusza podstawowe oczekiwania dla deskryptorów: powinny wyglądać jak atrybuty. Po co łamać podstawowe oczekiwania? Czemu to robić? Po co tworzyć coś tak złożonego? –

+0

To, co robię, nie wydaje mi się * to * skomplikowane, ale myślę, że można powiedzieć, że eksperymentuję z projektem. W moim aktualnym, szczególnym przypadku mam deskryptor, który zwraca siłę broni w grze. Wartość ta jest funkcją stanu deskryptora (siła broni) i instancji (stanu zdrowia statku). Istnieją różne rodzaje broni; zwykle po prostu chcę uzyskać wartość, ale w kilku przypadkach muszę wiedzieć, jaka to broń - typ deskryptora. A jeśli deskryptor ma metody, które nie są częścią protokołu deskryptora, i czy chcesz do nich zadzwonić? –

Odpowiedz

11

Większość deskryptorów wykonuje swoją pracę, gdy uzyskuje dostęp tylko jako atrybut instancji. Więc jest to wygodne, aby wrócić się, kiedy jest dostępna dla klasy:

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

Pozwala to uzyskać deskryptora sam:

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

uwaga, że ​​to działa na (większość?) Wbudowany deskryptorów TOO:

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

Dostęp deskryptor może być przydatna, gdy trzeba stopniu ją w podklasie, ale jest better way to zrobić.

+0

Nie zdawałem sobie z tego sprawy; dobrze wiedzieć. Funkcje same w sobie były wyjątkiem, wyjątkiem, ale może jedynym. Będzie musiał wczepić się w wbudowane deskryptory. –

+0

Chociaż nie odpowiedziałem na moje pytanie dokładnie tak, jak się o to pytałem, twoja odpowiedź pomogła mi rozwiązać mój podstawowy problem. Przyjmę to. –

+0

są funkcjami wyjątku od tego schematu (zakładam, że mówisz o metodach)? Nie, 'c.method' zwraca związaną metodę z opisu, podczas gdy' C.method' zwraca niezwiązaną metodę. To ten sam wzór. – u0b34a0f6ae

0

Powyższa metoda

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

to świetny sposób, kiedy można kontrolować kod nieruchomości, ale istnieją pewne przypadki, takie jak wtedy, gdy nieruchomość jest częścią biblioteki kontrolowany przez kogoś innego, gdzie inny podejście jest użyteczne. To alternatywne podejście może być również przydatne w innych sytuacjach, takich jak wdrażanie mapowania obiektów, chodzenie w przestrzeni nazw zgodnie z opisem w pytaniu lub inne specjalistyczne biblioteki.

Rozważmy klasę za pomocą prostego nieruchomości:

class ClassWithProp: 

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

Kiedy dostępne z obiektów kontenerowych klasy DICT pojęcie „magia deskryptor” jest pomijany.Zauważ też, że jeśli przypiszemy właściwość do nowej zmiennej klasy, zachowa się ona tak jak oryginał z "magią deskryptorów", ale jeśli zostanie przypisana do zmiennej instancji, właściwość zachowuje się jak każdy normalny obiekt, a także omija "magię deskryptorów".

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

Załóżmy, że chcemy uzyskać deskryptor dla obj.prop, gdzie type(obj) is C.

C.prop zwykle działa, ponieważ deskryptor zwykle zwraca się samoczynnie, gdy dostęp do niego uzyskuje się przez C (tj. Jest powiązany z C). Ale C.prop może wywołać deskryptor w metaclassie. Jeśli prop nie byłby obecny w obj, to podniósłby AttributeError, podczas gdy C.prop może nie. Więc lepiej jest użyć inspect.getattr_static(obj, 'prop').

Jeżeli nie jesteś zadowolony z tego, oto metoda CPython specyficzne (od _PyObject_GenericGetAttrWithDict w 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') zwraca deskryptor w taki sam sposób, gdy CPython używa go w obj.prop jeśli obj jest zwykły obiekt (nie klasa, na przykład).