第九章 面向对象进阶
阅读目录
-
一 isinstance(obj,cls)和issubclass(sub,super)
-
二 反射
-
三 __setattr__,__delattr__,__getattr__
-
四 二次加工标准类型(包装)
-
五 __getattribute__
-
六 描述符(__get__,__set__,__delete__)
-
六 再看property
-
七 __setitem__,__getitem,__delitem__
-
八 __str__,__repr__,__format__
-
九 __slots__
-
十 __next__和__iter__实现迭代器协议
-
十一 __doc__
-
十二 __module__和__class__
-
十三 __del__
-
十四 __enter__和__exit__
-
十五 __call__
-
十六 metaclass
一 isinstance(obj,cls)和issubclass(sub,super)
isinstance(obj,cls)检查是否obj是否是类 cls 的对象
>>> class A(object): ... pass ... >>> a = A() >>> class B(object): ... pass ... >>> b = B() >>> isinstance(a,A) True >>> isinstance(b,B) True >>> isinstance(a,B) False >>>
二 反射
-
什么是反射
反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。
-
python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)
通俗一点儿来说,在Python中反射是一种操作(查找、修改、删除)对象的一种方法,特点就是,可以使用和对象同名的字符串的形式对这个对象进行操作。下面先看看对象 和与其同名的字符串有什么不同和相同
下面就来介绍如何让上面的代码实现用用户输入的字符串形式去调用同名的对象
四个可以实现自省的函数
下列方法适用于类和对象(一切皆对象,类本身也是一个对象)
先来看源码
def getattr(object, name, default=None): # known special case of getattr
"""
getattr(object, name[, default]) -> value
Get a named attribute from an object; getattr(x, 'y') is equivalent to x.y.
When a default argument is given, it is returned when the attribute doesn't
exist; without it, an exception is raised in that case.
"""
pass
getattr(object, name, default=None)
# 简单翻译一下就是:
# 用于判断 object 下有没有一个和字符串 name 同名的方法或属性
def setattr(x, y, v): # real signature unknown; restored from __doc__
"""
Sets the named attribute on the given object to the specified value.
setattr(x, 'y', v) is equivalent to ``x.y = v''
"""
pass
setattr(x, y, v)
# 通过对一个和对象同名的字符串对对象的属性进行设置
def delattr(x, y): # real signature unknown; restored from __doc__
"""
Deletes the named attribute from the given object.
delattr(x, 'y') is equivalent to ``del x.y''
"""
pass
delattr(x, y)
# 通过一个和对象同名的字符串对对象进行删除
实例
class BlackMedium:
feature='Ugly'
def __init__(self,name,addr):
self.name=name
self.addr=addr
def sell_house(self):
print('%s 黑中介卖房子啦,傻逼才买呢,但是谁能证明自己不傻逼' %self.name)
def rent_house(self):
print('%s 黑中介租房子啦,傻逼才租呢' %self.name)
b1=BlackMedium('万成置地','回龙观天露园')
#检测是否含有某属性
print(hasattr(b1,'name'))
print(hasattr(b1,'sell_house'))
#获取属性
n=getattr(b1,'name')
print(n)
func=getattr(b1,'rent_house')
func()
# getattr(b1,'aaaaaaaa') #报错
print(getattr(b1,'aaaaaaaa','不存在啊')) # 对于不存在时,可以自定义返回的错误信息,不至于让程序崩溃
print('请重新获取正确的对象名')
#设置属性
setattr(b1,'sb',True)
setattr(b1,'show_name',lambda self:self.name+'sb')
print(b1.__dict__)
print(b1.show_name(b1))
#删除属性
delattr(b1,'addr')
delattr(b1,'show_name')
delattr(b1,'show_name111')#不存在,则报错
print(b1.__dict__)
四个方法的使用演示
class Foo(object):
staticField = "shark yun"
def __init__(self):
self.name = 'shark'
def func(self):
return 'func'
@staticmethod
def bar():
return 'bar'
print getattr(Foo, 'staticField')
print getattr(Foo, 'func')
print getattr(Foo, 'bar')
类也是对象
用反射方法导入模块 1
用反射方法导入模块 2
三 __setattr__,__delattr__,__getattr__
class Foo:
x=1
def __init__(self,y):
self.y=y
def __getattr__(self, item):
print('----> from getattr:你找的属性不存在')
def __setattr__(self, key, value):
print('----> from setattr')
# self.key=value #这就无限递归了,你好好想想
# self.__dict__[key]=value #应该使用它
def __delattr__(self, item):
print('----> from delattr')
# del self.item #无限递归了
self.__dict__.pop(item)
#__setattr__添加/修改属性会触发它的执行
f1=Foo(10) print(f1.__dict__) '''因为你重写了__setattr__,凡是赋值操作都会触发它的运行, 你啥都没写,就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值'''
#__getattr__只有在使用点调用属性且属性不存在的时候才会触发
f1.xxxxxx f1.z=3 print(f1.__dict__)
#__delattr__删除属性的时候会触发
f1.__dict__['a']=3#我们可以直接修改属性字典,来完成添加/修改属性的操作
print('>>>>',f1.__dict__)
del f1.a
print(f1.__dict__)
四 二次加工标准类型(包装)
包装:python为大家提供了标准数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增/改写方法,这就用到了我们刚学的继承/派生知识(其他的标准类型均可以通过下面的方式进行二次加工)
class List(list): # 首先继承 系统的类 list
def append(self, p_object):
'''我改写的方法'''
if not isinstance(p_object,str):
print('只有字符串类型能被添加到列表中')
return
# self.append(p_object) #进入无限递归
super().append(p_object)
def show_mid(self):
'''我新增的方法'''
index=int(len(self)/2)
print(self[index])
li=List('hello')
li.append('abc')
print(li,type(li))
#数字无法添加成功
li.append(1)
print('-->',li)
#基于标准类型新增了功能
li.show_mid()
授权:授权是包装的一个特性, 包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建,修改或删除原有产品的功能。其它的则保持原样。授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。
实现授权的关键点就是覆盖__getattr__方法
import time
class FileHandle:
def __init__(self,filename,mode='r',encoding='utf-8'):
self.file=open(filename,mode,encoding=encoding)
def write(self,line):
t=time.strftime('%Y-%m-%d %T')
self.file.write('%s %s' %(t,line))
def __getattr__(self, item):
return getattr(self.file,item)
f1=FileHandle('b.txt','w+')
f1.write('你好啊')
f1.seek(0)
print(f1.read())
f1.close()
# 授权示范一
#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'
#我们来加上b模式支持
import time
class FileHandle:
def __init__(self,filename,mode='r',encoding='utf-8'):
if 'b' in mode:
self.file=open(filename,mode)
else:
self.file=open(filename,mode,encoding=encoding)
self.encoding=encoding
def write(self,line):
t=time.strftime('%Y-%m-%d %T')
if 'b' in self.mode:
msg=bytes('%s %s' %(t,line),encoding=self.encoding)
self.file.write(msg)
def __getattr__(self, item):
return getattr(self.file,item)
f1=FileHandle('b.txt','wb')
f1.write('你好啊啊啊啊啊') #自定制的write,不用在进行encode转成二进制去写了,简单,大气
f1.close()
五 __getattribute__
class Foo:
def __init__(self,x):
self.x=x
def __getattr__(self, item):
print('执行的是我')
# return self.__dict__[item]
def __getattribute__(self, item):
print('不管是否存在,我都会执行')
raise AttributeError('哈哈') # 设置主动抛出异常,后面会讲的
f1=Foo(10)
f1.x
# f1.xxxxxx
# 当__getattribute__与__getattr__同时存在,只会执行__getattrbute__,
# 除非__getattribute__在执行过程中抛出异常AttributeError
六 描述符(__get__,__set__,__delete__)
参照:http://www.cnblogs.com/linhaifeng/articles/6204014.html#_label6
六 再看property
一个静态属性property本质就是实现了get,set,delete三种方法
class Foo:
@property
def AAA(self):
print('get的时候运行我啊')
@AAA.setter
def AAA(self,value):
print('set的时候运行我啊')
@AAA.deleter
def AAA(self):
print('delete的时候运行我啊')
#只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter
f1=Foo()
f1.AAA
f1.AAA='aaa'
del f1.AAA
class Foo:
def get_AAA(self):
print('get的时候运行我啊')
def set_AAA(self,value):
print('set的时候运行我啊')
def delete_AAA(self):
print('delete的时候运行我啊')
AAA=property(get_AAA,set_AAA,delete_AAA) #内置property三个参数与get,set,delete一一对应
f1=Foo()
f1.AAA
f1.AAA='aaa'
del f1.AAA
怎么用?
class Goods:
def __init__(self):
# 原价
self.original_price = 100
# 折扣
self.discount = 0.8
@property
def price(self):
# 实际价格 = 原价 * 折扣
new_price = self.original_price * self.discount
return new_price
@price.setter
def price(self, value):
self.original_price = value
@price.deleter
def price(self):
del self.original_price
obj = Goods()
obj.price # 获取商品价格
obj.price = 200 # 修改商品原价
print(obj.price)
del obj.price # 删除商品原价
#实现类型检测功能
#第一关:
class People:
def __init__(self,name):
self.name=name
@property
def name(self):
return self.name
# p1=People('shark') #property自动实现了set和get方法属于数据描述符,比实例属性优先级高,所以你这面写会触发property内置的set,抛出异常
#第二关:修订版
class People:
def __init__(self,name):
self.name=name #实例化就触发property
@property
def name(self):
# return self.name #无限递归
print('get------>')
return self.DouNiWan
@name.setter
def name(self,value):
print('set------>')
self.DouNiWan=value
@name.deleter
def name(self):
print('delete------>')
del self.DouNiWan
p1=People('shark') #self.name实际是存放到self.DouNiWan里
print(p1.name)
print(p1.name)
print(p1.name)
print(p1.__dict__)
p1.name='egon'
print(p1.__dict__)
del p1.name
print(p1.__dict__)
#第三关:加上类型检查
class People:
def __init__(self,name):
self.name=name #实例化就触发property
@property
def name(self):
# return self.name #无限递归
print('get------>')
return self.DouNiWan
@name.setter
def name(self,value):
print('set------>')
if not isinstance(value,str):
raise TypeError('必须是字符串类型')
self.DouNiWan=value
@name.deleter
def name(self):
print('delete------>')
del self.DouNiWan
p1=People('shark') #self.name实际是存放到self.DouNiWan里
p1.name=1
七 __setitem__,__getitem,__delitem__
用于索引操作,如字典。以上分别表示获取、设置、删除数据
class Foo(object):
def __getitem__(self, key):
print('__getitem__',key)
def __setitem__(self, key, value):
print('__setitem__',key,value)
def __delitem__(self, key):
print('__delitem__',key)
obj = Foo()
result = obj['k1'] # 自动触发执行 __getitem__
obj['k2'] = 'shark' # 自动触发执行 __setitem__
del obj['k1']
八 __str__,__repr__,__format__
改变对象的字符串显示__str__,__repr__
自定制格式化字符串__format__
#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'
format_dict={
'nat':'{obj.name}-{obj.addr}-{obj.type}',#学校名-学校地址-学校类型
'tna':'{obj.type}:{obj.name}:{obj.addr}',#学校类型:学校名:学校地址
'tan':'{obj.type}/{obj.addr}/{obj.name}',#学校类型/学校地址/学校名
}
class School:
def __init__(self,name,addr,type):
self.name=name
self.addr=addr
self.type=type
def __repr__(self):
return 'School(%s,%s)' %(self.name,self.addr)
def __str__(self):
return '(%s,%s)' %(self.name,self.addr)
def __format__(self, format_spec):
# if format_spec
if not format_spec or format_spec not in format_dict:
format_spec='nat'
fmt=format_dict[format_spec]
return fmt.format(obj=self)
s1=School('sharkyun','北京','私立')
print('from repr: ',repr(s1))
print('from str: ',str(s1))
print(s1)
'''
str函数或者print函数--->obj.__str__()
repr或者交互式解释器--->obj.__repr__()
如果__str__没有被定义,那么就会使用__repr__来代替输出
注意:这俩方法的返回值必须是字符串,否则抛出异常
'''
print(format(s1,'nat'))
print(format(s1,'tna'))
print(format(s1,'tan'))
print(format(s1,'asfdasdffd'))
date_dic={
'ymd':'{0.year}:{0.month}:{0.day}',
'dmy':'{0.day}/{0.month}/{0.year}',
'mdy':'{0.month}-{0.day}-{0.year}',
}
class Date:
def __init__(self,year,month,day):
self.year=year
self.month=month
self.day=day
def __format__(self, format_spec):
if not format_spec or format_spec not in date_dic:
format_spec='ymd'
fmt=date_dic[format_spec]
return fmt.format(self)
d1=Date(2016,12,29)
print(format(d1))
print('{:mdy}'.format(d1))
class A:
pass
class B(A):
pass
print(issubclass(B,A)) #B是A的子类,返回True
a1=A()
print(isinstance(a1,A)) #a1是A的实例
issubclass和isinstance
九 __slots__
'''
1.__slots__是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)
2.引子:使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的)
3.为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__
当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个
字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__一个不好的地方就是我们不能再给
实例添加新的属性了,只能使用在__slots__中定义的那些属性名。
4.注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该
只在那些经常被使用到 的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。
关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。 更多的是用来作为一个内存优化工具。
'''
class Foo:
__slots__='x'
f1=Foo()
f1.x=1
f1.y=2#报错
print(f1.__slots__) #f1不再有__dict__
class Bar:
__slots__=['x','y']
n=Bar()
n.x,n.y=1,2
n.z=3#报错
__slots__使用
class Foo:
__slots__=['name','age']
f1=Foo()
f1.name='shark'
f1.age=18
print(f1.__slots__)
f2=Foo()
f2.name='egon'
f2.age=19
print(f2.__slots__)
print(Foo.__dict__)
#f1与f2都没有属性字典__dict__了,统一归__slots__管,节省内存
十 __next__和__iter__实现迭代器协议
class Foo:
def __init__(self,x):
self.x=x
def __iter__(self):
return self
def __next__(self):
self.x+=1
return self.x
f1=Foo(10)
for i in f1:
print(i)
class Foo:
def __init__(self,x):
self.x=x
def __iter__(self):
return self
def __next__(self):
if self.x == 0:
raise StopIteration
self.x-=1
return self.x
f1=Foo(5)
for i in f1:
print(i)
'''
输出结果:
3
1
'''
十一 __doc__
__doc__ 打印类的描述信息(若有的话,没有则返回 None),并且该属性无法继承或被继承
class Foo:
'''我是描述信息'''
pass
print(Foo.__doc__)
十二 __module__和__class__
__module__ 表示当前操作的对象在那个模块
__class__ 表示当前操作的对象的类是什么
[root@localhost ~]# tree . -L 2 # 目录结构
.
├── anaconda-ks.cfg
├── client.py
├── python3
│ ├── examplemodule.py
│ ├── glance
│ ├── __init__.py
│ └── __pycache__
├── test.sh
└── yikes.file -> old.file
[root@localhost ~]# cat python3/examplemodule.py
#!/usr/bin/env python3
class A():
def __init__(self):
self.name = "SB"
[root@localhost ~]# python3
>>> from python3.examplemodule import A
>>> obj = A()
>>> print(obj.__module__)
python3.examplemodule # 当前运行的程序是来自这个模块
>>> print(obj.__class__)
<class 'python3.examplemodule.A'> # 当前运行的程序是来自这个类
>>>
十三 __del__
析构方法,当对象在内存中被释放时,自动触发执行。
注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。
[root@localhost ~]# cat python3/examplemodule.py
#!/usr/bin/env python3
class A():
def __init__(self):
self.name = "SB"
def __del__(self):
print('删除一个对象,我就被执行了')
[root@localhost ~]# python3
Python 3.6.0 (default, Feb 6 2017, 04:32:17)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-4)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from python3.examplemodule import A
>>> obj = A()
>>> obj.name
'SB'
>>> del obj
删除一个对象,我就被执行了
>>> exit()
下面看一个我在 PyCarm 中执行一段代码的奇怪现象
class Foo:
def __del__(self):
print('执行我啦')
f2=Foo()
# del f2
print('------->')
# 下面是输出结果
------->
执行我啦
# 这是为何?
其实这里可以分两种情况来说明此现象 1. 在主动去删除一个对象时,会触发执行 __del__()方法,之后程序并没有结束,接着执行 2. 在一个程序先结束的时候,这个程序的整个内存也就释放了,由于Python的主动回收内存的机制, 自然在这个程序运行内存中其他的对象也就不存在了,程序在结束后,Python会紧接着删除其他 的运行中的对象,也就会触发执行 __del__() 方法了。
十四 __enter__和__exit__
我们知道在操作文件对象的时候可以这么写
with open('a.txt') as f:
'代码块'
上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法
class Open:
def __init__(self,name):
self.name=name
def __enter__(self):
print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')
# return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('with中代码块执行完毕时执行我啊')
with Open('a.txt') as f:
print('=====>执行代码块')
# print(f,f.name)
上下文管理协议
__exit__()中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法执行
class Open:
def __init__(self,name):
self.name=name
def __enter__(self):
print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')
def __exit__(self, exc_type, exc_val, exc_tb):
print('with中代码块执行完毕时执行我啊')
print(exc_type)
print(exc_val)
print(exc_tb)
with Open('a.txt') as f:
print('=====>执行代码块')
raise AttributeError('***着火啦,救火啊***')
print('0'*100) #------------------------------->不会执行
如果__exit()返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行
class Open:
def __init__(self,name):
self.name=name
def __enter__(self):
print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')
def __exit__(self, exc_type, exc_val, exc_tb):
print('with中代码块执行完毕时执行我啊')
print(exc_type)
print(exc_val)
print(exc_tb)
return True
with Open('a.txt') as f:
print('=====>执行代码块')
raise AttributeError('***着火啦,救火啊***')
print('0'*100) #------------------------------->会执行
用途或者说好处:
1.使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预
2.在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处
十五 __str__ 如果一个类中定义了__str__方法,那么在打印 对象 时,默认输出该方法的返回值。
class Foo:
def __str__(self):
return '你打印了一个对象,但不要打对象哦'
obj = Foo()
print obj
# 输出结果
你打印了一个对象,但不要打对象哦
十六 __call__() 对象后面加括号,触发执行。
注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;
而对于一个类的 __call__ 方法的执行是由其实例化的对象后加括号触发的,即:对象() 或者 类()()
换句话说,一个对象后面加括号,会触发实例化这个对象的类 的 __call__()方法的执行。
还有先要记着的是这个__call__()方法,定义了随后需要执行的这个类的另外两个方法,__new__()
和 __init__(),会先执行
>>> class Foo(): ... def __init__(self,*args): ... pass ... def __call__(self): ... print("__calll__") ... >>> obj = Foo() >>> Foo()() __calll__ # 类对象 执行了 __call__,因为 Foo() 代表了类本身 >>> obj() >>> __calll__ # obj对象 执行了 __call__ >>>
十七 再来谈__new__ && metaclass && __init__
我们现在都知道,对象(obj)是通过 类(假设类名:Foo)实例化的对象,其实,不仅 obj 是一个对象,而且 Foo 类本身也是一个对象
因为在Python中一切事物都是对象。
如果按照一切事物都是对象的理论:
obj对象是通过执行Foo类的构造方法创建,那么Foo类对象又是通过那个类执行了类的 构造方法 创建的呢?
>>> class Foo(object):
... def __init__(self,name):
... self.name = name
...
>>> obj = Foo('shark')
>>> type(obj)
<class '__main__.Foo'>
>>> type(Foo)
<class 'type'> # 表示,obj 对象由Foo类创建
>>> type(type)
<class 'type'> # 表示,Foo类对象由 type 类创建
>>>type(type) # 无聊的事我已经帮你做了 ^V^
<class 'type'>
>>>
所以,obj对象是Foo类的一个实例,Foo类对象是 type 类的一个实例,即:Foo类对象 是通过type类的构造方法创建。
-
什么是元类?
元类是类的类,是类的模板
元类是用来控制如何创建类的,正如类是创建对象的模板一样
元类的实例为类,正如类的实例为对象(f1对象是Foo类的一个实例,Foo类是 type 类的一个实例)
type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象
-
创建类的两种方式
那么,创建类就可以有两种方式:
a). 普通方式
>>> class Foo(object):
... def func(self):
... print('Hello world')
...
>>>
b).特殊方式: 用元类生成一个类
def func(self):
print 'hello shark'
Foo = type('Foo',(object,), {'func': func})
#type第一个参数:类名
#type第二个参数:当前类的基类
#type第三个参数:类的成员,成员可以是类的一个属性,也可是类的一个函数
def func1(self):
print("hello %s"%self.name)
def __init__(self,name,age):
self.name = name
self.age = age
Foo = type('Foo',(object,),{'func':func1,'__init__':__init__})
f = Foo("shark",22)
f.func()
所以,类 是由 type 类实例化产生
那么问题来了,类默认是由 type 类实例化产生,type类中如何实现的创建类?类又是如何创建对象?
答案:一个类没有声明自己的元类,默认他的元类就是type,除了使用元类type,
用户也可以通过继承type来自定义元类
类中有一个属性 __metaclass__,其用来表示该类由 谁 来实例化创建,
所以,我们可以为 __metaclass__ 设置一个type类的派生类,从而查看元类如何控制类的创建,工作流程是什么
class MyType(type): # 首先必须先继承系统的元类 type
def __init__(cls,*args,**kwargs):
print("MyType __init__",cls,*args,**kwargs)
def __call__(cls,*args,**kwargs):
print('MyType __call__',cls,*args,**kwargs)
obj = cls.__new__(cls)
cls.__init__(obj,*args,**kwargs)
obj.age = 22
return obj
def __new__(typ,*args,**kwargs):
print("MyType __new__",typ,*args,**kwargs)
obj = type.__new__(typ,*args,**kwargs)
return obj
class Foo(object,metaclass=MyType):
def __init__(self,name):
self.name = name
print("Foo __init__")
def __call__(self,*args,**kwargs):
print("call...")
f = Foo('shark')
print(f.name,f.age)
# 输出结果
obj <class 'object'>
MyType __new__ <class '__main__.MyType'> Foo (<class 'object'>,) {'__module__': '__main__', '__call__': <function Foo.__call__ at 0x0000000000D42730>, '__init__': <function Foo.__init__ at 0x0000000000D426A8>, '__qualname__': 'Foo'}
MyType __init__ <class '__main__.Foo'> Foo (<class 'object'>,) {'__module__': '__main__', '__call__': <function Foo.__call__ at 0x0000000000D42730>, '__init__': <function Foo.__init__ at 0x0000000000D426A8>, '__qualname__': 'Foo'}
MyType __call__ <class '__main__.Foo'> shark
Foo __init__
shark 22
流程总结:
需要记住一点:名字加括号的本质(即,任何name()的形式),都是先找到name的上一级(父类),然后执行:父类.__call__
而父类.__call__一般做两件事:
1.调用name.__new__方法并返回一个对象
2.进而调用name.__init__方法对儿子name进行初始化
class 定义Foo,并指定元类为Mymeta,这就相当于要用Mymeta创建一个新的对象Foo,于是相当于执行
Foo=Mymeta('Foo',(...),{...})
因此目前我们可以看到是现象是,只定义class就会有如下执行效果
===>Mymeta.__new__
===>Mymeta.__init__ 并返回赋值给Foo
实际上class Foo(metaclass=Mymeta)是触发了Foo=Mymeta('Foo',(...),{...})操作,
遇到了名字加括号的形式,即Mymeta(...),于是就去找Mymeta的父类type,然后执行type.__call__(...)方法
于是触发Mymeta.__new__方法得到一个具体的对象,然后触发Mymeta.__init__方法对对象进行初始化
f = Foo('shark')
的原理同上面的一样
总结:元类的难点在于执行顺序很绕,其实我们只需要记住两点就可以了
1.谁后面跟括号,就从谁的父类中找__call__方法执行
type->Mymeta->Foo->obj
Mymeta()触发type.__call__
Foo()触发Mymeta.__call__
obj()触发Foo.__call__
2.__call__内按先后顺序依次调用其子类的__new__和__init__方法
-
流程图

![W25E4UKX4D]CH[Q8CON9D)R W25E4UKX4D]CH[Q8CON9D)R](https://images2015.cnblogs.com/blog/1062368/201703/1062368-20170312161538529-1865389629.png)



浙公网安备 33010602011771号