【问题标题】:Dynamically bind an instance method to class (not to class instance) in Python在 Python 中将实例方法动态绑定到类(而不是类实例)
【发布时间】:2020-07-17 13:54:31
【问题描述】:

描述和我的尝试:

我在 stackoverflow 中看到了很多关于将方法绑定到类实例的帖子(我知道已经有很多重复项了)。

但是我还没有找到关于将方法绑定到类本身的讨论。我可以想到解决方法,但我很好奇是否有一种简单的方法可以实现以下目标:

import types
def quacks(some_class):
    def quack(self, number_of_quacks):
       self.number_of_quacks = number_of_quacks
    setattr(some_class, "quack", types.MethodType(quack, some_class))
    return some_class

@quacks
class Duck:
   pass

但上面的行不通:

d1 = Duck()
d2 = Duck()
d1.quack(1)
d2.quack(2)
print(d2.number_of_quacks)
# 2
print(d1.number_of_quacks)
# 2

因为 quack 实际上是在修改类本身而不是实例。

我能想到两种解决方法。如下所示:

class Duck:
    def __init__(self):
        setattr(self, "quack", types.MethodType(quack, self))

或类似的东西

class Quacks:
    def quack(self, number_of_quacks):
        self.number_of_quacks = number_of_quacks

class Duck(Quacks):
    pass

问题: 所以我的问题是,有没有一种简单的方法来实现我上面描述的简单的@quacks 类装饰器?

为什么要问: 我打算创建一组函数来模块化地将我使用的常用方法添加到类中。如果我不退出这个项目,该列表可能会随着时间的推移而增长,我希望它在代码定义上看起来不错。从口味上看,我认为下面的选项 1 看起来比选项 2 更好:

# option 1
@quacks
@walks
@has_wings
@is_white
@stuff
class Duck:
    pass

# option 2
class Duck(
  Quacks,
  Walks,
  HasWings,
  IsWhite,
  Stuff):
    pass

【问题讨论】:

标签: python python-3.x class python-decorators


【解决方案1】:

找到另一种解决方法,我想下面会为我做。

def quacks(some_class):
    def quack(self, number_of_quacks):
        self.number_of_quacks = number_of_quacks

    old__init__ = some_class.__init__
    def new__init__(self, *args, **kwargs):
        setattr(self, "quack", types.MethodType(quack, self))
        old__init__(self, *args, **kwargs)
    setattr(some_class, "__init__", new__init__)
    return some_class

如果您发现此方法有任何缺点,请随意添加任何其他替代方案。

编辑: 一种受@SethMMorton 回答启发的不那么老套的方式:


def quack(self, number_of_quacks):
    self.number_of_quacks = number_of_quacks

def add_mixin(some_class, some_fn):
    new_class = type(some_class.__name__, (some_class,), {
        some_fn.__name__: some_fn
    })
    return new_class

def quacks(some_class):
    return add_mixin(some_class, quack)

@quacks
class Duck:
    pass

d1 = Duck()
d2 = Duck()
d1.quack(1)
d2.quack(2)
print(d1.number_of_quacks)
print(d2.number_of_quacks)

【讨论】:

    【解决方案2】:

    如果您不介意完全更改所需语法以获得所需功能,您可以使用type 动态构造类(参见第二个签名)。

    第一个参数是类的名称,第二个是超类的元组,第三个是要添加的属性字典。

    Duck = type("Duck", (), {
        "quack", quack_function,
        "walk", walk_function,
        ...
    })
    

    因此,不是在创建后注入适当功能的装饰器,而是在创建时直接添加功能。这种方法的好处是您可以通过编程方式构建属性字典,而使用装饰器则不能。

    【讨论】:

    • 非常感谢您提供的替代方案,我不知道这是可能的,而且它真的很有用。它不适合这个特定的用例,因为我想要一个包,您可以导入这些装饰器并添加到您已经使用的任何现有类中(因此它也会迫使打算使用该类的每个人重新组织他们的代码完全)。但无论哪种方式,这是一个非常酷的技巧,肯定会在某个时候帮助我,所以再次感谢!
    • @ozgeneral 那么在这种情况下,我会选择您的选项 2(混合)。在链接的答案中,他们展示的示例正是您想要实现的目标。
    • 只需阅读链接,我可能会对其进行重构。但出于好奇,除了作为标准约定之外,选项 2 是否有更好的实际原因?
    • 您无需编写 hack 即可使其工作 - 它使用该语言提供的标准功能。这意味着出错的可能性较小。
    • 刚刚想到将这两种方法结合起来看起来不错,请参阅下面答案中的编辑
    猜你喜欢
    • 2012-02-17
    • 1970-01-01
    • 2012-01-14
    • 2020-03-12
    • 1970-01-01
    • 2016-06-11
    • 1970-01-01
    • 2021-09-17
    • 1970-01-01
    相关资源
    最近更新 更多