2014-04-12 9 views
5

Un modello di progettazione comune quando si utilizzano i descrittori python consiste nel mantenere il descrittore in un dizionario delle istanze che utilizza tale descrittore. Ad esempio, supponiamo che io voglio fare un attributo che conta il numero di volte che è accessibile:Utilizzo di descrittori in classi non modificabili - python

class CountingAttribute(object): 

    def __init__(self): 
     self.count = 0 
     self.value = None 


class MyDescriptor(object): 

    def __init__(self): 
     self.instances = {} #instance -> CountingAttribute 

    def __get__(self, inst, cls): 
     if inst in self.instances: 
      ca = self.instances[inst] 
     else: 
      ca = CountingAttribute() 
      self.instances[inst] = ca 
     ca.count += 1 
     return ca 


class Foo(object): 
    x = MyDescriptor() 


def main(): 
    f = Foo() 
    f.x 
    f.x 
    print("f.x has been accessed %d times (including the one in this print)"%(f.x.count,)) 

if __name__ == "__main__": 
    main() 

Questo è un esempio del tutto stupido che non fa nulla di utile; Sto cercando di isolare il punto principale.

Il problema è che non posso usare questo descrittore in una classe che non è hashable, perché la linea

self.instances[inst] = ca 

utilizza casi come chiave del dizionario. C'è un modo saggio per gestire questo tipo di caso? Ad esempio, si pensa immediatamente di usare l'istanza id, ma non sono sicuro che se ciò dovesse accadere si interromperà qualcosa su come si suppone che gli hash vengano usati.

EDIT: Mi rendo conto che instances dovrebbe essere qualcosa come un weakref.WeakKeyDictionary ma sto cercando di mantenere qui semplice concentrarsi sul problema dell'habhability.

+1

Il mio primo pensiero è stato quello di provare a usare ['WeakKeyDictionary'] (https://docs.python.org/3.4/library/weakref.html#weakref.WeakKeyDictionary) per' instance'. Sfortunatamente, questo non sembra risolvere il problema sottostante di disabilità, ma è comunque una buona idea. (Altrimenti, il descrittore manterrebbe vivo un oggetto altrimenti morto.) – icktoofay

+0

Sì, nella mia implementazione effettiva uso un WeakKeyDictionary. Non l'ho fatto qui perché sto cercando di mantenere l'attenzione sulla domanda in questione. – DanielSank

risposta

3

È possibile utilizzare id(inst) come chiave.

Si noti che questo non copre il caso in cui un oggetto viene distrutto e ne viene creato uno nuovo con un nuovo id.

Per rilevare correttamente, è necessario memorizzare cae nel dizionario. Se si rileva che l'oggetto di riferimento di weakref è scomparso, si deve assumere che l'id specificato sia riutilizzato.

Qualcosa di simile

import weakref 

class MyDescriptor(object): 

    def __init__(self): 
     self.instances = {} #instance -> CountingAttribute 

    def __get__(self, inst, cls): 
     if inst is None: return self.instances # operating on the class, we get the dictionary. 
     i = id(inst) 
     if i in self.instances: 
      ca, wr = self.instances[i] 
      if wr() is None: del self.instances[i] 
     if i not in self.instances: 
      ca = CountingAttribute() 
      self.instances[i] = (ca, weakref.ref(inst)) 
     ca.count += 1 
     return ca 

Questo allevia dai problemi hashability conntected ad un WeakKeyDictionary.

Ma forse non hai affatto bisogno del dett. Un approccio completamente diverso potrebbe essere

class MyDescriptor(object): 

    def __get__(self, inst, cls): 
     if inst is None: return self, cls 
     try: 
      ca = inst.__the_ca 
     except AttributeError: 
      ca = inst.__the_ca = CountingAttribute() 
     ca.count += 1 
     return ca 

Questo approccio ha anche i suoi lati negativi. Ad esempio, non è possibile utilizzare facilmente il descrittore più di una volta in una classe senza renderlo brutto. Pertanto, dovrebbe essere usato solo con cura. La prima soluzione è, mentre più complessa, la più semplice.

+0

A che cosa si riferisce il riferimento debole? :) – DanielSank

+0

@DanielSank Modificato. – glglgl

+0

Questo è davvero intelligente. – DanielSank

Problemi correlati