Python学习第十二篇:面向对象进阶
一、isinstance和issubclass
1.isinstance(obj,cls)检查是否obj是否是类 cls 的对象
isinstance是Python中的一个内建函数
语法:
isinstance(object, classinfo)
如果参数object是classinfo的实例,或者object是classinfo类的子类的一个实例, 返回True。如果object不是一个给定类型的的对象, 则返回结果总是False
class Foo(object):
pass
obj=Foo()
print(isinstance(obj, Foo))
输出
True
2.issubclass(sub, super)检查sub类是否是 super 类父类的派生类
B是A类的子类, 返回True。否则返回False
pass
class B(A):
pass
print(issubclass(B, A))
输出
True
二、反射
1.反射定义
反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。
2.反射的实现
python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象,都可以使用反射
利用四个可以实现自省的函数,适用于类和对象(一切皆对象,类本身也是一个对象)
1.hasattr 2.setattr 3.getattr 4.delattr
1. hasattr(object,name)
判断一个对象里面是否有name属性或者name方法,返回BOOL值,有name特性返回True, 否则返回False。需要注意的是name要用括号括起来。
>>> class test(): ... name="xiaohua" ... def run(self): ... return "HelloWord" ... >>> t=test() >>> hasattr(t, "name") #判断对象有name属性 True >>> hasattr(t, "run") #判断对象有run方法 True >>>
2. getattr(object, name, default=None)
获取对象object的属性或者方法,如果存在打印出来,如果不存在,打印出默认值,默认值可选。需要注意的是,如果是返回的对象的方法,返回的是方法的内存地址,如果需要运行这个方法,可以在后面添加一对括号。
... name="xiaohua" ... def run(self): ... return "HelloWord" ... >>> t=test() >>> getattr(t, "name") #获取name属性,存在就打印出来。 'xiaohua' >>> getattr(t, "run") #获取run方法,存在就打印出方法的内存地址。 <bound method test.run of <__main__.test instance at 0x0269C878>> >>> getattr(t, "run")() #获取run方法,后面加括号可以将这个方法运行。 'HelloWord' >>> getattr(t, "age") #获取一个不存在的属性。 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: test instance has no attribute 'age' >>> getattr(t, "age","18") #若属性不存在,返回一个默认值。 '18' >>>
3. setattr(object, name, values)
给对象的属性赋值,若属性不存在,先创建再赋值。
>>> class test(): ... name="xiaohua" ... def run(self): ... return "HelloWord" ... >>> t=test() >>> hasattr(t, "age") #判断属性是否存在 False >>> setattr(t, "age", "18") #为属相赋值,并没有返回值 >>> hasattr(t, "age") #属性存在了 True >>>
4. delattr(object, name)
删除object对象名为name的属性。
综合例子
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','不存在啊'))
#设置属性
setattr(b1,'sb',True)
setattr(b1,'show_name',lambda self:self.name+'123')
print(b1.__dict__)
print(b1.show_name(b1))
#删除属性
delattr(b1,'addr')
delattr(b1,'show_name')
#delattr(b1,'show_name111')#不存在,则报错
print(b1.__dict__)
结果:
True
True
恒大
恒大 租房子
不存在啊
{'show_name': <function <lambda> at 0x10c9e8f28>, 'sb': True, 'addr': '回龙观', 'name': '恒大'}
恒大123
{'sb': True, 'name': '恒大'}
类也是对象
class Foo(object):
staticField = "old"
def __init__(self):
self.name = '123'
def func(self):
return 'func'
@staticmethod
def bar():
return 'bar'
print(getattr(Foo, 'staticField'))
print(getattr(Foo, 'func'))
print(getattr(Foo, 'bar'))
结果:
old <function Foo.func at 0x10f89f488> <function Foo.bar at 0x10f89f510>
模块的反射
import sys
def s1():
print('s1')
def s2():
print('s2')
this_module = sys.modules[__name__]
print(hasattr(this_module, 's1'))
print(getattr(this_module, 's2'))
结果:
True <function s2 at 0x108f4d400>
先判断再获取使用
if hasattr(f1,'get'):
func=getattr(f1,'get')
func()
3.反射的好处
- 实现可插拔机制
可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种‘后期绑定’,即你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能。
程序员A未实现功能
class FtpClient:
'ftp客户端,但是还么有实现具体的功能'
def __init__(self,addr):
print('正在连接服务器[%s]' %addr)
self.addr=addr
但不影响程序员B继续实现其他逻辑,利用反射事先做判断
#from module import FtpClient
f1=FtpClient('192.168.1.1')
if hasattr(f1,'get'): #判断方法是否实现
func_get=getattr(f1,'get')
func_get()
else:
print('---->不存在此方法')
print('处理其他的逻辑')
- 动态导入模块(基于反射当前模块成员)
三、__enter__ 和__exit__
上下文管理协议,
即with语句,为了让一个对象兼容with语句
对对象的操作才会触发
class Foo:
def __enter__(self):
print('__enter__')
return '1234' #会把enter这儿的值返回,若是as赋值,则这需要返回值
def __exit__(self,exc_type,exec_val,exc_tb):
print('__exit__')
with Foo() : #也是类的实例化
print('==>')
结果: #都是绑定到对象的方法,先执行enter方法,再执行exit
__enter__
==>
__exit__
with Foo() as x: #实际就是x=Foo()
print('==>')
print(x)
结果: #在enter那儿返回值给x
__enter__
==>
1234
__exit__
模拟with打开文件:
class Open:
def __init__(self, name,mode='r',encoding='utf-8'): #对象默认执行
self.name = name
self.mode=mode
self.encoding=encoding
self.f=open(self.name,mode=self.mode,encoding=self.encoding)
def __enter__(self): #with上下文管理里会先执行这个
return self.f #返回self.f =open(...)
def __exit__(self, exc_type, exc_val, exc_tb): #with上下文管理里最后执行这个,依次为异常类型、异常的值、异常的追踪信息
print(exc_type)
print(exc_val)
print(exc_tb)
self.f.close() #关闭
return True #加上这个with出错也不会抛异常(表示with自己处理了,不需要系统抛),不加的话with以外的代码也不会执行(程序崩溃)
with Open('c.txt','w') as f: #f=self.f
print(f)
1/0 #1除以0报异常,下面代码就不会运行
print('===>')
f.write('11111\n') #上面代码正常的话就能运行
f.write('22222\n')
#最后执行__exit__
结果:
<_io.TextIOWrapper name='c.txt' mode='w' encoding='utf-8'>
<class 'ZeroDivisionError'>
division by zero
<traceback object at 0x0000026A83D6F788>
关于异常(自定义):

