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

83

有没有一种方法在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__。当然,我可以简单地存储它,但如果数据是一个巨大的对象,我不想一直把它保存在内存中或甚至保存到磁盘上怎么办?


5
你为什么要这样做? - Björn Pollex
21
我不明白为什么你们投票反对它。这是一个有趣的问题,考虑到 Python 中可能存在的大量魔法,我相信可以通过元类等方式实现。 - pajton
4
@pajton 我完全同意!实际上,这个问题被很好地陈述了,将信息最小化以便理解。不幸的是,一旦人们的思维超出了他们有限的理解能力,他们很容易感到害怕。 - Woltan
1
@Woltan:我还是不明白。你是在做这样的事情吗:“a0 = pickle.load(...); a1 = A(a0)”? - Arlaharen
1
  1. 如果您可以更改类的代码,请重新编写它,使__init__不需要任何数据。
  2. 如果您可以查看类的代码,请传入一些默认值。
  3. 如果您甚至无法看到类的代码,请检查其文档,进行一些实验,找出其默认值并传递它们。或者继承一个不需要任何数据的新类。
- DrTyrsa
显示剩余7条评论
6个回答

80
你可以通过直接调用__new__来规避__init__。然后,你可以创建给定类型的对象,并调用__init__的替代方法。这是pickle会做的事情。
但是,首先我非常强调,这是你不应该做的事情,无论你试图实现什么,都有更好的方法可以完成,其中一些已经在其他答案中提到。特别是跳过调用__init__是一个坏主意
当对象被创建时,大致会发生以下情况:
a = A.__new__(A, *args, **kwargs)
a.__init__(*args, **kwargs)

您可以跳过第二步。

以下是不应这样做的原因: __init__ 的目的是初始化对象,填充所有字段,并确保也调用了父类的 __init__ 方法。使用 pickle 时会有例外,因为它尝试存储与对象关联的所有数据(包括为对象设置的任何字段/实例变量),因此 pickle 将恢复上一次由 __init__ 设置的任何内容,无需再次调用它。

如果跳过 __init__ 并使用另一种初始化程序,您将会有一种代码重复的情况 - 有两个地方填充实例变量,很容易在一个初始化程序中错过其中一个或意外地使两个程序以不同的方式填充字段。这会导致一些微妙的错误,这些错误并不那么容易追踪(您必须知道哪个初始化程序被调用),并且代码将更难维护。更不用说如果您使用继承,那么问题将会沿着继承链上升,因为您必须在整个继承链中都使用这种替代的初始化程序。

而且,这样做会更多或少地覆盖 Python 的实例创建并制作自己的。Python 已经为您做得很好了,不需要重新发明它,这会使使用您的代码的人感到困惑。

这里有更好的做法:使用一个单一的__init__方法,该方法将为类的所有可能实例化调用,并正确初始化所有实例变量。对于不同的初始化模式,请使用以下两种方法之一:
  1. 支持__init__的不同签名,通过使用可选参数来处理您的情况。
  2. 创建多个类方法作为替代构造函数。确保它们都以正常方式(即调用__init__)创建类的实例,如Roman Bodnarchuk所示,同时执行其他工作或其他操作。最好是它们将所有数据传递给类(并由__init__处理),但如果这是不可能或不方便的,则可以在实例创建后设置一些实例变量,并在__init__完成初始化后进行处理。
如果__init__具有可选步骤(例如,像处理data参数那样,尽管您必须更具体地说明),则可以将其作为可选参数或创建一个正常的方法来处理...或者两者都可以。

