【问题标题】:How to bind a type's `__init__` or `__new__` to some other class instance?如何将类型的 `__init__` 或 `__new__` 绑定到其他类实例?
【发布时间】:2020-11-20 23:14:57
【问题描述】:

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

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

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 声明为常规类不会改变任何事情,而声明 T::__get__ 也不会改变任何事情。

【问题讨论】:

  • 这似乎是XY problem。您在询问setattr,但您的实际目标似乎是A().T() 的绑定行为(您希望T 类绑定到它正在查找的A 实例)。是对的吗?如果您真的详细了解setattr,我不想在关于绑定行为的答案的第一段中忽略setattr
  • @Blckknght 你是对的,我会纠正的。
  • 它像任何其他对象一样工作。在其类命名空间中具有另一种类型的类型没有什么特别之处。你想要的根本不会自动发生。您可以通过定义一个作为描述符的元类来完成它,但是它将涉及关于如何/何时使用描述符的相当神秘的方面。老实说,这似乎不值得。 您要解决的实际问题是什么?您是否希望它像 Java 非静态嵌套类一样工作?
  • setattr 与此处无关。 setattr(A, "T", T)完全等同于A.T = T。此外,使用 type 构造函数并不真正相关。同样,如果您使用类定义语句,它的工作方式相同。
  • @juanpa.arrivillaga 我只想在调用a.T() 时在T 初始化中访问A 实例a,以避免a.T(a)。一旦我知道如何做到这一点,它可能就不值得了,尽管我仍然想知道如何做到这一点。目前在 Google 的首页找不到。

标签: python binding setattr


【解决方案1】:

setattr 函数只是一种以编程方式执行普通赋值操作的方法。您拨打setattr(A, "T", T) 与拨打A.T = T 完全相同。它并不能帮助您实现您想要的,即在 A(或者可能在 A 实例中)查找类 T 时具有绑定行为。

虽然您可以创建一个元类,使您声明为A 类的属性的T 类型成为描述符,但更简单的方法可能是编写@987654329 的方法@ 返回 T 类的实例,但实际上不是类本身。

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

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

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

如果您确实需要将类 T 放在 A 内,这里是元类方法:

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

如果您希望能够阅读和维护它,那么该元类比代码真正应该的要复杂和微妙得多。它为您查找原始类的每个A 实例创建T 的子类。在某些情况下,这可能会非常令人困惑(例如示例代码中的最后一个 isinstance 签入)!

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

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

【讨论】:

  • 辅助方法对我不起作用,因为我确实需要类型 T 而不仅仅是它的实例。我将尝试为此使用元类,如果我理解正确,我应该为 T 声明一个元类,使其成为描述符,是这样吗?你可以在你的答案中包含一个例子吗?
  • 嗯,我实际上是从元类方法开始的,它实际上比我预期的要复杂得多。问题是给定x = A()y = A(),您希望x.Ty.T 成为类,但在调用它们时将它们的实例绑定到两个不同的A 实例。如果它们是相同的类型,这很难做到,您可能需要它们都是T(即x.T != y.T)的独立子类。自动创建未知其他类的子类非常麻烦。我真的会质疑这种架构在 Python 中是否值得。肯定有更简单的方法。
  • 我将继续使用更简单的方法,将T 的工厂方法和T 类型本身都设置为A 的属性。更多上下文:我正在从 A 动态创建 T,虽然我想允许免费访问 T,但我想提供一种简单的方法(使用助手)来实例化 T。希望T.__init__ 可以处理帮助部分,但尝试将__get__ 属性设置为__new__ 是Python 不允许的,所以我想这种绑定不会发生,否则它是有意义的,否则会一团糟让 Python 将 cls 和绑定的实例传递给 __new__
  • 嵌套元类的巧妙方法!我同意你不要在严肃、理智的代码中使用它。
猜你喜欢
  • 2018-11-11
  • 2015-10-27
  • 2020-06-20
  • 2019-10-24
  • 2011-11-20
  • 2013-05-15
  • 2012-09-07
  • 2020-06-12
  • 1970-01-01
相关资源
最近更新 更多