Python中的多重继承(特定问题)

1 投票
3 回答
1422 浏览
提问于 2025-04-15 18:27

这里有没有人能告诉我,为什么下面这个例子最后会出现TypeError错误呢?

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

补充说明:

接下来是上面开始的模块的完整版本。它在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 <Noctis.Skytower@gmail.com>'
__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()

3 个回答

0

在类的层面上声明以下内容,是否足够合理呢:

__hash__ = threading.Thread.__hash__
__eq__ = threading.Thread.__eq__
1

虽然Alex对问题的诊断很到位,但我还是要强烈建议你在这种情况下(或者一般情况下)不要从dict进行多重继承。虽然从dict继承看起来很方便,可以自动获得所有字典的功能,但字典(以及其他内置类型)内部常常会有一些特殊处理。例如,'get'方法不会调用你修改过的__getitem__,即使它确实获取了那个项目:

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

(这样的情况还有很多。)

另外,涉及内置类型的多重继承要求所有类型的实例在内存中的布局是兼容的。恰好threading.Threadthreading._Rlock是Python类(这意味着它们在内存中的布局非常简单,和dict是兼容的),但如果将来这种情况发生变化,或者你想加入其他类型,就会出问题。

这真的是个坏主意。

3

这个问题出现在 threading.py 文件里,可以用更简单的方式来重现这个问题,如下所示:

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

你可以研究一下 threading.py,看看为什么线程对象需要是可哈希的,但解决这个问题也很简单:只需再重写两个方法

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

这样就能让你类的实例变得可哈希了。

撰写回答