翼度科技»论坛 编程开发 python 查看内容

python中__new__和__init__的实现

7

主题

7

帖子

21

积分

新手上路

Rank: 1

积分
21
1 前言

在Python中,每个对象都有两个特殊的方法:__new__和__init__。这两个方法在对象的创建和初始化过程中起着重要的作用,但它们的功能和用法有所不同。

1.1 功能上的区别

__new__方法是Python中的一个魔术方法(Magic Method),用于创建一个新的对象实例。当我们在Python中创建一个对象时,实际上是调用了__new__方法来创建一个新的对象实例,然后再调用__init__方法来初始化这个对象。
__init__方法是Python中的一个普通方法,用于初始化一个已经存在的对象。当我们使用__new__方法创建一个新的对象实例后(不可为None),就会调用这个对象的__init__方法来对对象进行初始化。

1.2 参数上的区别

__new__方法通常需要三个参数:第一个参数是类(cls,即class),第二个参数是传入的参数列表(args),即位置参数;第三个参数也是传入的参数列表(kwargs),即关键字参数。__new__方法的返回值是一个新的对象实例。
__init__方法通常需要1个或以上参数:第一个参数是对象实例(self,也就是__new__方法返回的对象实例),后续可有可无的若干参数是传入的参数列表(args),常用于设置实例化对象属性。__init__方法的返回值是None。

1.3 调用时机上的区别

__new__方法在创建对象时被调用,它的调用时机是在__init__方法之前。__new__方法的返回值是一个新的对象实例,这个实例会被传递给__init__方法进行初始化。
__init__方法在对象被创建后被调用,它的调用时机是在__new__方法之后。__init__方法用于对已经存在的对象进行初始化,它的参数列表通常包括传递给类的构造函数的参数。
上述可知,没有__new__方法,我们就无法创建新的对象实例;没有__init__方法,我们就无法对已经存在的对象进行初始化。两者功能上虽有差别,但是是用于共同来创建和初始化一个对象的,所以两者均很重要,下面则是具体使用的分析。

2 使用


2.1 简单示例
  1. class Clazz:

  2.     def __new__(cls, *args, **kwargs):
  3.         print("调用__new__")
  4.         print(f'cls:{cls}, args:{args}, kwargs: {kwargs}')

  5.     def __init__(self, name):
  6.         print("调用__init__")
  7.         print(f'self:{self}, name:{name}')
  8.         self.name = name


  9. clazz = Clazz("xiaoxu")
复制代码
执行结果:
  1. 调用__new__cls:<class '__main__.Clazz'>, args:('xiaoxu',), kwargs: {}
复制代码
可以看到,上述代码先执行了__new__,但是并未执行__init__方法,因为只有当我们使用__new__方法创建一个新的对象实例后,才会调用这个对象的__init__方法来对对象进行初始化。
__new__是一个内置staticmethod,其首个参数必须是type类型,即要实例化的class本身,其负责为传入的class type分配内存、创建一个新实例并返回该实例,该返回值其实就是后续执行__init__函数的入参self。
参考Python的源码typeobject.c中定义的type_call函数:
  1. static PyObject *
  2. type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
  3. {
  4.      PyObject *obj;

  5.     if (type->tp_new == NULL) {
  6.          PyErr_Format(PyExc_TypeError,
  7.                      "cannot create '%.100s' instances",
  8.                       type->tp_name);
  9.         return NULL;
  10.      }
  11. ...
  12.     obj = type->tp_new(type, args, kwds); # 这里先执行tp_new分配内存、创建对象返回obj
  13.      obj = _Py_CheckFunctionResult((PyObject*)type, obj, NULL);
  14. ...
  15.      type = Py_TYPE(obj); # 这里获取obj的class类型,并判定有tp_init则执行该初始化函数
  16.      if (type->tp_init != NULL) {
  17.          int res = type->tp_init(obj, args, kwds);
  18.         if (res < 0) {
  19.              assert(PyErr_Occurred());
  20.             Py_DECREF(obj);
  21.              obj = NULL;
  22.          }
  23.          else {
  24.              assert(!PyErr_Occurred());
  25.         }
  26.     }
  27.      return obj;
  28. }
