如何将类型的 `__init__` 或 `__new__` 绑定到其他 class 实例?

How to bind a type's `__init__` or `__new__` to some other class instance?

我试图使用 Python 内置 setattr 将类型属性设置为 class。我已经在类型中声明了 __new____init__ 方法以查看它们的参数是什么,令人惊讶的是它们没有被绑定接收 class 实例。我已经阅读了关于 setattr 和描述符的 Python 文档,并且在解释器中执行了一些测试,但我还没有找到绑定类型的 __new__ 或 [=16] 的方法=] 方法到 class 实例。

这是我一直在玩弄的代码片段:

T = type("T", (object,), {"__new__": lambda cls: print(f"T.__new__: {cls}") or object.__new__(cls), "__init__": lambda self: print(f"T.__init__: {self}")})

T()
# T.__new__: <class '__main__.T'>
# T.__init__: <__main__.T object at 0x7f970b667c10>
# <__main__.T object at 0x7f970b667c10>

class A:
    pass

setattr(A, "T", T)

A.T()
# T.__new__: <class '__main__.T'>
# T.__init__: <__main__.T object at 0x7f970b6675e0>
# <__main__.T object at 0x7f970b6675e0>

A().T()
# T.__new__: <class '__main__.T'>
# T.__init__: <__main__.T object at 0x7f970b667ee0>
# <__main__.T object at 0x7f970b667ee0> 

本质上我想知道如何让 T 在 __new____init__ 方法中接收 A 的实例。我相信我没有完全理解 setattr 的实际工作原理,我在滥用它或者实现此行为的方式与 setattr 完全无关。

PS.: 将 T 声明为常规 class 没有任何改变,声明 T::__get__ 也没有任何改变。

setattr 函数只是一种以编程方式执行您可以通过普通赋值执行的操作的方法。您的调用 setattr(A, "T", T) 与调用 A.T = T 完全相同。它不会帮助您实现您似乎想要的东西,这是为了 class TA 中查找时具有绑定行为(或者可能在 A实例)。

虽然您可以创建一个元class,使T类型声明为A[=62]的属性=] 一个描述符,一个更简单的方法可能是写一个 A 的方法, returns 一个 T class 的实例,而不是 class本身。

class T:
    def __init__(self, a):
        self.a = a

class A:
    def make_T(self):
        return T(self)

现在您可以执行 A().make_T() 并且您将获得一个 T 实例,该实例作为参数传递给 A 实例,作为其 __init__ 方法的参数。如果您愿意,您甚至可以将 make_T 重命名为 T,它 大部分 将像您预期的嵌套 class 一样工作。它并不完全相同,因为您不能在其他情况下将 A.T 用作 class,例如 isinstance 检查。使用像 make_T 这样的名称更清楚一点,它是一个工厂方法,而不是 class 本身。

如果你真的需要把 class T 放在 A 里面,这里是 metaclass 方法:

class BindingInnerClass(type):
    def __get__(cls, obj, owner=None):
        if obj is None:
            return cls

        class BoundSubclass(cls):
            if cls.__new__ is not object.__new__:
                def __new__(subcls, *args, **kwargs):
                    return super().__new__(subcls, obj, *args, **kwargs)

            if cls.__init__ is not object.__init__:
                def __init__(self, *args, **kwargs):
                    super().__init__(obj, *args, **kwargs)

        setattr(obj, cls.__name__,  BoundSubclass)
        return BoundSubclass

class A:
    class T(metaclass=BindingInnerClass):
        def __init__(self, a):  # the metaclass will also work if you define __new__
            self.a = a

a = A()
t = a.T()
print(a, t.a) # prints the same object twice
print(isinstance(t, a.T), isinstance(t, A.T), isinstance(t, A().T)) # True True False

元class 比代码要复杂和微妙得多,如果您想能够阅读和维护它的话。它为您查看原始 class 的每个 A 实例创建一个 T 的子 class。在某些情况下(比如示例代码中最后 isinstance 检查),这可能会让人非常困惑!

这里有一些微妙之处:我们需要选择创建 __init____new__ 中的哪一个,因为如果我们无条件地创建两者,如果 T 没有将它们都定义为将 obj 作为位置参数。使用天真的绑定(方法的方式),你最终会得到可能无限数量的 classes,因为你会为每次查找创建一个新的 subclass (a.T每次都会是不同的 class)。为避免这种情况,我使用 setattr 缓存 subclasses(让我们回到原点!)。

我强烈建议不要将这种设计用于任何严肃的代码。这种 class 架构真的很糟糕,试图强制 Python 进入一种更自然地适合某些其他编程语言的设计,其中内部 classes 是正常的事情。几乎可以肯定,以这种方式设计 classes 是不必要的,可能会有一个稍微修改的设计,对 Python 的 class 模型来说更自然。帮自己(以及将来需要阅读您的代码的任何人)一个巨大的帮助,找出更好的设计是什么,而不是使用一个很难理解或修改的 metaclass 怪物。