为什么 __init__() 总是在 __new__() 之后调用?

2024-11-28 08:37:00
admin
原创
11
摘要:问题描述:我只是试图简化我的一个类,并以与享元设计模式相同的风格引入一些功能。但是,我有点困惑,为什么__init__总是在之后调用__new__。我没想到这一点。有人能告诉我为什么会发生这种情况,以及我如何才能实现此功能吗?(除了将实现放入__new__感觉相当黑客的。)以下是一个例子:class A(ob...

问题描述:

我只是试图简化我的一个类,并以与享元设计模式相同的风格引入一些功能。

但是,我有点困惑,为什么__init__总是在之后调用__new__。我没想到这一点。有人能告诉我为什么会发生这种情况,以及我如何才能实现此功能吗?(除了将实现放入__new__感觉相当黑客的。)

以下是一个例子:

class A(object):
    _dict = dict()

    def __new__(cls):
        if 'key' in A._dict:
            print "EXISTS"
            return A._dict['key']
        else:
            print "NEW"
            return super(A, cls).__new__(cls)

    def __init__(self):
        print "INIT"
        A._dict['key'] = self
        print ""

a1 = A()
a2 = A()
a3 = A()

输出:

NEW
INIT

EXISTS
INIT

EXISTS
INIT

为什么?


解决方案 1:

__new__当您需要控制新实例的创建时使用。

__init__当您需要控制新实例的初始化时使用

__new__是实例创建的第一步。它首先被调用,并负责返回类的新实例。

相反,
__init__它不返回任何东西;它只负责在创建实例后对其进行初始化。

__new__一般来说,除非您要对不可变类型(如 str、int、unicode 或 tuple)进行子类化,否则不需要覆盖。

摘自 2008 年 4 月的 mail.python.org 上的帖子:何时使用__new__vs. __init__?。

您应该考虑到,您尝试执行的操作通常是通过工厂完成的,这是最好的方法。使用__new__不是一种好的干净解决方案,因此请考虑使用工厂。这是一个很好的例子:ActiveState Fᴀᴄᴛᴏʀʏ ᴘᴀᴛᴛᴇʀɴ Recipe。

解决方案 2:

__new__是静态类方法,而__init__是实例方法。
__new__必须先创建实例,然后__init__才能初始化它。请注意以参数形式__init__接收self。在创建实例之前,没有self

现在,我了解到您正在尝试在 Python 中实现单例模式。有几种方法可以做到这一点。

此外,从 Python 2.6 开始,您可以使用类装饰器。

def singleton(cls):
    instances = {}
    def getinstance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return getinstance

@singleton
class MyClass:
  ...

解决方案 3:

在大多数著名的 OO 语言中,类似的表达式SomeClass(arg1, arg2)将分配一个新实例,初始化该实例的属性,然后返回它。

在大多数著名的 OO 语言中,可以通过定义构造函数为每个类定制“初始化实例的属性”部分,构造函数基本上只是对新实例进行操作的代码块(使用提供给构造函数表达式的参数)以设置所需的任何初始条件。在 Python 中,这对应于类的__init__方法。

Python 的__new__“分配新实例”部分无非是类似的针对每个类的定制。这当然允许您执行不寻常的操作,例如返回现有实例而不是分配新实例。因此在 Python 中,我们不应该认为这部分必然涉及分配;我们所需要的只是__new__从某个地方找到一个合适的实例。

但这仍然只是任务的一半,Python 系统无法知道有时你想__init__在之后运行任务的另一半(),有时你不想。如果你想要这种行为,你必须明确说明。

通常,您可以重构,以便只需要__new__,或者不需要__new__,或者__init__对已初始化的对象有不同的行为。但如果您真的想这样做,Python 实际上允许您重新定义“作业”,因此SomeClass(arg1, arg2)不一定调用__new__后跟__init__。为此,您需要创建一个元类,并定义其__call__方法。

元类只是类的类。类的__call__方法控制调用类的实例时发生的情况。因此,元类__call__方法控制调用类时发生的情况;即,它允许您从头到尾重新定义实例创建机制。在这个级别上,您可以最优雅地实现完全非标准的实例创建过程,例如单例模式。事实上,使用不到 10 行代码,您就可以实现一个Singleton元类,然后甚至不需要您进行__new__ 任何操作,并且可以通过简单地添加将任何其他普通类变成单例__metaclass__ = Singleton

class Singleton(type):
    def __init__(self, *args, **kwargs):
        super(Singleton, self).__init__(*args, **kwargs)
        self.__instance = None
    def __call__(self, *args, **kwargs):
        if self.__instance is None:
            self.__instance = super(Singleton, self).__call__(*args, **kwargs)
        return self.__instance

然而,这可能比这种情况真正需要的更深层的魔法!

解决方案 4:

引用文档:

典型的实现是通过使用“super(currentclass, cls).__new__(cls[, ...])”和适当的参数调用超类的 __new__() 方法来创建该类的新实例,然后在返回新创建的实例之前根据需要对其进行修改。

...

如果 __new__() 没有返回 cls 的实例,那么新实例的 __init__() 方法将不会被调用。

__new__() 主要用于允许不可变类型(如 int、str 或 tuple)的子类自定义实例创建。

解决方案 5:

我意识到这个问题已经很老了,但我也遇到过类似的问题。以下内容满足了我的要求:

class Agent(object):
    _agents = dict()

    def __new__(cls, *p):
        number = p[0]
        if not number in cls._agents:
            cls._agents[number] = object.__new__(cls)
        return cls._agents[number]

    def __init__(self, number):
        self.number = number

    def __eq__(self, rhs):
        return self.number == rhs.number

Agent("a") is Agent("a") == True

我使用此页面作为资源http://infohost.nmt.edu/tcc/help/pubs/python/web/new-new-method.html

解决方案 6:

__new__返回同一个类的实例时,__init__会在返回的对象上随后运行。也就是说,您不能使用__new__来阻止__init__运行。即使您从 返回之前创建的对象__new__,它也会被__init__一次又一次地双重(三重等...)初始化。

下面是 Singleton 模式的通用方法,它扩展了上面的 vartec 答案并对其进行了修复:

def SingletonClass(cls):
    class Single(cls):
        __doc__ = cls.__doc__
        _initialized = False
        _instance = None

        def __new__(cls, *args, **kwargs):
            if not cls._instance:
                cls._instance = super(Single, cls).__new__(cls, *args, **kwargs)
            return cls._instance

        def __init__(self, *args, **kwargs):
            if self._initialized:
                return
            super(Single, self).__init__(*args, **kwargs)
            self.__class__._initialized = True  # Its crucial to set this variable on the class!
    return Single

完整故事在这里。

另一种方法,实际上涉及__new__使用类方法:

class Singleton(object):
    __initialized = False

    def __new__(cls, *args, **kwargs):
        if not cls.__initialized:
            cls.__init__(*args, **kwargs)
            cls.__initialized = True
        return cls


class MyClass(Singleton):
    @classmethod
    def __init__(cls, x, y):
        print "init is here"

    @classmethod
    def do(cls):
        print "doing stuff"

请注意,使用此方法您需要用 装饰您的所有方法@classmethod,因为您永远不会使用任何真正的 实例MyClass

解决方案 7:

我认为这个问题的简单答案是,如果__new__返回的值与类的类型相同,则__init__函数执行,否则不会执行。在这种情况下,您的代码返回A._dict('key')与相同的类cls,因此__init__将被执行。

解决方案 8:

对@AntonyHatchkins 答案的更新,您可能需要为元类型的每个类提供一个单独的实例字典,这意味着您应该__init__在元类中有一个方法来使用该字典初始化您的类对象,而不是使其成为所有类的全局方法。

class MetaQuasiSingleton(type):
    def __init__(cls, name, bases, attibutes):
        cls._dict = {}

    def __call__(cls, key):
        if key in cls._dict:
            print('EXISTS')
            instance = cls._dict[key]
        else:
            print('NEW')
            instance = super().__call__(key)
            cls._dict[key] = instance
        return instance

class A(metaclass=MetaQuasiSingleton):
    def __init__(self, key):
        print 'INIT'
        self.key = key
        print()

我继续使用__init__方法更新了原始代码,并将语法更改为 Python 3 符号(无参数调用super和类参数中的元类,而不是作为属性)。

不管怎样,这里的重点是,如果找到密钥,您的类初始化程序(__call__方法)将不会执行__new__或。这比使用更简洁,因为使用时如果您想跳过默认步骤,则需要标记对象。__init__`__new__`__init__

解决方案 9:

class M(type):
    _dict = {}

    def __call__(cls, key):
        if key in cls._dict:
            print 'EXISTS'
            return cls._dict[key]
        else:
            print 'NEW'
            instance = super(M, cls).__call__(key)
            cls._dict[key] = instance
            return instance

class A(object):
    __metaclass__ = M

    def __init__(self, key):
        print 'INIT'
        self.key = key
        print

a1 = A('aaa')
a2 = A('bbb')
a3 = A('aaa')

输出:

NEW
INIT

NEW
INIT

EXISTS

注意:作为副作用,属性会自动从asM._dict访问,因此请注意不要偶然覆盖它。A`A._dict`

解决方案 10:

不过,我有点困惑,为什么__init__总是在之后调用__new__

我认为 C++ 类比在这里很有用:

  1. __new__只需为对象分配内存。对象的实例变量需要内存来保存它,这就是该步骤__new__要做的。

  2. __init__将对象的内部变量初始化为特定值(可以是默认值)。

解决方案 11:

new 应该返回一个类的新的空白实例。然后调用 init 来初始化该实例。在 new 的“NEW”情况下,您不会调用 init__,因此它会为您调用。调用的代码__new__不会跟踪 __init 是否已在特定实例上调用,也不应该跟踪,因为您在这里做了一些非常不寻常的事情。

您可以在 init 函数中为对象添加一个属性,以表明它已初始化。首先检查 init 中是否存在该属性,如果存在则不要继续。

解决方案 12:

进一步深入挖掘!

CPython 中泛型类的类型为type,其基类为Object(除非明确定义另一个基类,如元类)。低级调用的顺序可在此处找到。第一个调用的方法是 ,type_call然后调用tp_new,然后tp_init

这里有趣的部分是,它将tp_new调用Object(基类)的新方法object_new,该方法执行tp_allocPyType_GenericAlloc)为对象分配内存:)

