2012-09-26 21 views
19

na pytanie:Descriptors jako atrybuty instancji w Pythonie

Dlaczego nie może być instancja deskryptory atrybuty?

to było answered że:

obiekty deskryptora musi żyć w klasie, a nie na przykład

ponieważ jest to sposób, że __getattribute__ jest realizowany.

Prosty przykład. Rozważmy Eurovoc:

class Prop(object): 

    def __get__(self, obj, objtype=None): 
     if obj is None: 
      return self 
     return obj._value * obj._multiplier 

    def __set__(self, obj, value): 
     if obj is None: 
      return self 
     obj._value = value 

class Obj(object): 

    val = Prop() 

    def __init__(self): 
     self._value = 1 
     self._multiplier = 0 

Rozważmy przypadek, w którym każdy obj ma wiele Prop: czy muszę korzystać z unikatowych nazw do określenia wartości i mnożników (jak here Mając za przykład obiekt deskryptora pozwoliłoby przechowywać. . _multiplier (a _value) w samej deskryptorze, upraszczając kilka rzeczy

aby wdrożyć na przykład deskryptora atrybuty trzeba albo:

  1. utworzyć za instancji klasy See here
  2. przesłanianie __getattribute__See here

Zdaję sobie sprawę, że podobne kwestie zostały podniesione wcześniej, ale nie znalazłem prawdziwe wyjaśnienie:

  1. Dlaczego Python został zaprojektowany w ten sposób?
  2. Jaki jest sugerowany sposób przechowywania informacji wymaganych przez deskryptor, ale na przykład?

Odpowiedz

9

Wiele zaawansowanych funkcji działa tylko wtedy, gdy są zdefiniowane w klasie, a nie w instancji; wszystkie specjalne metody, na przykład. Oprócz zwiększania wydajności kodowania, daje to wyraźne rozróżnienie między instancjami i typami, które w przeciwnym razie miałyby tendencję do zapadania się (ponieważ oczywiście wszystkie typy są obiektami).

Nie jestem pewien, jak to jest polecany, ale można w instancji przechowywania odwzorowującym przykład deskryptora przypisać wartość:

class Prop(object): 
    def __get__(self, obj, objtype=None): 
     if obj is None: 
      return self 
     return obj._value * obj._multiplier[self] 

    def __set__(self, obj, value): 
     if obj is None: 
      return self 
     obj._value = value 

class Obj(object): 
    val = Prop() 

    def __init__(self): 
     self._value = 1 
     self._multiplier = {Obj.val: 0} 

Ma to oczywiste zalety w stosunku do pozostałych dwóch sugerowanych opcji:

  1. klasy dla poszczególnych wystąpień przerywają orientację obiektów i zwiększają wykorzystanie pamięci;
  2. przesłonięcie __getattribute__ jest nieefektywne (ponieważ wszystkie uprawnienia do atrybutów muszą przejść przez przesłoniętą metodę specjalną) i są delikatne.

Jako alternatywę można użyć własności Proxy:

class PerInstancePropertyProxy(object): 
    def __init__(self, prop): 
     self.prop = prop 
    def __get__(self, instance, owner): 
     if instance is None: 
      return self 
     return instance.__dict__[self.prop].__get__(instance, owner) 
    def __set__(self, instance, value): 
     instance.__dict__[self.prop].__set__(instance, value) 
class Prop(object): 
    def __init__(self, value, multiplier): 
     self.value = value 
     self.multiplier = multiplier 
    def __get__(self, instance, owner): 
     if instance is None: 
      return self 
     return self.value * self.multiplier 
    def __set__(self, instance, value): 
     self.value = value 
class Obj(object): 
    val = PerInstancePropertyProxy('val') 
    def __init__(self): 
     self.__dict__['val'] = Prop(1.0, 10.0) 
    def prop(self, attr_name): 
     return self.__dict__[attr_name] 
+0

Czy możesz rozszerzyć to, co masz na myśli przez ** orientację obiektu na przerwę **. Po drugie, problemem, który widzę w tym podejściu, jest to, jak zapewnić prosty interfejs API do zmiany mnożnika. Użytkownicy będą musieli zrobić coś w stylu: "obj._multiplier [Obj.val] = 10". Może to być zawinięte w funkcję 'def change_multiplier (self, attr_name, new_value)' ale nie skaluje się ładnie, jeśli 'Prop' ma wiele atrybutów. Coś jak 'def prop (self, attr_name): return self.__dict __ [attr_name] 'może być użyte do zrobienia czegoś takiego jak" obj.prop ('val') .multurek = 10'. – Hernan

+0

@Hernan istnieje typowe założenie, że instancje mają ten sam typ; naruszy to i różne rzeczy się zepsują. Jeśli chodzi o zmianę mnożnika, być może właściwość proxy? - patrz edycja powyżej. – ecatmur

+0

Prawdą jest, że wszystkie instancje nie będą tego samego typu, ale możesz utworzyć podklasy, aby system nadal działał. Jeśli chodzi o pełnomocnika, napisałem coś takiego, ale nie jestem pewien, czy to dobry pomysł. Zasadniczo, 'obj.prop ('val')' zwraca obiekt proxy, który wie o 'obj' i' val'. Gdy wykonujesz polecenie 'obj.prop ('val'), mnożnik = 10' zapisuje do' obj._multiplier [val] = 10'. Po prostu nie jestem pewien, jak to będzie możliwe do utrzymania. – Hernan

15

To dokładne pytanie było raised on Python-list na początku tego roku. Zamierzam zacytować: Ian G. Kelly's response:

Zachowanie jest zgodne z projektem. Po pierwsze zachowanie zachowania obiektu w definicji klasy upraszcza implementację, a także sprawia, że ​​instancja sprawdza się bardziej. Aby pożyczyć twój przykład rejestru, jeśli deskryptor "M" jest zdefiniowany przez niektóre instancje, a nie przez klasę, to wiedząc, że obiekt "reg" jest instancją rejestru, nie mówi mi nic o tym, czy "reg.M "jest prawidłowym atrybutem lub błędem. Jako wynik, będę musiał strzec praktycznie każdego dostępu do "reg.M" z konstruktem try-except, na wypadek gdyby "reg" był niewłaściwym rejestrem.

Po drugie, oddzielenie klasy od instancji pomaga również zachować zachowanie obiektu oddzielnie od danych obiektu. Rozważmy następujący klasę:

class ObjectHolder(object): 
    def __init__(self, obj): 
     self.obj = obj 

nie martw się o to, co może być przydatne dla tej klasy.Wystarczy wiedzieć, że to ma trzymać i zapewnić nieograniczony dostęp do dowolnych Python obiektów:

>>> holder = ObjectHolder(42) 
>>> print(holder.obj) 42 
>>> holder.obj = range(5) 
>>> print(holder.obj) [0, 1, 2, 3, 4] 

Ponieważ klasa jest przewidziany do przechowywania dowolnych obiektów, to jest nawet ważne że ktoś może chcieć przechowywać tam obiekt Eurovoc:

>>> holder.obj = property(lambda x: x.foo) 
>>> print(holder.obj) <property object at 0x02415AE0> 

załóżmy teraz, że Python wywołany protokół deskryptor deskryptorów przechowywane w instancji atrybuty:

>>> holder = ObjectHolder(None) 
>>> holder.obj = property(lambda x: x.foo) 
>>> print(holder.obj) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
AttributeError: 'ObjectHolder' object has no attribute 'foo' 

W takim przypadku obiekt ObjectHolder nie po prostu zatrzyma obiekt jako obiekt danych. Sama czynność przypisania obiektu właściwości, deskryptorowi , do atrybutu instancji spowoduje zmianę ustawienia obiektu ObjectHolder na . Zamiast traktować "holder.obj" jako prosty atrybut danych , zacznie on wywoływać protokół deskryptorów na dostępach do "właściciel.obj" i ostatecznie przekierować je do nieistniejącego i pozbawionego znaczenia atrybutu "właściciel.foo", co z pewnością nie jest tym, co zamierzał autor klasy.

Jeśli chcesz mieć możliwość obsługi wielu wystąpień deskryptora, po prostu ustaw ten konstruktor deskryptora na argument nazwy (prefiks) i dodaj atrybuty o tej nazwie. Można nawet utworzyć obiekt przestrzeni nazw (słownik) wewnątrz instancji klasy, aby pomieścić wszystkie nowe wystąpienia właściwości.

+0

Linki do python-liście są martwe. Czy nowy link to https://mail.python.org/pipermail/python-list/2012-January/631340.html? –

+0

Tak. Zmienię to. – nneonneo