【问题标题】:Is it possible to change an instance's method implementation without changing all other instances of the same class? [duplicate]是否可以在不更改同一类的所有其他实例的情况下更改实例的方法实现? [复制]
【发布时间】:2010-12-11 11:31:36
【问题描述】:

我不太了解python(以前从未使用过它:D),但我似乎无法在网上找到任何东西。也许我只是没有用谷歌搜索正确的问题,但我走了:

我想更改实例对特定方法的实现。当我搜索它时,我发现你可以做到,但它改变了同一类的所有其他实例的实现,例如:

def showyImp(self):
    print self.y

class Foo:
    def __init__(self):
        self.x = "x = 25"
        self.y = "y = 4"

    def showx(self):
        print self.x

    def showy(self):
         print "y = woohoo"

class Bar:
    def __init__(self):
        Foo.showy = showyImp
        self.foo = Foo()

    def show(self):
        self.foo.showx()
        self.foo.showy()

if __name__ == '__main__':
    b = Bar()
    b.show()
    f = Foo()
    f.showx()
    f.showy()

这没有按预期工作,因为输出如下:

x = 25

y = 4

x = 25

y = 4

我希望它是:

x = 25

y = 4

x = 25

y = 呜呜

我试图用这个来改变 Bar 的 init 方法:

def __init__(self):
    self.foo = Foo()
    self.foo.showy = showyImp

但我收到以下错误消息:

showyImp() 只接受 1 个参数(给定 0)

是的...我尝试使用setattr(),但似乎与self.foo.showy = showyImp 相同。

有什么线索吗? :)

【问题讨论】:

  • 您不使用继承进行此操作是否有特殊原因?
  • 是的,因为我对创建的对象的访问权限非常有限。我不是创建它的人(因此不可能继承),并且我无法修改原始对象的代码(也因为无论如何它都没有意义,我只需要在完全不同的上下文中更改方法从对象最初的用途:)。

标签: python


【解决方案1】:

从 Python 2.6 开始,您应该使用 types 模块的 MethodType 类:

from types import MethodType

class A(object):
    def m(self):
        print 'aaa'

a = A()

def new_m(self):
    print 'bbb'

a.m = MethodType(new_m, a)

然而,正如另一个答案所指出的,这不适用于新型类的“魔术”方法,例如__str__()

【讨论】:

  • 谢谢!接受的答案在 Python 3 中不起作用,文档页面也没有很清楚地说明如何使用 MethodType
  • 这没什么大不了的,我猜(虽然如果你知道如何解决这个问题,那么我会很感激帮助),但是......这会覆盖实例化对象的方法,而不是类本身...因此,当创建类的新实例时,您必须运行此代码来更改方法,而不是在物理类创建器中更改方法。
【解决方案2】:

这个答案已经过时了; the answer below 适用于现代 Python

你想知道的关于Python Attributes and Methods的一切。

是的,这是一个间接的答案,但它展示了许多技术并解释了一些更复杂的细节和“魔法”。

要获得“更直接”的答案,请考虑python's new module。特别是,查看允许将方法“绑定”到实例的 instancemethod 函数——在这种情况下,这将允许您在方法中使用“self”。

import new
class Z(object):
  pass
z = Z() 
def method(self):
  return self
z.q = new.instancemethod(method, z, None)
z is z.q()  # true

【讨论】:

  • 非常感谢,正是我正在寻找的东西,并且像魅力一样工作:)
  • 这有 Python 3 等价物吗?
  • z.q = new.instancemethod(method, z, None) 的 python 3 等效项是 z.q = types.MethodType(method, z)。记得导入types 而不是new
  • 链接已损坏。
【解决方案3】:

如果您需要为 special 方法执行此操作(对于新样式的类 - 这是您应该始终使用的并且是 Python 3 中唯一的类型 - 是查找类,而不是实例),您可以只创建一个每个实例的类,例如...:

self.foo = Foo()
meths = {'__str__': lambda self: 'peekaboo!'}
self.foo.__class__ = type('yFoo', (Foo,), meths)

编辑:有人要求我在 new.instancemethod... 中阐明这种方法的优势...:

>>> class X(object): 
...   def __str__(self): return 'baah'
... 
>>> x=X()
>>> y=X()
>>> print x, y
baah baah
>>> x.__str__ = new.instancemethod(lambda self: 'boo!', x)
>>> print x, y
baah baah

如您所见,new.instance 方法在这种情况下完全没用。哦...:

>>> x.__class__=type('X',(X,),{'__str__':lambda self:'boo!'})
>>> print x, y
boo! baah

...分配一个新班级对于这种情况和其他情况都非常有用。顺便说一句,我希望很清楚,一旦您对给定实例执行此操作,您就可以稍后将更多方法和其他类属性添加到其 x.__class__ 并且本质上只影响那个实例!

【讨论】:

  • 与使用new. instancemethod相比,这种方法有什么优势?
  • 后一种方法只是不适用于新式类中的特殊方法,正如我所说,而这种方法是通用的。让我编辑答案以显示一个示例。
  • 啊,我明白了!非常感谢您的信息。这很有趣:)。
  • 一些考古学!...那么这种方法在 python 3 中仍然有效吗?
【解决方案4】:

如果您要绑定到实例,则不应包含 self 参数:

>>> class Foo(object):
...     pass
... 
>>> def donothing():
...     pass
... 
>>> f = Foo()
>>> f.x = donothing
>>> f.x()
>>> 

如果你绑定到一个类,你确实需要 self 参数:

>>> def class_donothing(self):
...     pass
... 
>>> foo.y = class_donothing
>>> f.y()
>>> 

【讨论】:

  • 我需要绑定到实例,我需要访问“self”,并且我无法修改该方法的现有调用过程,这就是为什么我不能这样做self.foo.showy(self.foo)
  • 不管怎样,这个答案对我很有帮助:)
【解决方案5】:

你的例子有点扭曲和复杂,我不太明白它与你的问题有什么关系。如果您愿意,请随时澄清。

但是,假设我没看错你的问题,那么做你想做的事情就很容易了。

class Foo(object):
    def bar(self):
        print('bar')

def baz():
    print('baz')

在解释器中 ...

>>> f = Foo()
>>> f.bar()
bar
>>> f.bar = baz
>>> f.bar()
baz
>>> g = Foo()
>>> g.bar()
bar
>>> f.bar()
baz

【讨论】:

  • 这个解决方案的问题是它没有使用self(作为参数),我确实需要,而且我不能做f.bar(f),主要是因为我没有'不想(它看起来很像反模式)。
【解决方案6】:

不要这样做。

改变一个实例的方法是错误的。

这是面向对象设计的规则。

  1. 避免魔法。

  2. 如果不能使用继承,请使用委托。

这意味着每次你认为你需要一些神奇的东西时,你应该在对象周围编写一个“包装器”或 Facade 来添加你想要的功能。

只写一个包装器。

【讨论】:

  • @naixn:这是你的问题。你拥有这个问题。您可以更新问题。如果您更新问题以包含所有事实,则不必求助于带外消息。请使用根本缺陷设计的详细理由更新问题。您的解释相当于“我不想包装和委托”。 (1) 请更新您的问题,(2) 请重新考虑您有缺陷的设计。
  • 如果您需要第三方库的行为略有不同怎么办?在我的特殊情况下,我需要一个特定的Logger 实例来将一些数据附加到库发送到记录器的info() 方法的每条日志消息中。是否可以为此使用包装和委托?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-06-27
  • 1970-01-01
  • 2023-01-01
  • 2015-04-03
相关资源
最近更新 更多