此时,对象在内存中创建,然后__init__调用该方法。如果__init__未在您的类中实现,则object_init调用该方法,并且不执行任何操作 :)

然后type_call只返回绑定到变量的对象。

解决方案 13:

在传统的面向对象语言中,应该将其视为__init__一个简单的构造函数。例如,如果您熟悉 Java 或 C++,则构造函数会隐式地传递一个指向其自身实例的指针。在 Java 的情况下,它是this变量。如果检查为 Java 生成的字节码,就会注意到两个调用。第一个调用是“new”方法,然后下一个调用是 init 方法(这是对用户定义构造函数的实际调用)。这两个步骤过程使得在调用类的构造函数方法(这只是该实例的另一种方法)之前可以创建实际实例。

现在,对于 Python 来说,__new__这是一项可供用户使用的附加功能。由于 Java 的类型特性,它不提供这种灵活性。如果一种语言提供了这种功能,那么的实现者__new__可以在返回实例之前在该方法中做很多事情,包括在某些情况下创建一个不相关对象的全新实例。而且,这种方法也非常有效,尤其是对于 Python 中的不可变类型。

解决方案 14:

参考此文档:

当对数字和字符串等不可变的内置类型进行子类化时,以及偶尔在其他情况下,静态方法__new__会派上用场。__new__是实例构造的第一步,在之前调用__init__

