day7-类的起源与metaclass
类的起源
创建类可以有2种方式
1.普通方式
class Foo(object): def __init__(self,name): self.name = name f = Foo("Dick")
上述代码中,f是通过Foo类实例化的对象,其实,不仅f是一个对象,Foo类本身也是一个对象,因为在Python中一切事物都是对象
如果按照一切事物都是对象的理论:f对象是通过执行Foo类的构造方法创建,那么Foo类对象应该也是通过执行某个类构造方法创建。
print(type(f)) #输出 <class '__main__.Foo'> #表示,f对象由Foo类创建 print(type(Foo)) #输出 <class 'type'> #表示,Foo类对象由type类创建
解析:所以f对象是Foo类的一个实例,Foo类对象是type类的一个实例,即:Foo类对象是通过type类的构建方法创建。
2.特殊方式
通过type创建类
格式:类名=type(“类名”,(父类,),{"方法名":方法的内存地址})
type第一个参数:类名
type第二个参数:当前类的基类(父类)
type第三个参数:类的成员方法
def func(self): #创建方法 print("hello %s"%self.name) def __init__(self,name,age): #创建构造方法 self.name = name self.age = age #经典类写法 #Foo = type("Foo",(),{"talk":func,"__init__":__init__}) #新式类写法 Foo = type("Foo",(object,),{"talk":func,"__init__":__init__}) print(type(Foo)) #打印类的类型 f = Foo("Dick",22) #根据Foo类创建对象f f.talk() #通过对象调用方法 #输出 <class 'type'> hello Dick
解析:类是由type类实例化产生的(通过type将2个不相干的方法装配到类Foo里,再通过类Foo实例化为f的对象),注意在新式类写法上,类继承父类是一个元组,所以必须要加上(,)逗号,如果不加的话,它就是一个值了。
__new__方法
概念
__new__()是在新式类中新出现的方法,它作用在构造方法创建实例之前,可以这么理解,在Python中存在于类里面的构造方法__init__()负责将类实例化,而在__init__()启动之前,__new__()则是决定是否要使用该__init__()方法,因为__new__()可以调用其他类的构造方法或者直接返回别的对象来作为本类的实例。
class Foo(object): def __init__(self,name): self.name = name print("init") def __new__(cls, *args, **kwargs): #第一个参数cls是当前正在实例化的类,即Foo print("new %s"%cls) print("__new__:",object.__new__(cls)) return object.__new__(cls) #继承父亲的__new__方法 #第一阶段:解释器从上到下执行代码创建Foo类 #第二阶段:通过Foo类创建obj对象 obj = Foo(”Jacky“) print("类Foo的对象obj:",obj) #输出 new <class '__main__.Foo'> #__new__方法先执行 __new__: <__main__.Foo object at 0x102178c18> #object.__new__(cls)为类Foo实例化的内存对象 init #__init__方法后__new__执行后执行 类Foo的对象obj: <__main__.Foo object at 0x102178be0> #类Foo实例化的内存对象obj
解析:类Foo本来就是一个对象,类Foo传给了__new__方法的cls参数(因为自己要继承父类,所以要把自己传进去,才能将类进行实例化),__new__方法必须要有返回值(Foo的内存对象)
我们可以看到,__new__方法的调用发生在__init__方法之前的。因为我们不知道__new__方法下的return object.__new__(cls)到底有什么作用,先把它注释掉,再重新执行程序
class Foo(object): def __init__(self,name): self.name = name print("init") def __new__(cls, *args, **kwargs): print("new %s"%cls) #return object.__new__(cls) #将return代码注释掉 obj = Foo("Dick") #输出 new <class '__main__.Foo'>
解析:可以发现__init__方法没有执行。看起来__new__方法接受的参数虽然也是和__init__一样,但是__init__是在类实例创建之后调用,而__new__方法正是创建这个类实例的方法。
注意:如果__new__()方法没有返回cls(即当前类)的实例,那么当前类的__init__()方法是不会被调用的。如果__new__()方法返回其他类(新式类或经典类均可)的实例,那么只会调用被返回的那个类的构造方法。
具体的执行过程是这样的:
1.obj=Foo("Dick")
2.首先执行使用"Dick"参数来执行Foo类的__new__方法,这个__new__方法会返回Foo类的一个实例(通常情况下是使用super(Foo,cls).__new__(cls,... ...)这样的方式)
3.然后利用这个实例来调用类的__init__方法,上一步里面__new__产生的实例也就是__init__里面的self
所以,__init__和__new__最主要的区别在于:
- __init__通常用于初始化一个新实例,控制这个初始化的过程,比如添加一些属性,做一些额外的操作,发生在类实例被创建完之后。它是实例级别的方法。
- __new__通常用于控制生成一个新实例的过程。它是类级别的方法。
1.没有重写__new__()方法
class Foo(object): def __init__(self,name): self.name = name print("init") def __new__(cls, *args, **kwargs): print("new %s"%cls) return object.__new__(cls) class Child(Foo): #Child没有__new__方法,那么会自动调用父类的__new__方法来 #创建实例,即会自动调用object.__new__(cls) pass obj1 = Foo("Dick") obj2 = Child("Jacky") #输出 new <class '__main__.Foo'> init new <class '__main__.Child'> init
解析:如果在新式类中没有重写__new__方法,Python默认是调用该类的直接父类的__new__方法来构造该类的实例,如果该类的父类也没有重写__new__方法,那么将一直按照同样的规则追溯至object的__new__方法,因为object是所有新式类的基类。
2.重写__new__方法
class Foo(object): def __new__(cls, *args, **kwargs): print("new %s"%cls) return object.__new__(cls) class Child(Foo): def __new__(cls, *args, **kwargs): obj = object.__new__(cls,*args,**kwargs) print("Calling __new__ for %s"%obj.__class__) return obj class Adult(object): def __new__(cls, *args, **kwargs): obj = object.__new__(Child,*args,**kwargs) print("Calling __new__ for %s"%obj.__class__) return obj obj1 = Foo() obj2 = Child() obj3 = Adult() #输出 new <class '__main__.Foo'> Calling __new__ for <class '__main__.Child'> Calling __new__ for <class '__main__.Child'>
解析:如果新式类中重写了__new__方法,那么你可以自由选择任意一个其他新式类的__new__方法来生成实例,包括这个新式类的所有前代类和后代类,这里的object.__new__(cls,*args,**kwargs)等价于 super(Foo,cls).__new__(cls,*args,**kwargs) 、object.__new__(Foo,*args,**kwargs) 、Child.__new__(cls,*args,**kwargs),在任何新式类中,不能调用自身的__new__来创建实例,因为这会造成死循环,所以要避免出现这样的语法 Foo.__new__(cls,*args,**kwargs) 或者 cls.__new__(cls,*args,**kwargs)
__metaclass__方法
作用
类中有一个数学__metaclass__是用来表示该类由谁来实例化创建的,所以,我们可以为__metaclass__设置一个type类的派生类,从而查看类创建的过程:
类的生成调用顺序
顺序依次为__new__ --> __init__ --> __call__