四、__init__ 、__str__
__init__()
在类中__init__()函数叫构造函数,又叫构造方法,也可以叫初始化函数。它的作用就是初始化实例时,初始化传入实例的的默认值。如果不写__init__(),就会调用的默认为空的__init__()。说白了,这个方法不管你写不写,都会调用,一旦实例化就会调
class Chinese:
country = 'China'
def __init__(self, name, age, sex): # p1的实例化过程其实就是调用Chinese的init方法,并把自己传进去了: Chinese.__init__(p1,'egon','18','male')
self.Name = name # 实例化(初始化)过程其实就是依次赋值: p1.Name=name;p1.Age=age,p1.Sex=sex
self.Age = age
self.Sex = sex
def talk(self):
print('%s is talking' %self.Name)
p1=Chinese('egon','18','male') #实际就是:Chinese.__init__(p1,'egon','18','male')
p2=Chinese('alex','9000','female') #实际就是:Chinese.__init__(p1,'egon','18','male')
__init__里面不能有返回值
__str__()
类里面有__str__(self),默认会执行,表示什么都不干打印内存结果,
所以可以自己重写这个函数来返回属性值(list等实例化时就是重写了)
def __str__(self): #重写这个什么都不做的__str__
# print('run __str__')
return 'name:%s age:%s' %(self.name,self.age)
必须有返回值,必须返回字符串类型
额外:---------------------------------------------------------
__str__,__repr__,__format__
改变对象的字符串显示__str__,__repr__
自定制格式化字符串__format__
-
__str__,__repr__
我们先定义一个Student类,打印一个实例:
>>> class Student(object):
... def __init__(self, name):
... self.name = name
...
>>> print(Student('Michael'))
<__main__.Student object at 0x109afb190>
打印出一堆<__main__.Student object at 0x109afb190>,不好看。
怎么才能打印得好看呢?只需要定义好__str__()方法,返回一个好看的字符串就可以了:
>>> class Student(object):
... def __init__(self, name):
... self.name = name
... def __str__(self):
... return 'Student object (name: %s)' % self.name
...
>>> print(Student('Michael'))
Student object (name: Michael)
这样打印出来的实例,不但好看,而且容易看出实例内部重要的数据。
但是细心的朋友会发现直接敲变量不用print,打印出来的实例还是不好看:
>>> s = Student('Michael')
>>> s
<__main__.Student object at 0x109afb310>
这是因为直接显示变量调用的不是__str__(),而是__repr__(),两者的区别是__str__()返回用户看到的字符串,而__repr__()返回程序开发者看到的字符串,也就是说,__repr__()是为调试服务的。
解决办法是再定义一个__repr__()。但是通常__str__()和__repr__()代码都是一样的,所以,有个偷懒的写法:
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name=%s)' % self.name
__repr__ = __str__
- __format__
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))
输出
2016:12:29 12-29-2016
五、__del__
析构方法,析构函数
---》当对象在内存中被释放时,自动触发执行。
此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。
定义:
class Foo:
def __del__(self):
print('执行删除')
f1=Foo()
print('**')
del f1 #正常是程序执行完再删除,再才打印析构函数。但是这里手动先删除释放对象了,所以先执行
print('------->')
结果:
** 执行删除 ------->
用处:
比如在建立数据库连接后,默认执行完不关闭。可以在这里手动执行关闭。做清理操作。
六、__setitem__,__getitem__,__delitem__
当实例中有类似字典的操作触发
用字典的方法来操作时会执行这几个方法。这时key是操作的目标,value是实际值。可以修改函数内容来做想要的操作。
class Foo:
def __init__(self,name):
self.name=name
def __getitem__(self, item):
print("====>",item)
return self.__dict__[item] #通过这样修改成字典取值和可以另外赋值其他没有的属性--->一个接口产生多个对象(属性)
def __setitem__(self, key, value):
self.__dict__[key]=value
print("====>",value) #对象本身不能字典取值,加上setitem就可以字典取值。这里输出赋的值,字典形式设置时会默认执行setitem,不是字典形式不会
def __delitem__(self, key):
self.__dict__.pop(key) #通过这样就修改成了可以删除其他没有属性
print('===>del obj[key]')
def __delattr__(self, item): #不是字典形式的输出
self.__dict__.pop(item)
print('===>del obj.key')
f1=Foo('sb')
f1.name='jh' #不是字典形式,所以不会执行setitem
f1['age']=18
f1['age1']=20
print(f1['age1']) #记住 这样其实拿不到值,需要通过getitem返回值。因为本身字典里面没有age 属性,通过这样改成了字典里可以赋值,所以需要return
print(f1['name']) #原本有name属性,所以可以取到
del f1['age']
del f1.age1 #不同形式
结果:
====> 18
====> 20
====> age1
20
====> name
jh
===>del obj[key]
===>del obj.key
用于需要字典形式操作对象时候,就需要这三个方法了:
def func(obj,key,value):
obj[key]=value
dic={'name':'egon','age':18} #调用这个函数就需要字典操作了
print(dic)
func(dic,'name','666') #obj['name']='666'
print(dic)
结果:
{'name': 'egon', 'age': 18}
{'name': '666', 'age': 18}
当需要把对象传进去,需要字典操作,但对象本身不能字典操作,加上上面三个方法就可以了:
func(f1,'name','666')
print(f1.name)
七、__setattr__,__getattr__,__delattr__
当对对象的属性有. 的操作时触发
1.__getattr__
拦截点号运算。当对未定义的属性名称和实例进行点号运算时,就会用属性名作为字符串调用这个方法。如果继承树可以找到该属性,则不调用此方法
找不到才会执行
2.__setattr__ (__init__ 实例化时有.属性操作,也会触发)
会拦截所有属性的的赋值语句。如果定义了这个方法,self.arrt = value 就会变成self,__setattr__("attr", value).
这个需要注意:当在__setattr__方法内对属性赋值时,不可使用self.attr = value方式,因为会再次调用self,__setattr__("attr", value),这样会形成无穷递归循环,最后导致堆栈溢出异常。