__new__方法以类作为其第一个参数来调用;其职责是返回该类的新实例。

将其与以下进行比较__init____init__以实例作为其第一个参数进行调用,并且它不返回任何内容;其职责是初始化实例。

在某些情况下,无需调用即可创建新实例__init__(例如,当从 pickle 加载实例时)。没有办法在不调用的情况下创建新实例__new__(尽管在某些情况下,您可以调用基类的__new__)。

关于您希望实现的目标,在同一个文档中也有关于 Singleton 模式的信息

class Singleton(object):
        def __new__(cls, *args, **kwds):
            it = cls.__dict__.get("__it__")
            if it is not None:
                return it
            cls.__it__ = it = object.__new__(cls)
            it.init(*args, **kwds)
            return it
        def init(self, *args, **kwds):
            pass

你也可以使用 PEP 318 中的这个实现,使用装饰器

def singleton(cls):
    instances = {}
    def getinstance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return getinstance

@singleton
class MyClass:
...

解决方案 15:

现在我遇到了同样的问题,出于某些原因,我决定避免使用装饰器、工厂和元类。我这样做了:

主文件

def _alt(func):
    import functools
    @functools.wraps(func)
    def init(self, *p, **k):
        if hasattr(self, "parent_initialized"):
            return
        else:
            self.parent_initialized = True
            func(self, *p, **k)

    return init


