从基本认识类,到深入认知类的属性、方法、访问控制、继承、限制等,如何去构建一个类。
1、类构造和初始化
我们定义一个类,并生成初始化_ _ init _ _ 对象函数和 _ _ new _ _对象函数:
class A(object):
def __init__(self,*args, **kwargs):
print ("init %s" %self.__class__)
def __new__(cls,*args, **kwargs):
print ("new %s" %cls)
return object.__new__(cls, *args, **kwargs)
a = A()
new <class '__main__.A'> init <class '__main__.A'>
从结果可以看出,当实例化A类时,”_ _new _ _ “方法首先被调用,然后是” _ _ init _ _”方法。
一般来说,”_ _ init _ _ “和” _ _ new_ _”函数都会有下面的形式:
def __init__(self, *args, **kwargs):
# func_suite
def __new__(cls, *args, **kwargs):
# func_suite
return obj
对于”_ _new _ _ “和” _ _ init _ _”可以概括为:
- “_ _new _ _ “方法在Python中是真正的构造方法(创建并返回实例),通过这个方法可以产生一个”cls”对应的实例对象,所以说” _ _ new _ _”方法一定要有返回
- 对于”_ _ init _ _ “方法,是一个初始化的方法,“self”代表由类产生出来的实例对象,” _ _ init _ _”将对这个对象进行相应的初始化操作
前面文章中已经介绍过了”_ _ init _ _ “的一些行为,包括继承情况中” _ _ init _ _ “的表现。下面就重点看看”_ _ new _ _”方法。
2、_ _ new_ _特性
“_ _ new_ _”是在新式类中新出现的方法,它有以下行为特性:
- “_ _ new_ _” 方法是在类实例化对象时第一个调用的方法,将返回实例对象
- “_ _ new_ _” 方法始终都是类方法(即第一个参数为cls),即使没有被加上装饰器
- 第一个参数cls是当前正在实例化的类,如果要得到当前类的实例,应当在当前类中的 “_ _ new _ _ ” 方法语句中调用当前类的父类的” _ _ new_ _” 方法
def __new__(cls, *args, **kwargs):
# func_suite
return object.__new__(cls, *args, **kwargs)
2.1、重写_ _ new_ _
如果(新式)类中没有重写”_ _ new _ _ “方法,Python默认是调用该类的直接父类的” _ _ new_ _ “方法来构造该类的实例,如果该类的父类也没有重写” _ _ new _ _ “,那么将一直按照同样的规则追溯至object的” _ _new_ _”方法,因为object是所有新式类的基类。
而如果新式类中重写了”_ _new_ _ “方法,那么可以选择任意一个其他的新式类(必须是新式类,只有新式类有”_ _ new_ _ “,因为所有新式类都是从object派生)的”__ _ new _ _”方法来创建实例,包括这个新式类的所有前代类和后代类,只要它们不会造成递归死循环。
class Fun(object):
def __new__(cls, *args, **kwargs):
obj = object.__new__(cls, *args, **kwargs)
# 这里的object.__new__(cls, *args, **kwargs) 等价于super(Fun, cls).__new__(cls, *args, **kwargs)
# object.__new__(Fun, *args, **kwargs)
# Ny.__new__(cls, *args, **kwargs)
# person.__new__(cls, *args, **kwargs),即使person跟Fun没有关系,也是允许的,因为person是从object派生的新式类
# 在任何新式类,不能调用自身的“__new__”来创建实例,因为这会造成死循环
# 所以要避免return Fun.__new__(cls, *args, **kwargs)或return cls.__new__(cls, *args, **kwargs)
print("Call __new__ for %s" %obj.__class__)
return obj
class Ny(Fun):
def __new__(cls, *args, **kwargs):
obj = object.__new__(cls, *args, **kwargs)
print("Call __new__ for %s" %obj.__class__)
return obj
class person(object):
# person没有“__new__”方法,那么会自动调用其父类的“__new__”方法来创建实例,即会自动调用 object.__new__(cls)
pass
class girl(object):
def __new__(cls, *args, **kwargs):
# 可以选择用Bar来创建实例
obj = object.__new__(Ny, *args, **kwargs)
print("Call __new__ for %s" %obj.__class__)
return obj
fun = Fun()
ny = Ny()
girl = girl()
Call __new__ for <class '__main__.Fun'> Call __new__ for <class '__main__.Ny'> Call __new__ for <class '__main__.Ny'>
2.2 、_ _ init_ _的调用
“_ _ new _ _ “决定是否要使用该类的” _ _ init_ _ “方法,因为”_ _ new _ _” 可以调用其他类的构造方法或者直接返回别的类创建的对象来作为本类的实例。
通常来说,新式类开始实例化时,”_ _ new _ _ “方法会返回cls(cls指代当前类)的实例,然后调用该类的”_ _ init_ _ “方法作为初始化方法,该方法接收这个实例(即self)作为自己的第一个参数,然后依次传入” _ _new _ _”方法中接收的位置参数和命名参数。
但是,如果”_ _ new _ _ “没有返回cls(即当前类)的实例,那么当前类的” _ _ init_ _”方法是不会被调用的。看下面的例子:
class A(object):
def __init__(self, *args, **kwargs):
print("Call __init__ from %s" %self.__class__)
def __new__(cls, *args, **kwargs):
obj = object.__new__(cls, *args, **kwargs)
print("Call __new__ for %s" %obj.__class__)
return obj
class B(object):
def __init__(self, *args, **kwargs):
print("Call __init__ from %s" %self.__class__)
def __new__(cls, *args, **kwargs):
obj = object.__new__(A, *args, **kwargs)
print("Call __new__ for %s" %obj.__class__)
return obj
b = B()
print(type(b))
Call __new__ for <class '__main__.A'> <class '__main__.A'>
2.3、派生不可变类型
- 关于”_ _ new_ _”方法还有一个重要的用途就是用来派生不可变类型。
例如,Python中float是不可变类型,如果想要从float中派生一个子类,就要实现”_ _ new _ _”方法:
class RoundTFloat(float):
def __new__(cls, num):
num = round(num, 4)
return super(RoundTFloat, cls).__new__(cls, num)
# return float.__new__(cls, num)
num = RoundTFloat(3.141592654)
print(num) #3.1416
print(num.__class__)
3.1416 <class '__main__.RoundTFloat'>
3、定制一个类
- 在Python中,我们可以通过”魔术方法”使自定义的class变得强大、易用