2010-01-25 7 views
0

Quelqu'un peut-il identifier ici pourquoi l'erreur TypeError est levée au bas de cet exemple illustré ci-dessous?Héritage multiple en Python (spécifique au problème)

>>> import threading 
>>> class SessionManager(threading.Thread, threading._RLock, dict): 

    UPDATE = 60 * 60 

    def run(self): 
     while True: 
      time.sleep(self.UPDATE) 
      with self: 
       for key in tuple(self): 
        if not self[key]: 
         del self[key] 

    def __getitem__(self, key): 
     session = super()[key] 
     session.wakeup() 
     return session 

>>> SM = SessionManager() 
>>> SM.daemon = True 
>>> SM.start() 
Traceback (most recent call last): 
    File "<pyshell#5>", line 1, in <module> 
    SM.start() 
TypeError: unhashable type: 'SessionManager' 
>>> 

Edit:

Ce qui suit est la version finale du module commencé ci-dessus. Il est utilisé dans le programme VerseMatch.

#! /usr/bin/env python 
"""Oversee the timely destruction of unused sessions. 

The two classes in this module allow automated memory cleanup to be regularly 
performed and timed actions to be executed within reasonable time periods.""" 

################################################################################ 

__author__ = 'Stephen "Zero" Chappell <[email protected]>' 
__date__ = '11 February 2010' 
__version__ = '$Revision: 3 $' 

################################################################################ 

import threading 
import time 

################################################################################ 

class SessionManager(threading.Thread, threading._RLock, dict): 

    """Manage session objects along with associated data. 

    This class acts as dictionary with a data-protection mutex. 
    It can run a cleanup routine at regular intervals if needed.""" 

    def __init__(self, sleep_interval): 
     """Initialize variables in SessionManager's parent classes.""" 
     threading.Thread.__init__(self) 
     threading._RLock.__init__(self) 
     self.__sleep_interval = sleep_interval 

    def run(self): 
     """Remove old sessions from memory as needed. 

     This method is executed by calling .start() on a SessionManager 
     object. The "daemon" attribute may need be set to True before 
     activating this feature. Please note that once this cleanup 
     routine begins, it must run until the program terminates.""" 
     while True: 
      time.sleep(self.__sleep_interval) 
      with self: 
       for key in tuple(self): 
        if not super().__getitem__(key): 
         del self[key] 

    def __setitem__(self, key, value): 
     """Add manager attribute to value before storing it.""" 
     value.manager = self 
     super().__setitem__(key, value) 

    def __getitem__(self, key): 
     """Retrieve the session specified by the given key. 

     Like a normal dictionary, the value is returned to the caller 
     if it was found. However, the wakeup method on the session is 
     called first. This effectively delays the session's deletion.""" 
     session = super().__getitem__(key) 
     session.wakeup() 
     return session 

    def __hash__(self): 
     """Compute a hash as required by Thread objects.""" 
     return id(self) 

################################################################################ 

class Session: 

    """Store session variables for a limited time period. 

    The only functionality this class directly supports is calling an event 
    handler when the instance is destroyed. Session objects given to a 
    SessionManager are automatically cleared out of memory when their "time to 
    live" is exceeded. The manager must be started for such functionality.""" 

    def __init__(self, time_to_live, on_destroyed=None): 
     """Initialize timeout setting and deletion handler.""" 
     self.__time_to_live = time_to_live 
     self.__on_destroyed = on_destroyed 
     self.wakeup() 

    def wakeup(self): 
     """Refresh the last-accessed time of this session object. 

     This method is automatically called by the class initializer. 
     Instances also get a wakeup call when retrieved from a manager.""" 
     self.__time = time.time() 

    def __bool__(self): 
     """Calculate liveliness of object for manager.""" 
     return time.time() - self.__time <= self.__time_to_live 

    def __del__(self): 
     """Call deletion event handler if present. 

     Completely optional: an on_destroyed handler may be specified 
     when the object is created. Exception handling is non-existent.""" 
     if self.__on_destroyed is not None: 
      self.__on_destroyed() 

+0

'type non lavable: 'SessionManager'': Peut-être que vous devez définir' __hash__'? – kennytm

+0

Pourquoi? Je ne comprends toujours pas qui a besoin du hachage en premier lieu. –

+0

Vous ne devriez pas multiplier-hériter des classes qui ne le supportent pas explicitement. Thread et (le non documenté) _RLock n'appellent pas les méthodes de base en coopération (avec 'super'); un objet fabriqué à partir des deux ne sera pas correctement initialisé, donc peut ne pas fonctionner comme prévu. Je ne sais pas pourquoi vous essayez d'utiliser MI ici ... la route normale serait certainement d'utiliser la composition ici pour les autres classes. – bobince

Répondre

2

Le problème vient de threading.py, et peut être reproduit plus simplement comme suit:

>>> import threading 
>>> class SessionManager(threading.Thread, threading._RLock, dict): pass 
... 
>>> s = SessionManager() 
>>> s.start() 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/threading.py", line 469, in start 
    _limbo[self] = self 
TypeError: unhashable type: 'SessionManager' 

Vous pouvez étudier threading.py pour voir doivent être hashable exactement pourquoi objets de fil, mais la solution est facile aussi: il suffit de remplacer deux autres méthodes

def __eq__(self, other): return self is other 
def __hash__(self): return hash(id(self)) 

Cela rend les instances de votre classe traitables.

+0

Merci! Je suppose que la documentation suivante est erronée alors: * Les classes définies par l'utilisateur ont les méthodes __eq __() et __hash __() par défaut; avec eux, tous les objets se comparent inégalement (sauf avec eux-mêmes) et x .__ hash __() renvoie id (x). * –

+1

Non, la documentation est correcte; Ce qu'il ne mentionne pas, cependant, c'est que la valeur par défaut '__hash__' ne fonctionne que si la valeur par défaut' __eq__' est également utilisée. Si une classe remplace '__eq__', la valeur par défaut' __hash__' déclenchera une erreur. –

1

Le diagnostic sur le problème d'Alex du problème nonobstant, je dirais fortement que vous ne devriez pas hériter de dict dans ce cas (ou en général, d'ailleurs.) Bien qu'il puisse sembler commode de sous-classer et automatiquement hériter de tout le comportement dict, dicts (et builtin types en général) sont souvent soumis à des raccourcis en interne. Par exemple, le « get » méthode ne sera pas appeler votre modification __getitem__, même quand il ne reçoit l'article: (. Et il y a de nombreux tels cas)

>>> class MyDict(dict): 
...  def __getitem__(self, key): 
...   print("in __getitem__(%r)" % (key,)) 
...   return super(MyDict, self).__getitem__(key) 
... 
>>> d = MyDict({'a': 'b', 'c': 'd'}) 
>>> d['a'] 
in __getitem__('a') 
'b' 
>>> d.get('c') 
'd' 
>>> 

De plus, l'héritage multiple impliquant builtin types nécessite que la disposition en mémoire des instances de tous les types soit compatible. Il se trouve juste que threading.Thread et threading._Rlock sont des classes Python (ce qui signifie qu'ils ont une disposition en mémoire très simple qui est compatible avec dict) mais si cela devait changer dans le futur, ou si vous vouliez inclure d'autres types, cela échouerait .

C'est vraiment une mauvaise idée.