class Parent:
    # Empty dictionary, shouldn't ever be filled with anything else
    parent_cache = {}

    def __new__(cls, n, *args, **kwargs):

        # Checks if object with this ID (n) has been created
        if n in cls.parent_cache:

            # It was, return it
            return cls.parent_cache[n]

        else:

            # Check if it was modified by this function
            if not hasattr(cls, "parent_modified"):
                # Add the attribute
                cls.parent_modified = True
                cls.parent_cache = {}

                # Apply it
                cls.__init__ = _alt(cls.__init__)

            # Get the instance
            obj = super().__new__(cls)

            # Push it to cache
            cls.parent_cache[n] = obj

            # Return it
            return obj

示例类

class A(Parent):

    def __init__(self, n):
        print("A.__init__", n)


class B(Parent):

    def __init__(self, n):
        print("B.__init__", n)

使用中

>>> A(1)
A.__init__ 1  # First A(1) initialized 
<__main__.A object at 0x000001A73A4A2E48>
>>> A(1)      # Returned previous A(1)
<__main__.A object at 0x000001A73A4A2E48>
>>> A(2)
A.__init__ 2  # First A(2) initialized
<__main__.A object at 0x000001A7395D9C88>
>>> B(2)
B.__init__ 2  # B class doesn't collide with A, thanks to separate cache
<__main__.B object at 0x000001A73951B080>
  • 警告:您不应该初始化 Parent,它与其他类发生冲突 - 除非您在每个子类中定义了单独的缓存,但这不是我们想要的。

  • 警告:似乎将 Parent 作为祖父母的类行为很奇怪。[未经验证]

在线尝试一下!

解决方案 16:

__init__在之后调用,__new__以便当您在子类中覆盖它时,您添加的代码仍将被调用。

如果您尝试对已具有 的类进行子类化__new__,则不知道这一点的人可能会首先调整__init__并将调用转发给子类__init__。 这种__init__在 之后调用的约定__new__有助于 按照预期工作。

仍然__init__需要允许超类__new__所需的任何参数,但如果不这样做,通常会产生明显的运行时错误。并且__new__应该明确允许*args和“**kw”,以明确扩展是可以的。

由于原始海报所描述的行为,在同一类中在同一继承级别上同时拥有__new__和通常是不好的形式。__init__

解决方案 17:

不过,我有点困惑,为什么__init__总是在之后调用__new__

除了这样做之外,没有太多原因。__new__没有初始化类的责任,其他方法有(__call__可能——我不确定)。

我没想到这一点。有人能告诉我为什么会发生这种情况吗?我该如何实现此功能?(除了将实现放入其中,__new__这感觉相当不靠谱)。

如果它已经被初始化,那么您可以什么都不做,或者您可以编写一个只调用新实例__init__的新元类,否则只返回。__call__`__init__`__new__(...)

解决方案 18:

原因很简单,new是用来创建实例的,而init是用来初始化实例的,初始化之前必须先创建实例,所以new应该先于init调用。

解决方案 19:

