关于python:有没有办法在不调用__init__的情况下实例化一个类?

Is there a way to instantiate a class without calling __init__?

有没有一种方法可以绕过python中类的构造函数__init__

例子:

1
2
3
4
5
6
class A(object):    
    def __init__(self):
        print"FAILURE"

    def Print(self):
        print"YEHAA"

现在我想创建一个A的实例。它可能看起来像这样,但是这种语法不正确。

1
2
a = A
a.Print()

编辑:

更复杂的例子:

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

1
2
3
4
5
6
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,不能调用构造函数,因为它需要参数文件。

1
2
3
4
5
6
7
8
9
    @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的一个实例,所以我必须以某种方式调用构造函数。

旧编辑:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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__。当然,我可以简单地存储它,但是如果数据是一个巨大的对象,我不想一直在内存中携带它,甚至不想把它保存到光盘上,那该怎么办呢?


你可以直接打电话给__new__,避开__init__。然后您可以创建一个给定类型的对象,并为__init__调用一个替代方法。这是pickle会做的事情。

然而,首先,我想强调的是,这是你不应该做的事情,无论你想做什么,都有更好的方法,其中一些方法在其他答案中已经提到。尤其是,不打电话给__init__是个坏主意。

创建对象时,或多或少会发生以下情况:

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

你可以跳过第二步。

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

如果跳过__init__并使用另一个初始值设定项,则会有一种代码重复——在两个地方填充实例变量,很容易在其中一个初始值设定项中遗漏其中一个变量,或者意外地使两个填充字段的操作不同。这就提供了一些不容易跟踪的细微错误(您必须知道调用了哪个初始值设定项),而代码将更难维护。更不用说,如果你使用继承的话,你会陷入更大的混乱中——问题会出现在继承链上,因为你必须在链上的任何地方使用这个可选的初始值设定项。

此外,这样做或多或少会覆盖Python的实例创建并创建自己的实例。Python已经很好地为您做到了这一点,不需要重新设计它,它会使使用您的代码的人感到困惑。

下面是最好的替代方法:使用一个单独的__init__方法,该方法将被调用,用于正确初始化所有实例变量的类的所有可能的实例化。对于不同的初始化模式,使用以下两种方法之一:

  • 支持使用可选参数处理案例的__init__的不同签名。
  • 创建几个作为可选构造函数的类方法。确保它们都以正常方式(即调用__init__)创建类的实例,如Roman Bodnarchuk所示,同时执行额外的工作或其他任何操作。最好是将所有数据传递给类(并且__init__处理它),但是如果这不可能或不方便,您可以在创建实例和完成__init__初始化之后设置一些实例变量。
  • 如果__init__有一个可选的步骤(例如,处理data参数,尽管您必须更具体一些),您可以将其作为可选参数,也可以使其成为进行处理的常规方法…或者两者兼而有之。


    使用classmethod装饰器进行Load方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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)

    所以你可以这样做:

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

    编辑:

    无论如何,如果您仍然想省略__init__方法,请尝试__new__方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    >>> 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>


    从字面上看,我会使用元类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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()

    这对于在不污染参数列表的情况下复制构造函数很有用。但要正确地做到这一点,比我提议的黑客要更加努力和关心。


    不是真的。__init__的目的是实例化一个对象,默认情况下,它实际上不做任何事情。如果__init__方法没有做您想要做的事情,而且您自己的代码也不能更改,那么您可以选择将它关闭。例如,以您的类A为例,我们可以执行以下操作以避免调用该__init__方法:

    1
    2
    3
    4
    5
    def emptyinit(self):
        pass
    A.__init__ = emptyinit
    a = A()
    a.Print()

    这将动态地从类中切换出哪个__init__方法,并将其替换为空调用。注意,这可能不是一件好事,因为它不调用超级类的__init__方法。

    您还可以对它进行子类化,以创建自己的类,该类执行所有相同的操作,除了重写__init__方法来执行您希望它执行的操作(可能什么都没有)。

    但是,您可能只希望从类中调用方法而不实例化对象。如果是这样的话,您应该研究@classmethod和@staticmethod修饰符。他们只允许这种行为。

    在代码中,您已经放置了@staticmethod decorator,它不接受self参数。也许@classmethod更适合这个目的,它看起来更像这样:

    1
    2
    3
    4
    5
    6
    @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__,而我实际上并没有意识到这一点(尽管这是完全合理的)。谢谢罗什!


    我在读python的食谱,有一节讨论这个:这个例子是使用__new__绕过__init__()给出的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    >>> 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下运行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    >>> 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__'
    >>>


    正如我在评论中所说,您可以更改您的__init__方法,这样它就可以在不给参数任何值的情况下进行创建:

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

    将成为:

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

    或:

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