我可以替换 Python 中对象的现有方法吗? [复制]

Posted

技术标签:

【中文标题】我可以替换 Python 中对象的现有方法吗? [复制]【英文标题】:Can I replace an existing method of an object in Python? [duplicate] 【发布时间】:2019-02-16 22:37:43 【问题描述】:

问:有没有办法改变 Python (3.6) 中现有对象的方法? (“方法”是指将self 作为参数传递的函数。)


示例

假设我有一个类Person 有一些非常有用的方法SayHi()

class Person(object):
    Cash = 100
    def HasGoodMood(self):
        return self.Cash > 10

    def SayHi(self):
        if self.HasGoodMood():
            print('Hello!')
        else:
            print('Hmpf.')

>>> joe = Person()
>>> joe.SayHi()
Hello!

如您所见,此人的反应取决于他们当前的情绪,通过方法HasGoodMood() 计算得出。一个默认的人只要身上有超过 10 美元的现金就会心情愉快。

我可以轻松打造一个不在乎钱,时时刻刻快乐的人:

>>> joe.HasGoodMood = lambda: True
>>> joe.SayHi()
Hello!
>>> joe.Cash = 0
>>> joe.SayHi()
Hello!

酷。请注意 Python 是如何知道在使用 HasGoodMood 的原始实现时,它会静默传递 self 作为第一个参数,但如果我将其更改为 lambda: True,它会调用不带参数的函数。问题是:如果我想将默认的HasGoodMood 更改为另一个也接受self 作为参数的函数怎么办?

让我们继续我们的例子:如果我想创建一个贪婪的Person,他只有在他们拥有超过 100 美元时才会高兴?我想做类似的事情:

>>> greedy_jack = Person()
>>> greedy_jack.HasGoodMood = lambda self: self.Cash > 100
TypeError: <lambda>() missing 1 required positional argument: 'self'

很遗憾,这不起作用。还有其他方法可以改变方法吗?


免责声明:以上示例仅用于演示目的。我知道我可以使用继承或保留现金门槛作为Person 的属性。但这不是问题的重点。

【问题讨论】:

@Arne 说 dupe 已经过时了,它仅指 Python 2。 @DanielRoseman 在 3 中不再起作用了吗? @Arne 那里有一些与 Python 3 相关的信息,但并不明显。事实上,很难判断这些答案中的大多数是在谈论旧式还是新式类。 据我所知,Arne 的骗局中的前 3 个答案确实适用于 python 3。我认为该骗局目标没有任何问题。 @Aran-Fey 够公平的。 Aaron 的答案是在考虑 Python 3 的情况下编写的。 【参考方案1】:

当你在一个类中写一个def,然后在一个实例上调用它,这就是一个方法,当你调用它时,方法调用的机制会填充self参数。

通过在您的实例中分配HasGoodMood,您不是在其中放置新方法,而是将函数放入属性中。您可以读取属性以获取函数并调用它,虽然这看起来像一个方法调用,但它只是调用一个恰好存储在属性中的函数。您不会获得自动提供的 self 参数。

但您已经知道self 将是什么,因为您将此函数分配给一个特定对象。

greedy_jack.HasGoodMood = (lambda self=greedy_jack: self.Cash > 100)

这将函数参数self 与变量greedy_jack 的当前值相关联。

Python 中的 Lambda 只能是一行。如果您需要更长的函数,可以改用def

def greedy_jack_HasGoodMood(self=greedy_jack):
    return self.Cash > 100

greedy_jack.HasGoodMood = greedy_jack_HasGoodMood

如需更简单的解决方案,请参阅Andrew McDowell's answer。

【讨论】:

如果您能说一下描述符和方法绑定,以解释 OP 的实际问题是什么,那就太好了。但是,虽然我不是您的创可贴解决方案的忠实拥护者,但我认为它不值得投反对票。【参考方案2】:

继承才是王道。

它可以很简单:

class Person(object):
    Cash = 100
    def HasGoodMood(self):
        return self.Cash > 10

    def SayHi(self):
        if self.HasGoodMood():
            print('Hello!')
        else:
            print('Hmpf.')


class newPersonObject(Person):
    def HasGoodMood(self):
        return self.Cash > 100

>>> greedy = newClassPerson()
>>> greedy.SayHi()
hmpf

当您执行greedy_jack.HasGoodMood = lambda self: self.Cash &gt; 100 时,您在某种程度上是在做同样的事情。您只是覆盖 greedy_jacks 属性。使用上面提到的方式,你可以创造贪婪的人、快乐的人、永远不快乐的人、嬉皮士等。

我认为更好的选择是在定义对象时接受现金参数。因此,您可以动态地使人们变得贪婪或正常。 (未测试)

class Person(object):
    def __init__(self, cash_to_be_happy):
        self.cash = cash_to_be_happy

    def HasGoodMood(self, has_money):
        return has_money > self.cash

    def SayHi(self, has_money):
        if self.HasGoodMood(has_money):
            print('Hello!')
        else:
            print('Hmpf.')

>>> joe = Person(100)
>>> joe.SayHi(150)
Hello!
>>> greedy_joe = Person(200)
>>> greedy_joe.SayHi(150)
Hmpf

【讨论】:

查看免责声明:对于真实场景,这些是正确且绝对可取的解决方案,但是,问题并不是特别要求它们。【参考方案3】:

使用以下提示:

Is it possible to change an instance's method implementation without changing all other instances of the same class?

您可以执行以下操作,方法是使用类型模块将方法分配给创建的对象而不影响类。您需要这样做,因为函数不会自动接收 self 对象作为第一个变量,但方法会。

import types

joe = Person()
bob = Person()

joe.SayHi()
>>> Hello!

def greedy_has_good_mood(self):
    return self.Cash > 100

joe.HasGoodMood = types.MethodType(greedy_has_good_mood, joe)


joe.SayHi()
>>> Hmpf.
bob.SayHi()

>>> Hello!

【讨论】:

以上是关于我可以替换 Python 中对象的现有方法吗? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

您可以在现有对象上保存新对象吗?

是否可以在不子类化的情况下替换 UIWebView 中现有方法的主体

数据框中现有值的 Python 条件 NaN 值替换

使用 Linq to XML 创建基于类/模型的现有对象 - 工作,但有更好的方法吗?

可以使用 ASM 向现有 Java 类添加方法吗?

SQLite 替换现有列中的数据