【问题标题】:Multiple Inheritance in Python (Problem Specific)Python 中的多重继承(特定问题)
【发布时间】:2011-01-09 05:15:13
【问题描述】:

任何人都可以确定为什么在下面显示的示例底部会引发 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()

【问题讨论】:

  • unhashable type: 'SessionManager': 也许你需要定义__hash__
  • 为什么?我仍然不明白谁首先需要哈希。
  • 你不应该从没有明确支持它的类中多重继承。线程和(未记录的)_RLock 不协同调用基本方法(与super);由两者制成的对象将无法正确初始化,因此可能无法按预期工作。我不知道你为什么在这里尝试使用 MI...正常的路线肯定是在这里为其他类使用组合。

标签: python multithreading multiple-inheritance typeerror


【解决方案1】:

在类级别声明以下内容是否足够且合理:

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

【讨论】:

    【解决方案2】:

    尽管 Alex 对问题进行了现场诊断,但我强烈认为,在这种情况下(或一般而言,就此而言),您不应该多次继承 dict。虽然从它继承子类似乎很方便,并且自动继承所有 dict 行为,dicts(和一般的内置类型)通常在内部受到快捷方式的影响。例如,'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))
      

      这使您的类的实例可散列。

      【讨论】:

      • 谢谢!我猜下面的文档是错误的:用户定义的类默认有 __eq__() 和 __hash__() 方法;与它们相比,所有对象都比较不相等(除了它们自己)并且 x.__hash__() 返回 id(x)。
      • 不,文档是对的;但是,它没有提到的是默认的__hash__ 仅在也使用默认的__eq__ 时才有效。如果一个类覆盖__eq__,默认的__hash__ 将引发错误。
      猜你喜欢
      • 2012-08-08
      • 2021-05-08
      • 2021-07-28
      • 1970-01-01
      • 2020-07-05
      • 1970-01-01
      • 1970-01-01
      • 2010-11-23
      相关资源
      最近更新 更多