Python:将方法从一个类分配给另一个类的实例

时间:2012-09-27 18:47:48

标签: python

我想使用new.instancemethod将函数(aFunction)从一个类(A)分配给另一个类(B)的实例。我不知道如何让aFunction允许自己应用于B的实例 - 目前我收到一个错误,因为aFunction期望在A的实例上执行。

[注意:我不能使用__class__属性将实例强制转换为A,因为我正在使用的类(在我的示例中为B)不支持类型转换]

import new

class A(object):
    def aFunction(self):
        pass

#Class which doesn't support typecasting
class B(object): 
    pass

b = B()
b.aFunction = new.instancemethod(A.aFunction, b, B.__class__)
b.aFunction()

这是错误:

TypeError: unbound method aFunction() must be called with A instance as first argument (got B instance instead)

5 个答案:

答案 0 :(得分:6)

new.instancemethod需要一个功能。 A.aFunction是一种未绑定的方法。那不是一回事。 (您可能想尝试添加一堆打印语句来显示所涉及的所有内容 - A.aFunction()A().aFunction等 - 及其类型,以帮助理解。)

我假设你没有descriptors的工作方式。如果你不想学习所有的血腥细节,那么实际发生的是你在类定义中声明aFunction会从函数中创建一个非数据描述符。这是一个神奇的事情,这意味着,根据您访问它的方式,您将获得未绑定的方法(A.aFunction)或绑定的方法(A().aFunction)。

如果您将aFunction修改为@staticmethod,这实际上会有效,因为对于静态方法,A.aFunctionA().aFunction都只是函数。 (我不确定标准会保证这是真的,但是很难看出其他人会如何实现它。)但如果你想要“aFunction允许自己应用于B的实例”,那么静态方法不会帮助你。

如果你真的想要获得底层函数,有很多方法可以做到;我认为这是帮助您理解描述符如何工作的最明确的:

f = object.__getattribute__(A, 'aFunction')

另一方面,最简单的可能是:

f = A.aFunction.im_func

然后,调用new.instancemethod是如何将该函数转换为可以作为B类实例的常规方法调用的描述符:

b.aFunction = new.instancemethod(f, b, B)

打印出大量数据会让事情变得更加清晰:

import new

class A(object):
  def aFunction(self):
    print self, type(self), type(A)

#Class which doesn't support typecasting
class B(object): 
    pass

print A.aFunction, type(A.aFunction)
print A().aFunction, type(A().aFunction)
print A.aFunction.im_func, type(A.aFunction.im_func)
print A().aFunction.im_func, type(A().aFunction.im_func)

A.aFunction(A())
A().aFunction()

f = object.__getattribute__(A, 'aFunction')
b = B()
b.aFunction = new.instancemethod(f, b, B)
b.aFunction()

你会看到这样的事情:

<unbound method A.aFunction> <type 'instancemethod'>
<bound method A.aFunction of <__main__.A object at 0x108b82d10>> <type 'instancemethod'>
<function aFunction at 0x108b62a28> <type 'function'>
<function aFunction at 0x108b62a28> <type 'function'>
<__main__.A object at 0x108b82d10> <class '__main__.A'> <type 'type'>
<__main__.A object at 0x108b82d10> <class '__main__.A'> <type 'type'>
<__main__.B object at 0x108b82d10> <class '__main__.B'> <type 'type'>

唯一没有显示的是创建绑定和未绑定方法的魔力。为此,你需要研究A.aFunction.im_func.__get__,那时你最好还是阅读描述符,而不是试图自己分开。

最后一件事:正如brandizzi指出的那样,这是你几乎不想做的事情。替代方案包括:将aFunction写为自由函数而不是方法,因此您只需调用b.aFunction();将实际工作的功能分解出去,让A.aFunctionB.aFunction调用它;让B聚合A并转发给它;等

答案 1 :(得分:1)

我已经回答了你问的问题。但是你不应该达到理解new.instancemethod来解决问题的程度。发生这种情况的唯一原因是因为你提出了错误的问题。让我们来看看应该提出的问题以及原因。

  

在PySide.QtGui中,我希望列表小部件项目具有设置字体和颜色的方法,但它们似乎没有。

这就是你真正想要的。可能有一种简单的方法可以做到这一点。如果是这样,那就是你想要的答案。此外,从这开始,您将避免所有关于“您真正想做什么?”的评论。或者“我怀疑这适用于你想要做的任何事情”(这通常伴随着投票 - 或者更重要的是,潜在的回答者只是忽略了你的问题)。

  

当然我可以编写一个接受QListWidgetItem并调用该函数的函数,而不是将其作为方法,但这对我不起作用,因为 _ _。

我认为有一个理由对你不起作用。但我真的不能想到一个好的。无论代码如何说:

item.setColor(color)

反而会这样说:

setItemColor(item, color)

很简单。

即使您需要(例如,使用绑定到其中的项目来传递颜色设置委托),使用方法的功能也几乎一样容易。而不是:

delegate = item.setColor

它是:

delegate = lambda color: setItemColor(item, color)

所以,如果 是一个很好的理由,你需要将它作为一种方法,这是你真正应该解释的。如果你很幸运,那么事实证明你错了,并且有一种更简单的方式来做你想要的而不是你想要的。

  

显而易见的方法是让PySide让我指定一个类或工厂函数,这样我就可以编写一个QListWidgetItem子类,而我处理过的每个列表项都是该子类的一个实例。但我找不到任何办法。