实例化一个类时,首先调用__new__()创建类的实例,然后调用__init__()初始化该实例

__新的__()

调用来创建 cls 类的新实例。......

如果在对象构造期间调用 __new__() 并且它返回 cls 的实例,那么新实例的 __init__() 方法将被调用,如 __init__(self[, ...]),...

__init__()

在实例创建之后调用(通过 __new__()),...

因为 __new__() 和 __init__() 在构造对象时协同工作(__new__() 用于创建对象,而 __init__() 用于自定义对象),...

例如,在实例化Teacher时,首先调用__new__()创建类的实例Teacher,然后调用__init__()初始化该实例,如下所示:

class Teacher:
    def __init__(self, name):
        self.name = name
        
class Student:
    def __init__(self, name):
        self.name = name

obj = Teacher("John") # Instantiation

print(obj.name)

这是输出:

<class '__main__.Teacher'>
John

并且,使用__new__()实例Teacher,我们可以创建的实例Student,如下所示:

# ...

obj = Teacher("John")
print(type(obj))
print(obj.name)

obj = obj.__new__(Student) # Creates the instance of "Student" class
print(type(obj))

现在,的实例Student创建如下:

<class '__main__.Teacher'>
<__main__.Teacher object at 0x7f4e3950bf10>
<class '__main__.Student'> # Here

接下来,如果我们尝试从的实例中获取变量的值name,如下所示:Student**

obj = Teacher("John")
print(type(obj))
print(obj.name)

obj = obj.__new__(Student)
print(type(obj))
print(obj.name) # Tries to get the value of "name" variable

出现以下错误是因为的实例Student尚未初始化__init__()

AttributeError:'Student' 对象没有属性 'name'

因此,我们初始化的实例Student,如下所示:

obj = Teacher("John") 
print(type(obj))
print(obj.name)

obj = obj.__new__(Student)
print(type(obj))
obj.__init__("Tom") # Initializes the instance of "Student" class
print(obj.name)

然后,我们可以从的实例中获取变量的值name,如下所示:Student

<class '__main__.Teacher'>
John
<class '__main__.Student'>
Tom # Here

解决方案 20:

人们已经详细说明了问题和答案,都使用了一些像单例等的例子。请参阅下面的代码:

__instance = None

def __new__(cls):
    if cls.__instance is None:
        cls.__instance = object.__new__(cls)
    return cls.__instance

执行上述代码

我从此链接获取了上述代码,其中有newinit的详细概述。值得一读!

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   649  
  如何借鉴华为IPD体系优化企业研发?在当今竞争激烈的市场环境中,企业要想保持技术领先和产品竞争力,必须拥有一套高效且严谨的研发管理体系。华为作为全球领先的ICT解决方案提供商,其集成产品开发(IPD, Integrated Product Development)体系与质量管理体系(如ISO 9000系列)的融合实践,...
IPD项目管理   0  
  IPD流程图的7种经典绘制方法详解在产品开发领域,集成产品开发(Integrated Product Development,简称IPD)流程被广泛应用,以提高产品开发的效率和质量。IPD流程图作为这一流程的可视化工具,其绘制方法至关重要。本文将详细介绍七种经典的IPD流程图绘制方法,帮助项目管理人员和团队更好地理解和...
IPD研发管理体系   0  
  IPD流程:企业创新管理的核心引擎在当今快速变化的市场环境中,企业要想持续保持竞争力,就必须不断进行创新。而IPD(Integrated Product Development,集成产品开发)流程作为一种先进的产品开发管理模式,正逐渐成为众多企业提升创新能力、加速产品上市速度、降低开发成本的重要选择。本文将深入探讨IP...
IPD管理   0  
  IPD流程与传统产品开发流程的概述在产品开发领域,企业不断寻求高效、系统的管理方法以确保产品能够顺利从概念转化为市场成功的产品。集成产品开发(Integrated Product Development,简称IPD)流程与传统产品开发流程是两种截然不同的管理理念和方法。传统产品开发流程往往以职能部门为核心,各部门按顺序...
IPD流程中PDCP是什么意思   0  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用