应该通过对属性字典做索引运算来赋值任何实例属性,也就是使用 self.__dict__['name'] = value --> 默认就会这样,若是自定义了setattr (改了就只执行改了的) 则需要加上这个
(使用setattr()也是self.key=value)
3.__delattr__
删除对象下的属性
(__del__是删除对象---程序运行完也会释放对象)
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__,凡是赋值操作都会触发自定义的操作,什么都没写,就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值
f1.z=3
print(f1.__dict__)
#__delattr__ 删除属性的时候会触发
f1.__dict__['a']=3 #我们可以直接修改属性字典,来完成添加/修改属性的操作
del f1.a
print(f1.__dict__)
#__getattr__只有在使用点调用属性且属性不存在的时候才会触发
f1.xxxxxx
输出
----> from setattr
{}
----> from setattr
{}
----> from delattr
{}
----> from getattr---你找的属性不存在
八、二次加工,二次加工标准类型
标准类型:字符串,字典,列表。。。
想自定义一个只能加字符串的列表就需要二次加工标准类型
1.包装
python为大家提供了标准数据类型,以及丰富的内置方法,
但在很多场景下需要基于标准数据类型来定制自己的数据类型,新增/改写方法,这就用到了继承/派生知识
(其他的标准类型均可以通过下面的方式进行二次加工)
class List(list): #继承list所有的属性,也可以派生出自己新的,比如append和mid
def append(self, p_object): #派生自己的append,这里面加了一个类型检查
if not isinstance(p_object,int):
raise TypeError('must be int')
super().append(p_object) #使用super()重用父类
@property #把动词变成名词
def mid(self): #加一个求中间值的函数
index=len(self)//2
return self[index]
l=List([1,2,3,4])
print(l)
l.append(5)
print(l)
# l.append('1111111') #报错,必须为int类型
print(l.mid)
#其余的方法还是继续都继承原list的
l.insert(0,-123)
print(l)
l.clear()
print(l)
输出
[1, 2, 3, 4] [1, 2, 3, 4, 5] 3 [-123, 1, 2, 3, 4, 5] []
2.授权 (自己没有的想用别人的,反射过来)
授权是包装的一个特性, 包装一个类型通常是对已存在类型的一些定制。这种做法可以新建,修改或删除原有产品的功能,其它的则保持原样。
授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。(类才有继承,修改函数功能就没办法用继承,只能授权处理)
实现授权的关键点就是覆盖 __getattr__ 方法
--> 找不到的函数属性就会执行__getattr__ -->然后getattr里面利用反射返回存在的函数,这样就能执行了
class List:
def __init__(self,seq,permission=False):
self.seq=seq
self.permission=permission
def clear(self):
if not self.permission: #self.permission是自带的属性,所以需要重写__init__
raise PermissionError('not allow the operation')
self.seq.clear()
def __getattr__(self, item): #找不到就会执行这里
return getattr(self.seq,item) #反射回存在的函数
def __str__(self):
return str(self.seq)
l=List([1,2,3])
# l.clear() #此时没有权限,抛出异常
l.permission=True
print(l)
l.clear()
print(l)
#基于授权,获得insert方法 (去__getattr__里找的)
l.insert(0,-123)
print(l)
结果:
[1, 2, 3] [] [-123]
九、迭代器协议:
__next__ 、__iter__
可以通过这两个函数模拟迭代器:
class Foo:
def __init__(self,n,stop): #定义停止位置
self.n=n
self.stop=stop
def __next__(self):
if self.n >= self.stop:
raise StopIteration #抛异常,for循环会捕捉这个异常暂停
x=self.n
self.n+=1
return x #如果直接return self.n下次就没值了
def __iter__(self): #迭代器的__iter__是本身
return self
obj=Foo(0,5) #0 1 2 3 4
print(next(obj)) #obj.__next__()
print(next(obj)) #obj.__next__()
结果:
0
1
from collections import Iterator
print(isinstance(obj,Iterator)) #判断是否是迭代器
结果:
True
for i in obj: #还可以循环取值
print(i)
结果:
0
1
2
3
4
十、__call__
一般情况下,对象不可被调用
可以在类里加一个绑定到对象的方法__call__就能使对象后面加括号,触发执行(可以被调用) --->此时也会执行__call__里的代码
注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;
而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
__call__里面有返回值,默认None
class Foo:
def __init__(self):
pass
def __call__(self, *args, **kwargs):
print('__call__')
obj = Foo() # 执行 __init__
obj() # 执行 __call__,对象被调用
结果:
__call__
print(type(Foo))
结果:
<class 'type'> #python里一切皆对象,类的类就是type,type里面有__call__(),最终都是执行函数
__doc__:查看文档信息--注释信息 -- if not value.__doc__:
十一、eval(),exec()
1、eval()
- 函数的作用
计算指定表达式的值。也就是说它要执行的Python代码只能是单个运算表达式(注意eval不支持任意形式的赋值操作),而不能是复杂的代码逻辑,这一点和lambda表达式比较相似。
- 函数定义
eval(expression, globals=None, locals=None)
- 参数说明:
- expression:必选参数,可以是字符串,也可以是一个任意的code对象实例(可以通过compile函数创建)。如果它是一个字符串,它会被当作一个(使用globals和locals参数作为全局和本地命名空间的)Python表达式进行分析和解释。
- globals:可选参数,表示全局命名空间(存放全局变量),如果被提供,则必须是一个字典对象。
- locals:可选参数,表示当前局部命名空间(存放局部变量),如果被提供,可以是任何映射对象。如果该参数被忽略,那么它将会取与globals相同的值。
- 如果globals与locals都被忽略,那么它们将取eval()函数被调用环境下的全局命名空间和局部命名空间。
- 返回值:
- 如果expression是一个code对象,且创建该code对象时,compile函数的mode参数是'exec',那么eval()函数的返回值是None;
- 否则,如果expression是一个输出语句,如print(),则eval()返回结果为None;
- 否则,expression表达式的结果就是eval()函数的返回值;
x = 10
def func():
y = 20
a = eval('x + y')
print('a: ', a)
b = eval('x + y', {'x': 1, 'y': 2})
print('b: ', b)
c = eval('x + y', {'x': 1, 'y': 2}, {'y': 3, 'z': 4})
print('c: ', c)
d = eval('print(x, y)')
print('d: ', d)
func()
输出
a: 30 b: 3 c: 4 10 20 d: None
- 对输出结果的解释:
- 对于变量a,eval函数的globals和locals参数都被忽略了,因此变量x和变量y都取得的是eval函数被调用环境下的作用域中的变量值,即:x = 10, y = 20,a = x + y = 30
- 对于变量b,eval函数只提供了globals参数而忽略了locals参数,因此locals会取globals参数的值,即:x = 1, y = 2,b = x + y = 3
- 对于变量c,eval函数的globals参数和locals都被提供了,那么eval函数会先从全部作用域globals中找到变量x, 从局部作用域locals中找到变量y,即:x = 1, y = 3, c = x + y = 4
- 对于变量d,因为print()函数不是一个计算表达式,没有计算结果,因此返回值为None
2.exec()
- 函数的作用:
动态执行Python代码。也就是说exec可以执行复杂的Python代码,而不像eval函数那么样只能计算一个表达式的值。
- 函数定义:
exec(object[, globals[, locals]])
- 参数说明:
- object:必选参数,表示需要被指定的Python代码。它必须是字符串或code对象。如果object是一个字符串,该字符串会先被解析为一组Python语句,然后在执行(除非发生语法错误)。如果object是一个code对象,那么它只是被简单的执行。
- globals:可选参数,同eval函数
- locals:可选参数,同eval函数
- 返回值:
exec函数的返回值永远为None.
需要说明的是在Python 2中exec不是函数,而是一个内置语句(statement),但是Python 2中有一个execfile()函数。可以理解为Python 3把exec这个statement和execfile()函数的功能够整合到一个新的exec()函数中去了:
- eval()函数与exec()函数的区别:
- eval()函数只能计算单个表达式的值,而exec()函数可以动态运行代码段。
- eval()函数可以有返回值,而exec()函数返回值永远为None。
x = 10
def func():
y = 20
a = exec('x + y')
print('a: ', a)
b = exec('x + y', {'x': 1, 'y': 2})
print('b: ', b)
c = exec('x + y', {'x': 1, 'y': 2}, {'y': 3, 'z': 4})
print('c: ', c)
d = exec('print(x, y)')
print('d: ', d)
func()
输出
a: None b: None c: None 10 20 d: None
x = 10
expr = """
z = 30
sum = x + y + z
print(sum)
"""
def func():
y = 20
exec(expr)
exec(expr, {'x': 1, 'y': 2})
exec(expr, {'x': 1, 'y': 2}, {'y': 3, 'z': 4})
func()
输出
60 33 34
- 对输出结果的解释:
前两个输出跟上面解释的eval函数执行过程一样,不做过多解释。关于最后一个数字34,我们可以看出是:x = 1, y = 3是没有疑问的。关于z为什么还是30而不是4,这其实也很简单,我们只需要在理一下代码执行过程就可以了,其执行过程相当于:
x = 1
y = 2
def func():
y = 3
z = 4
z = 30
sum = x + y + z
print(sum)
func()
十二、元类
1.元类的定义
元类是用来控制类的创建行为(如何创建类的),正如类是创建对象的模板一样,元类是类的类,是类的模版。控制class
元类的实例化的结果为我们用class定义的类,正如类的实例为对象 ( f1对象是Foo类的一个实例,Foo类是 type 类的一个实例)
type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象

