【问题标题】:__self__ of a new class method still refers to the previous class?一个新的类方法的__self__ 仍然引用以前的类?
【发布时间】:2021-02-16 08:52:47
【问题描述】:

我想做的事情看起来很奇怪,简而言之,我正在尝试构建一个新类并从另一个类复制实例/静态/类方法而不进行继承(即,我只想要一个只有几个方法的类) .当我在网上查找时,似乎人们在建议我在插图中所做的(新类),但当我尝试测试它时,新类中声明的类方法似乎仍然指向前一个类(旧类)。

这就是我所做的:


class OldClass(object):
    current_count = 1

    def __init__(self):
        pass

    @classmethod
    def method_class(cls):
        cls.current_count += 1

    def method_normal(self):
        return 2


# new class
class NewClass(object):
    method_class_copy = OldClass.method_class
    method_normal_copy = OldClass.method_normal

检查 __self__ 给出以下结果:

class_self = getattr(NewClass.method_class_copy, '__self__', None)
print(class_self)

它返回:

<class '__main__.OldClass'>

但是在实例化后检查实例方法:

instance_self = getattr(NewClass().method_normal_copy, '__self__', None)
print(instance_self)

如您所见,它仍然指的是 OldClass,如果我运行类方法 NewClass.method_class_copy()OldClass.current_count 将按照我定义的方式增加其值。

它返回:

<__main__.NewClass object at 0x000001D8CC1AEA88>
  • 如何使用类方法的 __self__ 构造(新)类实际上是指 NewClass 而不是 OldClass?
  • 为什么要“自动调整”实例方法?

谢谢

【问题讨论】:

  • “我想做的事情看起来很奇怪,简而言之,我正在尝试构建一个新类并从另一个类复制实例/静态/类方法而不进行继承” - 这不是一个明智的设计。如果您想在两个类之间共享一个方法实现而不让一个类继承另一个类,请将实现放在类之外的某个位置,例如在辅助函数或 mixin 中。
  • @user2357112 支持 Monica,新类将从任何给定的类对象动态创建。基本上,我想在类中使用选定的方法复制整个类,而不运行原始的初始化和创建过程。然后将新的类对象传递给其他函数以执行其他任务。
  • 请注意,复制一个类是非常重要的,即使您了解如何将类从它们的各个部分拼凑在一起。您看到的行为是描述符,它是元类的小而简单的兄弟,实际上为类提供了动力。如果你真的想复制甚至拼凑一个课程,作为一个整体提出要求。如果您只想在其他类中重用方法,请关注结果的期望行为,而不是假定的复制方法。

标签: python class inheritance types


【解决方案1】:

这是descriptor protocol 的效果以及classmethod 如何使用它:classmethod 通过被查找绑定到它的类。粗略地说,查找some_cls.some_classmethod 返回方法,其中cls 参数预填充到some_class

由于“普通”方法通过在实例上查找来绑定到它们的实例,因此在类上查找它们并没有绑定它们。

>>> class Foo:
...    def some_normalmethod(self): ...
...    @classmethod
...    def some_classmethod(cls): ...
...
>>> Foo.some_classmethod
<bound method Foo.some_classmethod of <class '__main__.Foo'>>
>>> Foo.some_normalmethod
<function __main__.Foo.some_normalmethod(self)>

因此,通过从类中获取 classmethod 来“复制”classmethod 不能按预期工作(它已经绑定到类),而“复制”普通方法确实有效(它没有绑定到实例)。


为了“复制”classmethod,提取其底层函数并从中创建一个新的classmethod

>>> bound_cm = Foo.some_classmethod  # bound classmethod
>>> base_cm = bound_cm.__func__      # function underlying classmethod
>>> class Bar:
...     some_classmethod = classmethod(base_cm)  # new classmethod of same function
...
>>> Bar.some_classmethod
<bound method Foo.some_classmethod of <class '__main__.Bar'>>

请注意,这会创建类方法的“副本”,而不是底层函数。某些元数据,例如Foo.some_classmethod 这个名字仍然指向它的起源。

如果需要原始的classmethod 对象,绕过描述符协议可以直接访问而不绑定方法。

>>> Foo.__dict__['some_classmethod']
<classmethod at 0x10b6d9d00>
>>> class Qux:
...     some_classmethod = Foo.__dict__['some_classmethod']
...
>>> Qux.some_classmethod
<bound method Foo.some_classmethod of <class '__main__.Qux'>>

【讨论】:

  • 感谢您的详细解释,这确实消除了我的一些困惑!
【解决方案2】:

只有在构建第二个类之后,您才能通过将方法定义为类方法来做您想做的事情。

class OldClass(object):
    current_count = 1

    def __init__(self):
        pass

    def method_class(cls):
        cls.current_count += 1
    
    def method_normal(self):
        return 2


# new class
class NewClass(object):
    method_class_copy = OldClass.method_class
    method_normal_copy = OldClass.method_normal


OldClass.method_class = classmethod(OldClass.method_class)
NewClass.method_class_copy = classmethod(NewClass.method_class_copy)

如果你用这个来测试

class_self = getattr(OldClass.method_class, '__self__', None)
print(class_self)
instance_self = getattr(OldClass().method_normal, '__self__', None)
print(instance_self)

print()

class_self = getattr(NewClass.method_class_copy, '__self__', None)
print(class_self)
instance_self = getattr(NewClass().method_normal_copy, '__self__', None)
print(instance_self)

你得到

<class '__main__.OldClass'>
<__main__.OldClass object at 0x7f1adc174ac0>

<class '__main__.NewClass'>
<__main__.NewClass object at 0x7f1adc138c70>

这就是你想要的。

编辑:您也可以动态执行此操作(在OldClass 中使用@classmethod),首先将OldClass 的所有类方法设为静态staticmethod(),然后创建@ 987654328@,然后拨打classmethod()

【讨论】:

  • 感谢您的帮助!我会按照使用classmethod()的线索,看看它是否能解决我的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-01-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多