2012-09-26 9 views
19

Alla domanda:descrittori come attributi esempio in python

Perché non può essere descrittori attributi esempio?

è stato answered che:

oggetti descrittori ha bisogno di vivere nella classe, non nel caso

perché questo è il modo in cui il __getattribute__ è implementato.

Un semplice esempio. Si consideri un descrittore:

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 

Si consideri il caso in cui ogni obj ha Prop multipla: avrei bisogno di usare nomi univoci per identificare i valori e moltiplicatori (come here Avere un oggetto descrittore per esempio permetterebbe di memorizzare il. . _multiplier (e la _value) nel descrittore di per sé, semplificando alcune cose

per attuare per descrittore esempio gli attributi è necessario uno:

  1. per creare un'istanza di classe 012.
  2. esclusione __getattribute__See here

Sono consapevole del fatto che le questioni analoghe sono state sollevate prima, ma non ho trovato una vera e propria spiegazione:

  1. Perché Python è stato progettato in questo modo?
  2. Qual è il modo suggerito per archiviare le informazioni che il descrittore necessita ma è per istanza?

risposta

9

Un sacco di funzionalità avanzate funziona solo se definito su una classe anziché su un'istanza; tutti i metodi speciali, per esempio. Oltre a rendere più efficiente la valutazione del codice, ciò rende evidente la separazione tra istanze e tipi che altrimenti tenderanno a collassare (perché ovviamente tutti i tipi sono oggetti).

io non sono sicuro di come raccomandato questo è, ma nel caso è possibile memorizzare una mappatura da un'istanza descrittore di attribuire valore:

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} 

Questo ha evidenti vantaggi rispetto alle altre due opzioni suggerite:

  1. le classi peristanza interrompono l'orientamento dell'oggetto e aumentano l'utilizzo della memoria;
  2. ignorare __getattribute__ è inefficiente (poiché tutti gli accessi degli attributi devono passare attraverso il metodo speciale sovrascritto) ed è fragile.

In alternativa, è possibile utilizzare una proprietà di 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

Puoi espandere ciò che intendi per ** break object orientation **. In secondo luogo, il problema che vedo con questo approccio è come fornire una semplice API per modificare il moltiplicatore. Gli utenti dovranno fare qualcosa come "obj._multiplier [Obj.val] = 10'. Questo può essere racchiuso in una funzione 'def change_multiplier (self, attr_name, new_value)' ma non si adatta bene se 'Prop' ha attributi multipli. Qualcosa come "def prop (self, attr_name): return self.__dict __ [attr_name] 'potrebbe essere usato per fare qualcosa come' obj.prop ('val'). moltiplicatore = 10'. – Hernan

+0

@Hernan c'è una solita ipotesi che le istanze abbiano lo stesso tipo; violare ciò e varie cose si romperanno. Per quanto riguarda la modifica del moltiplicatore, forse una proprietà proxy? - vedi modifica sopra. – ecatmur

+0

È vero che tutte le istanze non saranno dello stesso tipo, ma potresti creare sottoclassi in modo che isinstance funzioni ancora. Per quanto riguarda il proxy, ho scritto qualcosa del genere ma non sono sicuro che sia una buona idea. Fondamentalmente, 'obj.prop ('val')' restituisce un oggetto proxy che conosce 'obj' e 'val'. Quando si esegue 'obj.prop ('val'). Moltiplicatore = 10' scrive in' obj._multiplier [val] = 10'. Non sono sicuro di quanto sarà mantenibile. – Hernan

15

Questa domanda esatta era raised on Python-list all'inizio di quest'anno. Sto solo citando Ian G. Kelly's response:

Il comportamento è di progettazione. Innanzitutto, mantenere il comportamento degli oggetti nella definizione della classe semplifica l'implementazione e rende anche più significativi i controlli dell'istanza . Per prendere a prestito il tuo esempio Register, se il descrittore "M" è definito da alcune istanze piuttosto che dalla classe, allora sapendo che l'oggetto "reg" è un'istanza di Register non dice a nulla riguardo se "reg.M "è un attributo valido o un errore. Come risultato di , dovrò proteggere virtualmente ogni accesso di "reg.M" con un costrutto try-except nel caso in cui "reg" sia il tipo di registro sbagliato.

In secondo luogo, la separazione della classe dall'istanza consente inoltre di mantenere il comportamento dell'oggetto separato dai dati dell'oggetto. Si consideri il seguente classe:

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

Non preoccuparti per quello che questa classe potrebbe essere utile per.Basta sapere che è destinata a tenere e fornire accesso illimitato a arbitrari Python oggetti:

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

Poiché la classe è destinato a contenere oggetti arbitrari, è ancora valida che qualcuno potrebbe desiderare di salvare un oggetto descrittore lì:

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

supponiamo ora che Python invocato il protocollo descrittore per descrittori memorizzata in caso attributi:

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

In questo caso, ObjectHolder non riuscirebbe semplicemente a mantenere l'oggetto di proprietà come dati. Il semplice atto di assegnare l'oggetto proprietà, un descrittore , ad un attributo di istanza, , modifica il comportamento di ObjectHolder. Invece di trattare "holder.obj" come un semplice attributo , inizierebbe a richiamare il protocollo descrittore sugli accessi a "holder.obj" e in definitiva reindirizzare l'inesistente e l'attributo "holder.foo" inesistente, , che non è certamente quello che l'autore della classe ha voluto.

Se si desidera essere in grado di supportare più istanze di un descrittore, basta fare in modo che il costruttore del descrittore prenda un argomento di nome (prefisso) e anteponga gli attributi aggiunti con quel nome. Si potrebbe anche creare un oggetto namespace (dizionario) all'interno dell'istanza della classe per contenere tutte le nuove istanze di proprietà.

+0

I link a python-list sono morti. È il nuovo link https://mail.python.org/pipermail/python-list/2012-January/631340.html? –

+0

Sì. Lo modificherò. – nneonneo