有没有方法可以实例化一个类而不调用 __init__?

84 投票
6 回答
67572 浏览
提问于 2025-04-16 19:46

有没有办法绕过Python中类的构造函数__init__

举个例子:

class A(object):    
    def __init__(self):
        print "FAILURE"

    def Print(self):
        print "YEHAA"

现在我想创建一个类A的实例。看起来可以这样做,但这个语法是不正确的。

a = A
a.Print()

编辑:

再举一个更复杂的例子:

假设我有一个对象C,它的作用是存储一个参数并进行一些计算。不过,这个参数并不是直接传递的,而是嵌入在一个巨大的参数文件中。它可能看起来像这样:

class C(object):
    def __init__(self, ParameterFile):
        self._Parameter = self._ExtractParamterFile(ParameterFile)
    def _ExtractParamterFile(self, ParameterFile):
        #does some complex magic to extract the right parameter
        return the_extracted_parameter

现在我想保存和加载这个对象C的实例。但是,当我加载这个对象时,我只有一个单独的变量self._Parameter,而且我不能调用构造函数,因为它需要参数文件。

    @staticmethod
    def Load(file):
        f = open(file, "rb")
        oldObject = pickle.load(f)
        f.close()

        #somehow create newObject without calling __init__
        newObject._Parameter = oldObject._Parameter
        return newObject

换句话说,没有传递参数文件就无法创建实例。不过在我的“真实”情况下,并不是参数文件,而是一大堆我不想一直占用内存或者存储到磁盘的数据。

而且因为我想从方法Load返回一个C的实例,所以我必须以某种方式调用构造函数。

旧编辑:

这是一个更复杂的例子,解释了我为什么会问这个问题

class B(object):    
    def __init__(self, name, data):
        self._Name = name
        #do something with data, but do NOT save data in a variable

    @staticmethod
    def Load(self, file, newName):
        f = open(file, "rb")
        s = pickle.load(f)
        f.close()

        newS = B(???)
        newS._Name = newName
        return newS

正如你所看到的,由于data没有存储在类变量中,我无法将其传递给__init__。当然,我可以简单地存储它,但如果数据是一个巨大的对象,我不想一直把它放在内存中,或者甚至保存到磁盘上,那该怎么办呢?

相关问题:

6 个回答

11

如果认真理解你的问题,我会使用元类:

class MetaSkipInit(type):
    def __call__(cls):
        return cls.__new__(cls)


class B(object):
    __metaclass__ = MetaSkipInit

    def __init__(self):
        print "FAILURE"

    def Print(self):
        print "YEHAA"


b = B()
b.Print()

这在某些情况下很有用,比如在复制构造函数时,可以避免参数列表变得复杂。不过,要做到这一点其实需要更多的工作和细心,可能比我提的简单方法要麻烦得多。

25

使用 classmethod 装饰器来处理你的 Load 方法:

class B(object):    
    def __init__(self, name, data):
        self._Name = name
        #store data

    @classmethod
    def Load(cls, file, newName):
        f = open(file, "rb")
        s = pickle.load(f)
        f.close()

        return cls(newName, s)

这样你就可以这样做:

loaded_obj = B.Load('filename.txt', 'foo')

补充:

无论如何,如果你还是想省略 __init__ 方法,可以试试 __new__

>>> class A(object):
...     def __init__(self):
...             print '__init__'
...
>>> A()
__init__
<__main__.A object at 0x800f1f710>
>>> a = A.__new__(A)
>>> a
<__main__.A object at 0x800f1fd50>
81

你可以通过直接调用 __new__ 来绕过 __init__。这样你就可以创建一个指定类型的对象,并调用一个替代的 __init__ 方法。这是 pickle 会做的事情。

不过,我想强调的是,这种做法是不推荐的,无论你想实现什么,有更好的方法可以做到这一点,其他回答中也提到了一些。特别是,跳过调用 __init__ 是个主意。

当对象被创建时,大致上会发生以下步骤:

a = A.__new__(A, *args, **kwargs)
a.__init__(*args, **kwargs)

你可以跳过第二步。

这是你不应该这样做的原因: __init__ 的目的是初始化对象,填充所有字段,并确保父类的 __init__ 方法也被调用。使用 pickle 是个例外,因为它试图存储与对象相关的所有数据(包括为对象设置的任何字段/实例变量),因此之前通过 __init__ 设置的内容会被 pickle 恢复,不需要再调用一次。

如果你跳过 __init__ 而使用替代的初始化方法,就会出现代码重复的情况——实例变量会在两个地方被填充,很容易在某个初始化方法中漏掉一个,或者不小心让两个方法的填充行为不同。这可能导致一些不容易发现的微妙错误(你需要知道哪个初始化方法被调用了),而且代码会更难维护。如果你使用了继承,问题会更严重,因为你需要在继承链的每个地方都使用这个替代的初始化方法。

此外,这样做基本上是覆盖了 Python 的实例创建过程,自己重新实现了一遍。Python 已经很好地为你处理了这个过程,不需要重新发明轮子,这样会让使用你代码的人感到困惑。

那么最好的做法是什么: 使用一个单一的 __init__ 方法,适用于所有可能的类实例化,确保正确初始化所有实例变量。对于不同的初始化模式,可以使用以下两种方法:

  1. 支持不同的 __init__ 方法签名,通过使用可选参数来处理你的情况。
  2. 创建几个类方法,作为替代构造函数。确保它们都以正常的方式创建类的实例(即调用 __init__),正如 Roman Bodnarchuk 所示,同时执行额外的工作或其他操作。最好是将所有数据传递给类(然后由 __init__ 处理),但如果这不可能或不方便,你可以在实例创建后、__init__ 完成初始化后设置一些实例变量。

如果 __init__ 有一个可选步骤(例如处理 data 参数,尽管你需要更具体),你可以将其设为可选参数,或者创建一个正常的方法来处理……或者两者都可以。

撰写回答