这似乎应该是PySide的一个功能。所以也许是,在这种情况下你想知道。如果不是,并且你或任何评论者或回答者都没有想到一个很好的理由,那就是糟糕的设计或难以实现,你应该提交一个功能请求,它可能在下一个版本中。 (如果您需要在下周针对当前版本发布代码,这不会对您有所帮助,但它仍然值得做。)

  

由于我找不到这样做的方法,我试图找到一些方法将setColor方法添加到QListWidgetItem类,但无法想到任何内容。

Monkey-patching类非常简单:

QListWidgetItem.setColor = setItemColor

如果你不知道你能做到这一点,那没关系。如果人们知道你正在尝试做的事情,这将是他们建议的第一件事。 (好吧,也许没有多少Python程序员对猴子修补有很多了解,但它仍然比知道描述符或new.instancemethod的数字要多得多。)而且,除了作为答案之外你还会更快,并且不那么麻烦,这是一个更好的答案。

即使您确实知道这一点,一些扩展模块也不会让您这样做。如果您尝试过但失败了,请解释一下不起作用的内容:

  

PySide不允许我将该方法添加到类中;当我尝试猴子修补时, _ _。

也许有人知道为什么它不起作用。如果没有,至少他们知道你试过了。

  

所以我必须将它添加到每个实例中。

Monkey-patching实例如下所示:

myListWidgetItem.setColor = setItemColor

再次,你会得到一个快速回答,一个更好的回答。

但是也许你知道这一点,并尝试过它,它也不起作用:

  

PySide也不允许我将方法添加到每个实例;当我尝试时, _ _。

     

所以我尝试修补每个实例的__class__,使其指向自定义子类,因为它在PyQt4中有效,但在PySide中它 _ _。

这可能不会让你有用,因为它只是PySide的工作方式。但值得一提的是你尝试过。

  

所以,我决定创建那个自定义子类,然后复制它的方法,就像这样,但 _ _。

在这里,一直到你把所有的东西放在你原来的问题中。如果确实有必要解决您的问题,那么信息就会存在。但是如果有一个简单的解决方案,你就不会从那些只是猜测new.instancemethod如何工作的人那里得到一堆混乱的答案。

答案 2 :(得分:0)

如果可能,您可以使用aFunction装饰器使@staticmethod无约束: -

class A(object):
    @staticmethod
    def aFunction(B):    # Modified to take appropriate parameter..
        pass

class B(object): 
    pass

b = B()
b.aFunction = new.instancemethod(A.aFunction, b, B.__class__)
b.aFunction()

*注意: - 您需要修改方法以采取适当的参数..

答案 3 :(得分:0)

令我惊讶的是,这些答案都没有给出看似最简单的解决方案,特别是在您希望现有实例将方法 x 替换为来自另一个类(例如子类)的方法 x(同名)的情况下,“嫁接在它上面。

我在完全相同的上下文中遇到了这个问题,即 PyQt5。具体来说,使用 QTreeView 类,问题是我已经为树结构(第一列)中涉及的所有树项子类化了 QStandardItem(称之为 MyItem),等等事情,添加或删除这样的项目涉及一些额外的东西,特别是向字典添加键或从字典中删除键。

覆盖 appendRow(以及 removeRowinsertRowtakeRow 等)没有问题:

class MyItem( QStandardItem ):
    ...

    def appendRow( self, arg ):
        # NB QStandarItem.appendRow can be called either with an individual item 
        # as "arg", or with a list of items 
        if isinstance( arg, list ):
            for element in arg:
                assert isinstance( element, MyItem )
                self._on_adding_new_item( element )
        elif isinstance( arg, MyItem ):
            self._on_adding_new_item( arg )
        else:
            raise Exception( f'arg {arg}, type {type(arg)}')
        super().appendRow( self, arg )

... 其中 _on_adding_new_item 是一种填充字典的方法。

当您想要添加“0 级”行时会出现问题,即其父行是 QTreeView 的“不可见根”。自然地,您希望这个新的“0 级”行中的项目为每个项目创建一个字典条目,但是如何获得这个不可见的根项目,类 QStandardItem 来做到这一点?

我尝试覆盖模型的方法 invisibleRootItem() 以提供不是 super().invisibleRootItem(),而是一个新的 MyItem。这似乎不起作用,可能是因为 QStandardItemModel.invisibleRootItem() 在幕后做事,例如设置项目的 model() 方法以返回 QStandardItemModel

解决方案非常简单:

class MyModel( QStandardItemModel ):
    def __init__( self ):
        self.root_item = None
        ...

    ...

    def invisibleRootItem( self ):
        # if this method has already been called we don't need to do our modification
        if self.root_item != None:
            return self.root_item 

        self.root_item = super().invisibleRootItem()
        # now "graft" the method from MyItem class on to the root item instance
        def append_row( row ):
            MyItem.appendRow( self.root_item, row  )
        self.root_item.appendRow = append_row

... 然而,这还不够:super().appendRow( self, arg ) 中的 MyItem.appendRow 然后在根项目上调用时会引发异常,因为 QStandardItem 没有 {{1} }.相反,super() 更改为:

MyItem.appendRow

答案 4 :(得分:-1)

感谢Rohit Jain - 这就是答案:

import new

class A(object):
    @staticmethod
    def aFunction(self):
        pass

#Class which doesn't support typecasting
class B(object): 
    pass

b = B()
b.aFunction = new.instancemethod(A.aFunction, b, B.__class__)
b.aFunction()