复制代码
执行代码class(*args, **kwargs) 时,其会先调用type_new函数(__new__方法)分配内存创建实例并返回为obj,而后通过Py_TYPE(obj)获取其具体type,再进一步检查type->tp_init不为空则执行该初始化函数(也就是__init__方法)。
若__new__方法返回为None,依然不会执行__init__方法:
  1. class Clazz:

  2.     def __new__(cls, *args, **kwargs):
  3.         print("调用__new__")
  4.         print(f'cls:{cls}, args:{args}, kwargs: {kwargs}')
  5.         return None

  6.     def __init__(self, name):
  7.         print("调用__init__")
  8.         print(f'self:{self}, name:{name}')
  9.         self.name = name

  10. clazz = Clazz("xiaoxu")
  11. print(clazz)

  12. # 调用__new__
  13. # cls:<class '__main__.Clazz'>, args:('xiaoxu',), kwargs: {}
  14. # None
复制代码
作如下修改:
  1. class Clazz:

  2.     def __new__(cls, *args, **kwargs):
  3.         print("调用__new__")
  4.         print(f'cls:{cls}, args:{args}, kwargs: {kwargs}')
  5.         # x = super().__new__(cls) 等同写法
  6.         x = super(Clazz, cls).__new__(cls)
  7.         print("self_first:", x)
  8.         return x

  9.     def __init__(self, name, age=99):
  10.         print("调用__init__")
  11.         print(f'self:{self}, name:{name}, age: {age}')
  12.         super(Clazz, self).__init__()
  13.         self.name = name
  14.         # Cannot return a value from __init__
  15.         # __init__是不需要返回值的
  16.         # return None


  17. clazz = Clazz("xiaoxu", age=66)
  18. print(clazz)
复制代码
因为python中任何类都继承于object 类,上述的super().__new__(cls),其实就是调用内置的object.__new__()方法来创建对象实例。一般的形式有super(类名, cls).__new__(cls, … …)。
执行结果如下:

结果也印证了上述提到的,__new__方法的返回值,就是后续执行__init__函数的入参self
小结说明:
1、继承自object的新式类才有__new__。
2、__new__至少要有一个参数cls,代表当前类,此参数在实例化时由Python解释器自动识别。
3、__new__必须要有返回值,返回实例化出来的实例,这点在自己实现__new__时要特别注意,可以return父类(通过super(当前类名, cls))__new__出来的实例,或者直接是object的__new__出来的实例。
4、__init__有一个参数self,就是这个__new__返回的实例,__init__在__new__的基础上可以完成一些其它初始化的动作,__init__不需要返回值。
5、如果__new__创建的是当前类的实例,会自动调用__init__函数,通过return语句里面调用的__new__函数的第一个参数是 cls 来保证是当前类实例,如果是其他类的类名,那么实际创建返回的就是其他类的实例,就不会调用当前类的__init__函数,也不会调用其他类的__init__函数。
6、在定义子类时没有重新定义__new__()时,Python默认是调用该类的直接父类的__new__()方法来构造该类的实例,如果该类的父类也没有重写__new__(),那么将一直按此规矩追溯至object的__new__()方法,因为object是所有新式类的基类。
7、如果子类中重写了__new__()方法,那么你可以自由选择任意一个的其他的新式类(必定要是新式类,只有新式类必定都有__new__(),因为所有新式类都是object的后代,而经典类则没有__new__()方法)的__new__()方法来制造实例,包括这个新式类的所有前代类和后代类,只要它们不会造成递归死循环。不能调用自己的__new__,因为是递归死循环调用。
8、对于子类的__init__,其调用规则跟__new__是一致的,当然如果子类和父类的__init__函数都想调用,可以在子类的__init__函数中加入对父类__init__函数的调用。

2.2 __new__的作用

参考Python官方文档,__new__方法主要是当你继承一些不可变的class时(比如int, str, tuple), 提供给你一个自定义这些类的实例化过程的途径,另外就是实现自定义的metaclass。
(1)根据int举个栗子:
  1. class PositiveInteger(int):
  2.     def __new__(cls, *args, **kwargs):
  3.         print("param:", args)
  4.         return super(PositiveInteger, cls).__new__(cls, abs(args[0]))


  5. p = PositiveInteger(-5)
  6. print(p)
复制代码
执行结果:

(2)再根据tuple举个栗子:
__new__方法自定义要求保证实例创建、并且必须记得返回实例对象的一系列固定逻辑正确,而__init__方法相当简单只需要设置想要设置的属性即可,出错的可能性很小,绝大部分场景用户完全只需要更改__init__方法,用户无需感知__new__的相关逻辑。
理论上是可以通过多次调用__init__函数进行初始化的,但是任何实例都只可能被创建一次,因为每次调用__new__函数理论上都是创建一个新实例返回(特殊情况如单例模式则只返回首次创建的实例),而不会存在重新构造已有实例的情况。
针对__init__可被多次调用的情况,mutable和immutable对象会有不同的行为,因为immutable对象(不可变对象)从语义上来说首次创建、初始化完成后就不可以修改了,所以后续再调用其__init__方法应该无任何效果才对,示例如下:
  1. a = [1, 2, 3]
  2. print(id(a), a)

  3. # 对list实例重新初始化改变其取值为[4, 5]
  4. a.__init__([4, 5])
  5. print(id(a), a)

  6. b = (1, 2, 3)
  7. print(id(b), b)

  8. # 对tuple实例尝试重新初始化并无任何效果,
  9. # 符合对immutable类型的行为预期
  10. b.__init__((4, 5))
  11. print(id(b), b)
复制代码
执行结果如下:

定义、继承immutable class,tuple的栗子:
  1. class PositiveTuple(tuple):

  2.     def __init__(self, *args, **kwargs):
  3.         print('get in init one, self:', id(self), self)
  4.         # 直接通过索引赋值的方式会报:
  5.         # PositiveTuple' object does not support item assignment
  6.         # for i, x in enumerate(self):
  7.         # self[i] = abs(x)
  8.         # 只能尝试对self整体赋值
  9.         self = tuple(abs(x) for x in self)
  10.         print('get in init two, self:', id(self), self)


  11. t = PositiveTuple([-3, -2, 5])
  12. print(id(t), t)
复制代码
执行结果:

可以看到虽然在__init__中重新对self进行了赋值,其实只是相当于新生成了一个tuple对象28859528,t指向的依然是最开始生成好的实例28847512。
如下为使用自定义__new__的方法:
  1. class PositiveTuple(tuple):

  2.     def __new__(cls, *args, **kwargs):
  3.         self = super().__new__(cls, *args, **kwargs)

  4.         print('get in init one, self:', id(self), self)
  5.         # 直接通过索引赋值的方式会报: PositiveTuple' object does not support item assignment
  6.         # for i, x in enumerate(self):
  7.         # self[i] = abs(x)
  8.         # 只能尝试对self整体赋值
  9.         self = tuple(abs(x) for x in self)
  10.         print('get in init two, self:', id(self), self)
  11.         return self


  12. t = PositiveTuple([-3, -2, 5])
  13. print(id(t), t)
复制代码
执行结果如下:

可以看到一开始调用super.__new__时其实已经创建了一个实例27667864,而后通过新生成一个全部转化为正数的tuple 27679880赋值后返回,最终返回的实例t也就最终需要的全正数tuple。
(3)通过__new__方法实现单实例:
  1. class Singleton(object):
  2.     def __new__(cls):
  3.         if not hasattr(cls, 'instance'):
  4.             cls.instance = super(Singleton, cls).__new__(cls)
  5.         # 每次生成的都是同一个实例
  6.         return cls.instance


  7. s1 = Singleton()
  8. s2 = Singleton()

  9. s1.attr1 = 'xiaoxu'
  10. print(s1.attr1, s2.attr1)
  11. print(s1 is s2)  # 返回True表明是同一个实例
  12. print(s1 == s2)
  13. # xiaoxu xiaoxu
  14. # True
  15. # True
复制代码
一般比如字典使用==比较是比较值相等,is是比较地址相等。而在Class对象比较中,使用==和is都是比较地址相等(可以通过自定义__eq__来实现想要的效果)。
通过上述的分析,在实际应用中,我们通常就可以同时使用__new__和__init__方法来创建和初始化一个对象。通过重写这两个方法,我们可以自定义对象的创建和初始化过程,从而实现更加灵活和强大的功能。
到此这篇关于python中__new__和__init__的实现的文章就介绍到这了,更多相关python __new__和__init__内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

来源:https://www.jb51.net/python/321088o1t.htm
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具