【问题标题】:Polymorphism with callables in PythonPython中可调用的多态性
【发布时间】:2012-02-27 03:11:21
【问题描述】:

我有一个名为 iResource 的接口类和许多子类,每个子类都实现“请求”方法。请求函数对其他机器使用套接字 I/O,因此异步运行它们是有意义的,这样其他机器可以并行工作。

问题是当我用 iResource.request 启动一个线程并给它一个子类作为第一个参数时,它会调用超类方法。如果我尝试以“type(a).request”和“a”作为第一个参数开始它,我会得到“”作为 type(a) 的值。任何想法这意味着什么以及如何获得该方法的真实类型?我能以某种方式在 Python 中正式声明一个抽象方法吗?

编辑:包括代码。

def getSocialResults(self, query=''):
    #for a in self.types["social"]: print type(a)
    tasks = [type(a).request for a in self.types["social"]]
    argss = [(a, query, 0) for a in self.types["social"]]

    grabbers = executeChainResults(tasks, argss)

    return igrabber.cycleGrabber(grabbers)

"executeChainResults" 接受一个可调用的“tasks”列表和一个 args-tuples 的列表“argss”,并假设每个都返回一个列表。然后它在一个单独的线程中执行每个,并连接结果列表。如有必要,我可以发布该代码,但我没有遇到任何问题,所以我暂时将其省略。

对象“a”绝对不是 iResource 类型,因为它只有一个构造函数,它只是抛出一个异常。但是,用“iResource.request”替换“type(a).request”会调用基类方法。此外,直接调用 "self.types["social"][0].request" 可以正常工作,但上面的代码给了我:"type object 'instance' has no attribute 'request'"。

取消注释注释行会打印<type 'instance'> 多次。

【问题讨论】:

  • 你能显示一些代码吗?从我的脑海中,我会说这不应该发生。
  • 你需要了解 Duck Typing 的概念
  • 请不管你做什么,从object开始继承你的类——这是从Python 2.2开始的正确做法

标签: python instance callable


【解决方案1】:

你可以只使用绑定的方法对象本身:

tasks = [a.request for a in self.types["social"]]
#        ^^^^^^^^^
grabbers = executeChainResults(tasks, [(query, 0)] * len(tasks))
#                                     ^^^^^^^^^^^^^^^^^^^^^^^^^

如果你坚持通过基类调用你的方法,你也可以这样做:

from abc import ABCMeta
from functools import wraps

def virtualmethod(method):
    method.__isabstractmethod__ = True
    @wraps(method)
    def wrapper(self, *args, **kwargs):
        return getattr(self, method.__name__)(*args, **kwargs)
    return wrapper

class IBase(object):
    __metaclass__ = ABCMeta
    @virtualmethod
    def my_method(self, x, y):
        pass

class AddImpl(IBase):
    def my_method(self, x, y):
        return x + y

class MulImpl(IBase):
    def my_method(self, x, y):
        return x * y

items = [AddImpl(), MulImpl()]

for each in items:
    print IBase.my_method(each, 3, 4)

b = IBase()  #  <-- crash

结果:

7
12
Traceback (most recent call last):
  File "testvirtual.py", line 30, in <module>
    b = IBase()
TypeError: Can't instantiate abstract class IBase with abstract methods my_method

Python 不支持接口,例如Java 可以。但是使用abc 模块,您可以确保某些方法必须在子类中实现。通常你会用abc.abstractmethod() 装饰器来做这件事,但你仍然不能像你想要的那样通过基类调用子类方法。我曾经有过类似的问题,我想到了 virtualmethod() 装饰器。这很简单。它本质上与abc.abstratmethod() 做同样的事情,但也将调用重定向到子类方法。 abc 模块的细节可以在the docsPEP3119 中找到。

顺便说一句:我假设您使用的是 Python >= 2.6。

【讨论】:

    【解决方案2】:

    当您在 Python 中使用“旧样式类”时获得的对“&lt;type "instance" &gt;”的引用 - 即:不是从“对象”类型层次结构派生的类。旧样式类不应该与该语言的几个 新特性一起使用,包括描述符和其他特性。并且,除其他外,您无法使用您正在执行的操作从旧样式类的类中检索属性(或方法):

    >>> class C(object):
    ...    def c(self): pass
    ... 
    >>> type (c)
    <class '__main__.C'>
    >>> c = C()
    >>> type(c).c
    <unbound method C.c>
    >>> class D: #not inheriting from object: old style class
    ...   def d(self): pass
    ... 
    >>> d = D()
    >>> type(d).d
    >>> type(d)
    <type 'instance'>
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: type object 'instance' has no attribute 'd'
    >>> 
    

    因此,只需让您的基类继承自“object”而不是“nothing”,并检查在从 type(a) 请求“request”方法时是否仍然收到错误消息:

    至于您的其他观察: “问题是当我用 iResource.request 启动一个线程并给它一个子类作为第一个参数时,它会调用超类方法。”

    看来它应该做的“正确”的事情就是:

    >>> class A(object):
    ...   def b(self):
    ...     print "super"
    ... 
    >>> class B(A):
    ...   def b(self):
    ...     print "child"
    ... 
    >>> b = B()
    >>> A.b(b)
    super
    >>> 
    

    在这里,我调用“A”类中的一个方法,给它一个“A”的特殊实例——该方法仍然是“A”类中的方法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-10-13
      • 2011-09-15
      • 1970-01-01
      • 1970-01-01
      • 2013-02-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多