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

时间:2011-06-17 09:37:23

标签: python class constructor instantiation

有没有办法绕过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

换句话说,如果不传递参数文件,则无法创建实例。然而,在我的“真实”情况下,它不是一个参数文件,而是一些巨大的数据垃圾,我当然不想在内存中随身携带,甚至将它存储到光盘中。

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

OLD EDIT:

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

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 个答案:

答案 0 :(得分:50)

可以通过直接致电__init__来绕过__new__。然后,您可以创建给定类型的对象,并为__init__调用替代方法。这是pickle会做的事情。

然而,首先我要强调的是, 不应 这样做,无论你想要实现什么,还有更好的方法如何做到这一点,其中一些已在其他答案中提到。特别是,跳过调用__init__是一个错误的想法。

创建对象时,或多或少会发生这种情况:

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

你可以跳过第二步。

以下是不应执行此操作的原因: __init__的目的是初始化对象,填写所有字段并确保{还会调用父类的{1}}方法。对于__init__,它是一个例外,因为它试图存储与对象关联的所有数据(包括为对象设置的任何字段/实例变量),所以设置的任何内容都是通过pickle前一次将被pickle恢复,没有必要再次调用它。

如果你跳过__init__并使用另一个初始化程序,你就会有一种代码重复 - 有两个地方可以填充实例变量,并且很容易错过其中一个其中一个初始化器或意外地使两个填充字段的行为不同。这给出了微小的错误的可能性,这些错误不是那么简单(你必须知道调用了哪个初始化程序),并且代码将更难维护。更不用说如果你使用继承,你将陷入更大的混乱 - 问题会在继承链上出现,因为你必须在链的各个地方使用这个替代的初始化器。

通过这样做,你或多或少会覆盖Python的实例创建并制作自己的实例。 Python已经很好地完成了这项工作,不需要重新创建它,它会让使用你代码的人感到困惑。

以下是最佳选择:使用单个__init__方法,该方法可以为类的所有可能实例化调用,以正确初始化所有实例变量。对于不同的初始化模式,请使用以下两种方法之一:

  1. 支持__init__使用可选参数处理案例的不同签名。
  2. 创建几个用作替代构造函数的类方法。确保它们以正常方式创建类的实例(即调用__init__),as shown by Roman Bodnarchuk,同时执行其他工作或其他任何操作。最好是将所有数据传递给类(并且__init__处理它),但如果这是不可能或不方便的,您可以在创建实例并__init__完成初始化后设置一些实例变量。
  3. 如果__init__有一个可选步骤(例如处理__init__参数,虽然你必须更具体),你可以将它作为一个可选参数,或者制作一个普通方法处理......或两者兼而有之。

答案 1 :(得分:18)

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>

答案 2 :(得分:9)

从字面上理解我的问题,我会使用元类:

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

这可能是有用的,例如用于复制构造函数而不会污染参数列表。 但要正确地做到这一点比我提议的黑客更多的工作和关心。

答案 3 :(得分:8)

不是真的。 __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的优秀答案指出,您可以通过实施__init__来避免调用__new__,我实际上并不知道(虽然这很有道理)。谢谢Rosh!

答案 4 :(得分:6)

我正在阅读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__'
>>> 

答案 5 :(得分:2)

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

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

会变成:

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
相关问题