3
如果你不想调用被覆盖的A.__new__,甚至可以使用a = object.__new__(A) - Kijewski
8
如果你正在构建一个工厂模式并希望将构造委派给另一个类,但同时也想在不使用该工厂的情况下具有默认构造函数,则可以考虑使用这个方法。class ThreadPoolFactory: def PersistentThreadPool(self): ThreadPool.__new__ .... 构造并记录它 - gbtimmon
谢谢!这对于将工厂模式与可管理的默认__init__方法相结合非常有用。由于__init__()是用户接触的第一个地方之一,因此保持其参数(和docstring)易于阅读很有帮助。为所有可能的构造函数提供所有可能的可选参数(其中的子集可能是互斥的)将导致无法使用的文档噩梦。我想你可以用一个空存根和一张注释说“请参阅构造方法”来替换__init__(),但此时...它就是同样的事情(: - MRule

24

使用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>

在这种情况下,在行return cls(newName, s)中,data将是s。但是这不是这种情况,因为我不知道data是什么。 - Woltan
1
@Woltan 我也不知道。也许你应该再详细说明一下你的问题? - Roman Bodnarchuk
3
return cls(newName, s)调用了__init__()方法。 - martineau
@martineau 是的,但是__init__的第二个参数(data)的类型与s不同(请参见我的新编辑示例)。 - Woltan

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()

这可以用于复制构造函数,而不会污染参数列表。但是要做到这一点,需要比我提出的 hack 更多的工作和注意。


这是一个很好的解决方案,准确地展示了如何通过在元类级别实现自定义__call__方法来覆盖默认的__new__ + __init__。它不需要任何“黑客”技巧,允许您执行其他有用的操作(例如实现享元模式),并将其完全隐藏在用户之外。对于用户而言,它仍然看起来像是一个普通的构造函数调用。 - dieterg

10

其实不是这样的。 __init__ 的目的是实例化一个对象,默认情况下它实际上什么也不做。如果 __init__ 方法不能满足您的需求,而且无法修改它,您可以选择切换它。例如,对于您的类A,我们可以采取以下方法来避免调用那个 __init__ 方法:

def emptyinit(self):
    pass
A.__init__ = emptyinit
a = A()
a.Print()

这将动态替换类中的__init__方法,用一个空调用来代替它。请注意,这可能不是一个好主意,因为它不会调用超类的__init__方法。

你也可以通过创建自己的类来继承它, 保持所有相同的行为,只是重写__init__方法实现你所需的功能(也许是什么都不做)。

也许,你只想在不实例化对象的情况下从类中调用该方法。 如果是这种情况,你应该研究一下@classmethod@staticmethod修饰符。 它们允许进行此类型的操作。

在你的代码中,你已经使用了@staticmethod修饰符,它不需要一个self参数。也许对于这个目的更好的选择会是@classmethod修饰符,可能会像下面这样:

@classmethod
def Load(cls, file, newName):
    # Get the data
    data = getdata()
    # Create an instance of B with the data
    return cls.B(newName, data)

更新:Rosh的精彩回答指出,您可以通过实现 __new__ 来避免调用__init__,这是我之前不知道的(尽管这很有道理)。谢谢Rosh!


9
我正在阅读Python食谱,其中有一节谈到了这个问题:使用__new__来绕过__init__()。以下是一个示例:
>>> class A:
    def __init__(self,a):
        self.a = a
>>> test = A('a') >>> test.a 'a' >>> test_noinit = A.__new__(A) >>> test_noinit.a Traceback (most recent call last): File "", line 1, in test_noinit.a AttributeError: 'A' object has no attribute 'a' >>>
然而,我认为这只适用于Python3。以下运行在2.7下:
>>> class A:
    def __init__(self,a):
        self.a = a
>>> test = A.__new__(A)
Traceback (most recent call last): File "", line 1, in test = A.__new__(A) AttributeError: class A has no attribute '__new__' >>>

2
在2.7版本中,你需要使用“新式类”——也就是继承自object的子类:class A(object) - Douglas Bagnall

2

正如我在评论中所说,您可以更改__init__方法,以便允许在不给参数赋值的情况下创建对象:

def __init__(self, p0, p1, p2):
   # some logic

would become:

def __init__(self, p0=None, p1=None, p2=None):
   if p0 and p1 and p2:
       # some logic

或者:

def __init__(self, p0=None, p1=None, p2=None, init=True):
    if init:
        # some logic

是的,我可以设置默认参数,但这并不能回答我的问题。也许你是对的,我在我的类中可能有某种布局问题。 - Woltan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接