You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python中__init__被调用两次的原因、参数异常及解决方法咨询

Python类实例化中__init__重复调用的问题分析与解决

简化代码

class Base:
    __possible_types__ = {}

    def __new__(cls, *args, **kwargs) -> type:
        # pop the `__type__` argument because it
        # should be passed to the class `__init__`
        type_ = kwargs.pop("__type__", None)

        if type_ is not None:
            possible_type = cls.__possible_types__.get(type_)

            if possible_type is not None:
                return possible_type(*args, **kwargs)
        return super().__new__(cls)

    def __init__(self, *args, **kwargs) -> None:
        print(f"{self.__class__.__name__}.__init__", args, kwargs)

    def __init_subclass__(cls) -> None:
        __possible_types__ = {}

        for parent in cls.__mro__[1:]:
            if issubclass(parent, Base):
                parent.__possible_types__[cls.__name__] = cls


class Parent(Base):
    pass


class Child(Parent):
    pass 


class Adult(Parent):
    pass


should_be_child = Parent(
    name="bob",
    __type__="Child"
)

print(should_be_child)

执行输出

Child.__init__ () {'name': 'bob'}
Child.__init__ () {'name': 'bob', '__type__': 'Child'}
<__main__.Child object at 0x...>

问题解答

1. 为什么__init__会被调用两次?

两次调用的核心原因是构造流程的重复触发:

  • 第一次:在Base.__new__中,我们显式调用了possible_type(*args, **kwargs)(即Child(name="bob")),这个调用会完整执行Child的构造逻辑——调用__new__创建实例后,Python自动触发Child.__init__,这是第一次调用。
  • 第二次:当Base.__new__返回Child实例后,回到Parent(...)的构造逻辑。由于Child是Parent的子类,isinstance(Child实例, Parent)结果为True,Python会自动调用该实例的__init__方法,并传入Parent(...)调用时的原始参数,这就触发了第二次__init__调用。

2. 已弹出的字典项__type__为何会出现在第二次__init__调用的参数中?

Python在构造函数调用逻辑中,触发__init__时使用的是传递给构造函数的原始参数集合,而非__new__方法中修改后的kwargs。虽然我们在Base.__new__里执行了kwargs.pop("__type__"),但这只是修改了__new__函数内部的kwargs字典引用,Python触发第二次__init__时,仍然会传入最初调用Parent(...)时的完整参数,所以__type__会再次出现。

3. 该如何解决这个问题?

推荐从根源避免重复触发__init__,以下是两种可行方案:

方案1:直接调用目标类的__new__创建实例(推荐)

修改Base.__new__,不直接调用目标类的构造函数(避免触发第一次__init__),而是直接调用目标类的__new__方法创建实例,让Python在后续逻辑中统一触发一次__init__:

class Base:
    __possible_types__ = {}

    def __new__(cls, *args, **kwargs) -> type:
        type_ = kwargs.pop("__type__", None)

        if type_ is not None:
            possible_type = cls.__possible_types__.get(type_)

            if possible_type is not None:
                # 直接调用目标类的__new__创建实例,不触发完整构造流程
                obj = possible_type.__new__(possible_type, *args, **kwargs)
                return obj
        return super().__new__(cls)

    # 其余代码不变

修改后执行输出:

Child.__init__ () {'name': 'bob'}
<__main__.Child object at 0x...>

此时__init__仅调用一次,且参数中没有__type__(因为kwargs.pop已经修改了原始字典)。

方案2:给__init__添加重复执行判断

如果无法修改__new__逻辑,可以给实例添加标记,避免__init__重复执行核心逻辑:

class Base:
    # 其余代码不变

    def __init__(self, *args, **kwargs) -> None:
        # 检查是否已经初始化过
        if hasattr(self, "_initialized"):
            return
        print(f"{self.__class__.__name__}.__init__", args, kwargs)
        self._initialized = True

这种方案通过标记过滤重复调用,虽然不会阻止__init__被调用两次,但会跳过重复的逻辑执行。


内容的提问来源于stack exchange,提问作者dsal3389

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 08:02:33