2.创建类的方式 (执行类的过程就是执行类体的代码,产生名称空间)
准备工作:
创建类的三要素:
1 类名
2 类的父类
3 类体
方式:
1. 使用class关键字
class Chinese(object):
country='China'
def __init__(self,name,age):
self.name=name
self.age=age
def talk(self):
print('%s is talking' %self.name)
2. 手动模拟class创建类的过程:
将创建类的步骤拆分开,手动去创建
#类名
class_name='Chinese'
#类的父类
class_bases=(object,)
#类体
class_body="""
country='China'
def __init__(self,name,age):
self.name=name
self.age=age
def talk(self):
print('%s is talking' %self.name)
"""
步骤一(先处理类体->名称空间):类体定义的名字都会存放于类的名称空间中(一个局部的名称空间),
我们可以事先定义一个空字典,然后用exec去执行类体的代码(exec产生名称空间的过程与真正的class过程类似,只是后者会将__开头的属性变形),生成类的局部名称空间,即填充字典
class_dic={}
exec(class_body,globals(),class_dic)
print(class_dic)
#{'country': 'China', 'talk': <function talk at 0x101a560c8>, '__init__': <function __init__ at 0x101a56668>}
步骤二:调用元类type(也可以自定义)来产生类Chinense
Foo=type(class_name,class_bases,class_dic) #实例化type得到对象Foo,即我们用class定义的类Foo print(Foo) print(type(Foo)) print(isinstance(Foo,type)) ''' <class '__main__.Chinese'> <class 'type'> True '''
我们看到,type 接收三个参数:
-
第 1 个参数是字符串 ‘Foo’,表示类名
-
第 2 个参数是元组 (object, ),表示所有的父类
-
第 3 个参数是字典,这里是一个空字典,表示没有定义属性和方法
补充:若Foo类有继承,即class Foo(Bar):.... 则等同于type('Foo',(Bar,),{})
一个类没有声明自己的元类,默认他的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元类
----> 使用metaclass=Mytype使用元类
自定制元类精简版
class Mytype(type): #自定义元类,写上继承type
def __init__(self,what,bases=None,dict=None):
print(what,bases,dict)
def __call__(self, *args, **kwargs):
print('--->')
obj=object.__new__(self)
self.__init__(obj,*args,**kwargs)
return obj
class Room(metaclass=Mytype): #继承自定义元类
def __init__(self,name):
self.name=name
r1=Room('alex')
print(r1.__dict__)

浙公网安备 33010602011771号