文章目录

  • 一、isinstance和issubclass
  • 二、反射
    • 1 什么是反射
    • 2 python面向对象中的反射
    • 3 反射的好处
  • 三 、__ setattr__,__ delattr__,__ getattr__
  • 四、__ getattribute__
  • 五、描述符(__ get__,__ set__,__ delete__)
    • 1 什么是描述符
    • 2 描述符的作用
    • 3 描述符种类
    • 4 注意事项
    • 5 描述符的使用
    • 6 描述符总结
  • 六、再看property
  • 七、__ setitem__,__ getitem__,__ delitem__
  • 八、__ str__,__ repr__,__ format__
  • 九、__next__和__iter__实现迭代器协议
  • 十、__ doc__
  • 十一、__ module__和__class__
  • 十二、__ del__
  • 十三、__ call__
  • 十四、__ enter__和__ exit__
  • 十五、__ len__
  • 十六、__ hash__
  • 十七、__ eq__
  • 十八、__ new__和元类metaclass
    • 1 什么是元类
    • 2 class关键字创建类的流程分析
    • 3 自定义元类控制类的创建
    • 4 自定义元类控制类的调用
    • 5 属性查找

一、isinstance和issubclass

isinstance(obj,cls)检查是否obj是否是类 cls 的对象

class Foo(object):passobj = Foo()isinstance(obj, Foo)

issubclass(sub, super)检查sub类是否是 super 类的派生类

class Foo(object):passclass Bar(Foo):passissubclass(Bar, Foo)

二、反射

1 什么是反射

反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。

2 python面向对象中的反射

通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)

四个可以实现自省的函数
下列方法适用于类和对象(一切皆对象,类本身也是一个对象)

  • hasattr(obj, name):判断object中有没有一个name字符串对应的方法或属性
  • getattr(obj, name, default=None):从对象获取命名属性;getattr(x,‘y’)等价于x.y。当一个默认参数被给定时,当属性没有被指定时,它被返回存在;没有它,在这种情况下会引发异常。
  • setattr(x, y, v):将给定对象上的命名属性设置为指定值。setattr(x,‘y’,v)等价于“x.y=v”
  • delattr(x, y):从给定对象中删除命名属性。delattr(x,‘y’)等价于“del x.y”

四个方法的使用演示:

class BlackMedium:feature='Ugly'def __init__(self,name,addr):self.name=nameself.addr=addrdef 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+'sb')
print(b1.__dict__)
print(b1.show_name(b1))#删除属性
delattr(b1,'addr')
delattr(b1,'show_name')
delattr(b1,'show_name111')#不存在,则报错print(b1.__dict__)'''
AttributeError: show_name111
True
True
万成置地
万成置地 黑中介租房子啦,傻逼才租呢
不存在啊
{'name': '万成置地', 'addr': '回龙观天露园', 'sb': True, 'show_name': <function <lambda> at 0x000001CA326CC430>}
万成置地sb
'''

类也是对象:

class Foo(object):staticField = "old boy"def __init__(self):self.name = 'wupeiqi'def func(self):return 'func'@staticmethoddef bar():return 'bar'print(getattr(Foo, 'staticField'))
print(getattr(Foo, 'func'))
print(getattr(Foo, 'bar'))'''
old boy
<function Foo.func at 0x000001A6B27AC5E0>
<function Foo.bar at 0x000001A6B27ACB80>
'''

反射当前模块成员:

import sysdef 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 0x000001F90DF9C670>
'''

导入其他模块,利用反射查找该模块是否存在某个方法

def test():print('from the test')
"""
程序目录:module_test.pyindex.py当前文件:index.py
"""import module_test as obj#obj.test()print(hasattr(obj,'test'))getattr(obj,'test')()

获取当前模块:

current_module=__import__(__name__)
x=111
print(hasattr(current_module,"x"))
print(getattr(current_module,"x"))

3 反射的好处

好处一:可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种‘后期绑定’,什么意思?即你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能

例如还未实现全部功能的代码:

class FtpClient:'ftp客户端,但是还么有实现具体的功能'def __init__(self,addr):print('正在连接服务器[%s]' %addr)self.addr=addr

但不影响其他人的代码编写:

#from module import FtpClient
f1=FtpClient('192.168.1.1')
if hasattr(f1,'get'):func_get=getattr(f1,'get')func_get()
else:print('---->不存在此方法')print('处理其他的逻辑')

好处二:动态导入模块(基于反射当前模块成员)


三 、__ setattr__,__ delattr__,__ getattr__

class Foo:x=1def __init__(self,y):self.y=ydef __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:你找的属性不存在
'''

四、__ getattribute__

回顾__getattr__:

class Foo:def __init__(self,x):self.x=xdef __getattr__(self, item):print('执行的是我')# return self.__dict__[item]f1=Foo(10)
print(f1.x)
f1.xxxxxx #不存在的属性访问,触发__getattr__'''
10
执行的是我
'''

__ getattribute__的用法:

class Foo:def __init__(self,x):self.x=xdef __getattribute__(self, item):print('不管是否存在,我都会执行')f1=Foo(10)
f1.x
f1.xxxxxx'''
不管是否存在,我都会执行
不管是否存在,我都会执行
'''

当__getattribute__与__getattr__同时存在,只会执行__getattrbute__,除非__getattribute__在执行过程中抛出异常AttributeError

class Foo:def __init__(self,x):self.x=xdef __getattr__(self, item):print('执行的是我')# return self.__dict__[item]def __getattribute__(self, item):print('不管是否存在,我都会执行')raise AttributeError('哈哈')f1=Foo(10)
f1.x
f1.xxxxxx'''
不管是否存在,我都会执行
执行的是我
不管是否存在,我都会执行
执行的是我
'''

五、描述符(__ get__,__ set__,__ delete__)

1 什么是描述符

描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__ set__(),__ delete__()中的一个,这也被称为描述符协议。

  • __ get__():调用一个属性时,触发
  • __ set__():为一个属性赋值时,触发
  • __ delete__():采用del删除属性时,触发

定义一个描述符:

class Foo: #在python3中Foo是新式类,它实现了三种方法,这个类就被称作一个描述符def __get__(self, instance, owner):passdef __set__(self, instance, value):passdef __delete__(self, instance):pass

2 描述符的作用

描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)

何时何地会触发描述符的三个方法的执行?

#描述符Str
class Str:def __get__(self, instance, owner):print('Str调用')def __set__(self, instance, value):print('Str设置...')def __delete__(self, instance):print('Str删除...')#描述符Int
class Int:def __get__(self, instance, owner):print('Int调用')def __set__(self, instance, value):print('Int设置...')def __delete__(self, instance):print('Int删除...')class People:name=Str()age=Int()def __init__(self,name,age): #name被Str类代理,age被Int类代理,self.name=nameself.age=age#何地?:定义成另外一个类的类属性#何时?:且看下列演示p1=People('alex',18)#描述符Str的使用
p1.name
p1.name='egon'
del p1.name#描述符Int的使用
p1.age
p1.age=18
del p1.age#我们来瞅瞅到底发生了什么
print(p1.__dict__)
print(People.__dict__)#补充
print(type(p1) == People) #type(obj)其实是查看obj是由哪个类实例化来的
print(type(p1).__dict__ == People.__dict__)'''
Str设置...
Int设置...
Str调用
Str设置...
Str删除...
Int调用
Int设置...
Int删除...
{}
{'__module__': '__main__', 'name': <__main__.Str object at 0x000001FF6D153FD0>, 'age': <__main__.Int object at 0x000001FF6D153FA0>, '__init__': <function People.__init__ at 0x000001FF6D151700>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None}
True
True
'''

3 描述符种类

分为两种:
一 数据描述符:至少实现了__get__()和__set__()

class Foo:def __set__(self, instance, value):print('set')def __get__(self, instance, owner):print('get')

二 非数据描述符:没有实现__set__()

class Foo:def __get__(self, instance, owner):print('get')

4 注意事项

  • 一、描述符本身应该定义成新式类,被代理的类也应该是新式类
  • 二、必须把描述符定义成这个类的类属性,不能为定义到构造函数中
  • 三、要严格遵循该优先级,优先级由高到底分别是:类属性 > 数据描述符 > 实例属性 > 非数据描述符 > 找不到的属性触发__getattr__()

5 描述符的使用

如果我们的类有很多属性,你仍然采用在定义一堆类属性的方式去实现,low

类的装饰器(无参):

def decorate(cls):print('类的装饰器开始运行啦------>')return cls@decorate #无参:People=decorate(People)
class People:def __init__(self,name,age,salary):self.name=nameself.age=ageself.salary=salaryp1=People('egon',18,3333.3)'''
类的装饰器开始运行啦------>
'''

类的装饰器(有参):

def typeassert(**kwargs):def decorate(cls):print('类的装饰器开始运行啦------>',kwargs)return clsreturn decorate
@typeassert(name=str,age=int,salary=float)
#有参:
# 1.运行typeassert(...)返回结果是decorate,此时参数都传给kwargs
# 2.People=decorate(People)
class People:def __init__(self,name,age,salary):self.name=nameself.age=ageself.salary=salaryp1=People('egon',18,3333.3)'''
类的装饰器开始运行啦------> {'name': <class 'str'>, 'age': <class 'int'>, 'salary': <class 'float'>}
'''

描述符的使用:

class Typed:def __init__(self,name,expected_type):self.name=nameself.expected_type=expected_typedef __get__(self, instance, owner):print('get--->',instance,owner)if instance is None:return selfreturn instance.__dict__[self.name]def __set__(self, instance, value):print('set--->',instance,value)if not isinstance(value,self.expected_type):raise TypeError('Expected %s' %str(self.expected_type))instance.__dict__[self.name]=valuedef __delete__(self, instance):print('delete--->',instance)instance.__dict__.pop(self.name)def typeassert(**kwargs):def decorate(cls):print('类的装饰器开始运行啦------>',kwargs)for name,expected_type in kwargs.items():setattr(cls,name,Typed(name,expected_type))return clsreturn decorate
@typeassert(name=str,age=int,salary=float) #有参:1.运行typeassert(...)返回结果是decorate,此时参数都传给kwargs 2.People=decorate(People)
class People:def __init__(self,name,age,salary):self.name=nameself.age=ageself.salary=salaryprint(People.__dict__)
p1=People('egon',18,3333.3)'''
类的装饰器开始运行啦------> {'name': <class 'str'>, 'age': <class 'int'>, 'salary': <class 'float'>}
{'__module__': '__main__', '__init__': <function People.__init__ at 0x00000243FCFB1790>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None, 'name': <__main__.Typed object at 0x00000243FCFB53D0>, 'age': <__main__.Typed object at 0x00000243FCFB5310>, 'salary': <__main__.Typed object at 0x00000243FCFB52B0>}
set---> <__main__.People object at 0x00000243FCFB5FD0> egon
set---> <__main__.People object at 0x00000243FCFB5FD0> 18
set---> <__main__.People object at 0x00000243FCFB5FD0> 3333.3
'''

6 描述符总结

描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod,@staticmethd,@property甚至是__slots__属性

描述父是很多高级库和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件.


六、再看property

一个静态属性property本质就是实现了get,set,delete三种方法

用法一:

class Foo:@propertydef AAA(self):print('get的时候运行我啊')@AAA.setterdef AAA(self,value):print('set的时候运行我啊')@AAA.deleterdef AAA(self):print('delete的时候运行我啊')#只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter
f1=Foo()
f1.AAA
f1.AAA='aaa'
del f1.AAA'''
get的时候运行我啊
set的时候运行我啊
delete的时候运行我啊
'''

用法二:

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'''
get的时候运行我啊
set的时候运行我啊
delete的时候运行我啊
'''

实例:

class Goods:def __init__(self):# 原价self.original_price = 100# 折扣self.discount = 0.8@propertydef price(self):# 实际价格 = 原价 * 折扣new_price = self.original_price * self.discountreturn new_price@price.setterdef price(self, value):self.original_price = value@price.deleterdef price(self):del self.original_priceobj = Goods()
obj.price         # 获取商品价格
obj.price = 200   # 修改商品原价
print(obj.price)
del obj.price     # 删除商品原价

七、__ setitem__,__ getitem__,__ delitem__

用于索引操作,如字典等,分别实现设置、获取、删除等功能

class Foo:def __init__(self,name):self.name=namedef __getitem__(self, item):print(self.__dict__[item])def __setitem__(self, key, value):self.__dict__[key]=valuedef __delitem__(self, key):print('del obj[key]时,我执行')self.__dict__.pop(key)def __delattr__(self, item):print('del obj.key时,我执行')self.__dict__.pop(item)f1=Foo('sb')
f1['age']=18 # 自动触发执行__setitem__方法
f1['age1']=19
del f1.age1 # 自动触发执行__delattr__方法
del f1['age'] # 自动触发执行__delitem__方法
f1['name']='alex'
print(f1.__dict__)'''
del obj.key时,我执行
del obj[key]时,我执行
{'name': 'alex'}
'''

八、__ str__,__ repr__,__ format__

改变对象的字符串显示__str__,__repr __,如果一个类中定义了__str __方法,那么在打印对象时默认输出该方法的返回值

自定制格式化字符串__format__

#_*_coding:utf-8_*_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=nameself.addr=addrself.type=typedef __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_specif 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('oldboy1','北京','私立')
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'))'''
from repr:  School(oldboy1,北京)
from str:  (oldboy1,北京)
(oldboy1,北京)
oldboy1-北京-私立
私立:oldboy1:北京
私立/北京/oldboy1
oldboy1-北京-私立
'''

%s和%r:

class B:def __str__(self):return 'str : class B'def __repr__(self):return 'repr : class B'b=B()
print('%s'%b)
print('%r'%b)'''
str : class B
repr : class B
'''

九、__next__和__iter__实现迭代器协议

如果类中有__iter__方法,则该类是一个可迭代对象

其中 对象.__iter__的返回值是迭代器

示例:

class Foo:def __init__(self,x):self.x=xdef __iter__(self):return selfdef __next__(self):n=self.xself.x+=1return self.xf=Foo(3)
for i in f:print(i)

十、__ doc__

表示类的描述信息,且该属性无法被继承

class Foo:'我是描述信息'passprint(Foo.__doc__)'''
我是描述信息
'''

十一、__ module__和__class__

__ module__ 表示当前操作的对象在那个模块

__ class__ 表示当前操作的对象的类是什么

示例文件lib/aa.py:

class C:def __init__(self):self.name = ‘SB'

index.py:

from lib.aa import Cobj = C()
print obj.__module__  # 输出 lib.aa,即:输出模块
print obj.__class__      # 输出 lib.aa.C,即:输出类

十二、__ del__

析构方法,当对象在内存中被释放时,自动触发执行。

注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。因此什么时候执行并不知道

示例:

class Foo:def __del__(self):print('执行我啦')f1=Foo()
del f1
print('------->')#输出结果
执行我啦
------->

十三、__ call__

对象后面加括号,触发执行。

注:

  • 构造方法__init__的执行是由创建对象触发的,即:对象 = 类名() ;
  • 而对于 __ call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
class Foo:def __init__(self):passdef __call__(self, *args, **kwargs):print('__call__')obj = Foo() # 执行 __init__
obj()       # 执行 __call__'''
__call__
'''

十四、__ enter__和__ exit__

我们知道在操作文件对象的时候可以这么写:

with open('a.txt') as f:'代码块'

上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法

class Open:def __init__(self,name):self.name=namedef __enter__(self):print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')# return selfdef __exit__(self, exc_type, exc_val, exc_tb):print('with中代码块执行完毕时执行我啊')with Open('a.txt') as f:print('=====>执行代码块')# print(f,f.name)'''
出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量
=====>执行代码块
with中代码块执行完毕时执行我啊
'''

__ exit__()中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法执行

class Open:def __init__(self,name):self.name=namedef __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) #------------------------------->不会执行'''
出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量
=====>执行代码块
with中代码块执行完毕时执行我啊
<class 'AttributeError'>
***着火啦,救火啊***
<traceback object at 0x00000261F6ABDB80>
Traceback (most recent call last):File "D:\pystudy\fullstack\作业练习\脚本.py", line 18, in <module>raise AttributeError('***着火啦,救火啊***')
AttributeError: ***着火啦,救火啊***
'''

如果__exit()返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行

class Open:def __init__(self,name):self.name=namedef __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 Truewith Open('a.txt') as f:print('=====>执行代码块')raise AttributeError('***着火啦,救火啊***')
print('0'*100) #------------------------------->会执行
'''
出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量
=====>执行代码块
with中代码块执行完毕时执行我啊
<class 'AttributeError'>
***着火啦,救火啊***
<traceback object at 0x00000240B7E8DC00>
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
'''

__ enter__和__ exit__的用途:

  1. 使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预

  2. 在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处


十五、__ len__

class A:def __init__(self):self.a = 1self.b = 2def __len__(self):return len(self.__dict__)
a = A()
print(len(a))'''
2
'''

十六、__ hash__

class A:def __init__(self):self.a = 1self.b = 2def __hash__(self):return hash(str(self.a)+str(self.b))
a = A()
print(hash(a))'''
-9051584000431974590
'''

十七、__ eq__

class A:def __init__(self):self.a = 1self.b = 2def __eq__(self,obj):if  self.a == obj.a and self.b == obj.b:return True
a = A()
b = A()
print(a == b)'''
True
'''

一道面试题:

class Person:def __init__(self,name,age,sex):self.name = nameself.age = ageself.sex = sexdef __hash__(self):return hash(self.name+self.sex)def __eq__(self, other):if self.name == other.name and self.sex == other.sex:return Truep_lst = []
for i in range(84):p_lst.append(Person('egon',i,'male'))print(p_lst)
print(set(p_lst))
'''
[<__main__.Person object at 0x0000016F4A143FD0>, <__main__.Person object at 0x0000016F4A143F70>, <__main__.Person object at 0x0000016F4A143E80>, <__main__.Person object at 0x0000016F4A143DC0>, <__main__.Person object at 0x0000016F4A143D60>, <__main__.Person object at 0x0000016F4A143790>, <__main__.Person object at 0x0000016F4A143730>, <__main__.Person object at 0x0000016F4A1436D0>, <__main__.Person object at 0x0000016F4A143670>, <__main__.Person object at 0x0000016F4A143610>, <__main__.Person object at 0x0000016F4A1435B0>, <__main__.Person object at 0x0000016F4A143550>, <__main__.Person object at 0x0000016F4A1434F0>, <__main__.Person object at 0x0000016F4A143490>, <__main__.Person object at 0x0000016F4A143430>, <__main__.Person object at 0x0000016F4A1433D0>, <__main__.Person object at 0x0000016F4A143370>, <__main__.Person object at 0x0000016F4A143310>, <__main__.Person object at 0x0000016F4A1432B0>, <__main__.Person object at 0x0000016F4A143250>, <__main__.Person object at 0x0000016F4A1431F0>, <__main__.Person object at 0x0000016F4A143190>, <__main__.Person object at 0x0000016F4A143130>, <__main__.Person object at 0x0000016F4A1430D0>, <__main__.Person object at 0x0000016F4A143070>, <__main__.Person object at 0x0000016F4A142FD0>, <__main__.Person object at 0x0000016F4A142F70>, <__main__.Person object at 0x0000016F4A142F10>, <__main__.Person object at 0x0000016F4A142EB0>, <__main__.Person object at 0x0000016F4A142E50>, <__main__.Person object at 0x0000016F4A142DF0>, <__main__.Person object at 0x0000016F4A142D90>, <__main__.Person object at 0x0000016F4A142D30>, <__main__.Person object at 0x0000016F4A142CD0>, <__main__.Person object at 0x0000016F4A142C70>, <__main__.Person object at 0x0000016F4A142C10>, <__main__.Person object at 0x0000016F4A142BB0>, <__main__.Person object at 0x0000016F4A142880>, <__main__.Person object at 0x0000016F4A142820>, <__main__.Person object at 0x0000016F4A1427C0>, <__main__.Person object at 0x0000016F4A142760>, <__main__.Person object at 0x0000016F4A142700>, <__main__.Person object at 0x0000016F4A1426A0>, <__main__.Person object at 0x0000016F4A142640>, <__main__.Person object at 0x0000016F4A1425E0>, <__main__.Person object at 0x0000016F4A142580>, <__main__.Person object at 0x0000016F4A142520>, <__main__.Person object at 0x0000016F4A1424C0>, <__main__.Person object at 0x0000016F4A142460>, <__main__.Person object at 0x0000016F4A142400>, <__main__.Person object at 0x0000016F4A1423A0>, <__main__.Person object at 0x0000016F4A142340>, <__main__.Person object at 0x0000016F4A1422E0>, <__main__.Person object at 0x0000016F4A142130>, <__main__.Person object at 0x0000016F4A1420D0>, <__main__.Person object at 0x0000016F4A142070>, <__main__.Person object at 0x0000016F4A127AC0>, <__main__.Person object at 0x0000016F4A127B50>, <__main__.Person object at 0x0000016F4A127BB0>, <__main__.Person object at 0x0000016F4A127C10>, <__main__.Person object at 0x0000016F4A127D00>, <__main__.Person object at 0x0000016F49F2B0A0>, <__main__.Person object at 0x0000016F49F2B070>, <__main__.Person object at 0x0000016F49F2BC10>, <__main__.Person object at 0x0000016F4A0C0A90>, <__main__.Person object at 0x0000016F49F25130>, <__main__.Person object at 0x0000016F49EFBBB0>, <__main__.Person object at 0x0000016F49EFBB50>, <__main__.Person object at 0x0000016F49A8B0D0>, <__main__.Person object at 0x0000016F4A145580>, <__main__.Person object at 0x0000016F4A145520>, <__main__.Person object at 0x0000016F4A1454C0>, <__main__.Person object at 0x0000016F4A145460>, <__main__.Person object at 0x0000016F4A145400>, <__main__.Person object at 0x0000016F4A1453A0>, <__main__.Person object at 0x0000016F4A145340>, <__main__.Person object at 0x0000016F4A1452E0>, <__main__.Person object at 0x0000016F4A145280>, <__main__.Person object at 0x0000016F4A145220>, <__main__.Person object at 0x0000016F4A1451C0>, <__main__.Person object at 0x0000016F4A145160>, <__main__.Person object at 0x0000016F4A145100>, <__main__.Person object at 0x0000016F4A1450A0>, <__main__.Person object at 0x0000016F4A145040>]
{<__main__.Person object at 0x0000016F4A143FD0>}'''

十八、__ new__和元类metaclass

1 什么是元类

python中一切皆为对象。让我们先定义一个类:

class OldboyTeacher(object):school='oldboy'def __init__(self,name,age):self.name=nameself.age=agedef say(self):print('%s says welcome to the oldboy to learn Python' %self.name)

所有的对象都是实例化或者说调用类而得到的(调用类的过程称为类的实例化),比如对象t1是调用类OldboyTeacher得到的

t1=OldboyTeacher('egon',18)
print(type(t1)) #查看对象t1的类是<class '__main__.OldboyTeacher'>

如果一切皆为对象,那么类OldboyTeacher本质也是一个对象,既然所有的对象都是调用类得到的,那么OldboyTeacher必然也是调用了一个类得到的,这个类称为元类

于是我们可以推导出===>产生OldboyTeacher的过程一定发生了:OldboyTeacher=元类(…)

print(type(OldboyTeacher)) # 结果为<class 'type'>,证明是调用了type这个元类而产生的OldboyTeacher,即默认的元类为type

2 class关键字创建类的流程分析

上文我们基于python中一切皆为对象的概念分析出:我们用class关键字定义的类本身也是一个对象,负责产生该对象的类称之为元类(元类可以简称为类的类),内置的元类为type

class关键字在帮我们创建类时,必然帮我们调用了元类OldboyTeacher=type(…),那调用type时传入的参数是什么呢?必然是类的关键组成部分,一个类有三大组成部分,分别是:

  1. 类名class_name=‘OldboyTeacher’
  2. 基类们class_bases=(object,)
  3. 类的名称空间class_dic,类的名称空间是执行类体代码而得到的

调用type时会依次传入以上三个参数

综上,class关键字帮我们创建一个类应该细分为以下四个过程

补充:exec的用法:

三个参数:

  • 参数一:包含一系列python代码的字符串
  • 参数二:全局作用域(字典形式),如果不指定,默认为globals()
  • 参数三:局部作用域(字典形式),如果不指定,默认为locals()

可以把exec命令的执行当成是一个函数的执行,会将执行期间产生的名字存放于局部名称空间中

g={'x':1,'y':2
}
l={}exec('''
global x,z
x=100
z=200m=300
''',g,l)print(g) #{'x': 100, 'y': 2,'z':200,......}
print(l) #{'m': 300}

3 自定义元类控制类的创建

一个类没有声明自己的元类,默认他的元类就是type,除了使用内置元类type,我们也可以通过继承type来自定义元类,然后使用metaclass关键字参数为一个类指定元类:

class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类passclass OldboyTeacher(object,metaclass=Mymeta): # OldboyTeacher=Mymeta('OldboyTeacher',(object),{...})school='oldboy'def __init__(self,name,age):self.name=nameself.age=agedef say(self):print('%s says welcome to the oldboy to learn Python' %self.name)

自定义元类可以控制类的产生过程,类的产生过程其实就是元类的调用过程,即OldboyTeacher=Mymeta(‘OldboyTeacher’,(object),{…}),调用Mymeta会先产生一个空对象OldoyTeacher,然后连同调用Mymeta括号内的参数一同传给Mymeta下的__init__方法,完成初始化,于是我们可以:

class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类def __init__(self,class_name,class_bases,class_dic):# print(self) #<class '__main__.OldboyTeacher'># print(class_bases) #(<class 'object'>,)# print(class_dic) #{'__module__': '__main__', '__qualname__': 'OldboyTeacher', 'school': 'oldboy', '__init__': <function OldboyTeacher.__init__ at 0x102b95ae8>, 'say': <function OldboyTeacher.say at 0x10621c6a8>}super(Mymeta, self).__init__(class_name, class_bases, class_dic)  # 重用父类的功能if class_name.islower():raise TypeError('类名%s请修改为驼峰体' %class_name)if '__doc__' not in class_dic or len(class_dic['__doc__'].strip(' \n')) == 0:raise TypeError('类中必须有文档注释,并且文档注释不能为空')class OldboyTeacher(object,metaclass=Mymeta): # OldboyTeacher=Mymeta('OldboyTeacher',(object),{...})"""类OldboyTeacher的文档注释"""school='oldboy'def __init__(self,name,age):self.name=nameself.age=agedef say(self):print('%s says welcome to the oldboy to learn Python' %self.name)

4 自定义元类控制类的调用

调用一个对象,就是触发对象所在类中的__call__方法的执行,如果把OldboyTeacher也当做一个对象,那么在OldboyTeacher这个对象的类中也必然存在一个__call__方法

class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类def __call__(self, *args, **kwargs):print(self) #<class '__main__.OldboyTeacher'>print(args) #('egon', 18)print(kwargs) #{}return 123class OldboyTeacher(object,metaclass=Mymeta):school='oldboy'def __init__(self,name,age):self.name=nameself.age=agedef say(self):print('%s says welcome to the oldboy to learn Python' %self.name)# 调用OldboyTeacher就是在调用OldboyTeacher类中的__call__方法
# 然后将OldboyTeacher传给self,溢出的位置参数传给*,溢出的关键字参数传给**
# 调用OldboyTeacher的返回值就是调用__call__的返回值
t1=OldboyTeacher('egon',18)
print(t1) '''
<class '__main__.OldboyTeacher'>
('egon', 18)
{}
123
'''

默认地,调用t1=OldboyTeacher(‘egon’,18)会做三件事:

  1. 产生一个空对象obj
  2. 调用__init__方法初始化对象obj
  3. 返回初始化好的obj

对应着,OldboyTeacher类中的__call__方法也应该做这三件事

class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类def __call__(self, *args, **kwargs): #self=<class '__main__.OldboyTeacher'>#1、调用__new__产生一个空对象objobj=self.__new__(self) # 此处的self是类OldoyTeacher,必须传参,代表创建一个OldboyTeacher的对象obj#2、调用__init__初始化空对象objself.__init__(obj,*args,**kwargs)#3、返回初始化好的对象objreturn objclass OldboyTeacher(object,metaclass=Mymeta):school='oldboy'def __init__(self,name,age):self.name=nameself.age=agedef say(self):print('%s says welcome to the oldboy to learn Python' %self.name)t1=OldboyTeacher('egon',18)
print(t1.__dict__) '''
{'name': 'egon', 'age': 18}
'''

上例的__call__相当于一个模板,我们可以在该基础上改写__call__的逻辑从而控制调用OldboyTeacher的过程,比如将OldboyTeacher的对象的所有属性都变成私有的

class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类def __call__(self, *args, **kwargs): #self=<class '__main__.OldboyTeacher'>#1、调用__new__产生一个空对象objobj=self.__new__(self) # 此处的self是类OldoyTeacher,必须传参,代表创建一个OldboyTeacher的对象obj#2、调用__init__初始化空对象objself.__init__(obj,*args,**kwargs)# 在初始化之后,obj.__dict__里就有值了obj.__dict__={'_%s__%s' %(self.__name__,k):v for k,v in obj.__dict__.items()}#3、返回初始化好的对象objreturn objclass OldboyTeacher(object,metaclass=Mymeta):school='oldboy'def __init__(self,name,age):self.name=nameself.age=agedef say(self):print('%s says welcome to the oldboy to learn Python' %self.name)t1=OldboyTeacher('egon',18)
print(t1.__dict__) '''
{'_OldboyTeacher__name': 'egon', '_OldboyTeacher__age': 18}
'''

上例中涉及到查找属性的问题,比如self.new,请看下一小节

5 属性查找

结合python继承的实现原理+元类重新看属性的查找应该是什么样子呢???

在学习完元类后,其实我们用class自定义的类也全都是对象(包括object类本身也是元类type的 一个实例,可以用type(object)查看),我们学习过继承的实现原理,如果把类当成对象去看,将下述继承应该说成是:对象OldboyTeacher继承对象Foo,对象Foo继承对象Bar,对象Bar继承对象object

class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类n=444def __call__(self, *args, **kwargs): #self=<class '__main__.OldboyTeacher'>obj=self.__new__(self)self.__init__(obj,*args,**kwargs)return objclass Bar(object):n=333class Foo(Bar):n=222class OldboyTeacher(Foo,metaclass=Mymeta):n=111school='oldboy'def __init__(self,name,age):self.name=nameself.age=agedef say(self):print('%s says welcome to the oldboy to learn Python' %self.name)print(OldboyTeacher.n) #自下而上依次注释各个类中的n=xxx,然后重新运行程序,发现n的查找顺序为OldboyTeacher->Foo->Bar->object->Mymeta->type

于是属性查找应该分成两层,一层是对象层(基于c3算法的MRO)的查找,另外一个层则是类层(即元类层)的查找

查找顺序:
1、先对象层:OldoyTeacher->Foo->Bar->object
2、然后元类层:Mymeta->type

依据上述总结,我们来分析下元类Mymeta中__call__里的self.__new__的查找

class Mymeta(type): n=444def __call__(self, *args, **kwargs): #self=<class '__main__.OldboyTeacher'>obj=self.__new__(self)print(self.__new__ is object.__new__) #Trueclass Bar(object):n=333# def __new__(cls, *args, **kwargs):#     print('Bar.__new__')class Foo(Bar):n=222# def __new__(cls, *args, **kwargs):#     print('Foo.__new__')class OldboyTeacher(Foo,metaclass=Mymeta):n=111school='oldboy'def __init__(self,name,age):self.name=nameself.age=agedef say(self):print('%s says welcome to the oldboy to learn Python' %self.name)# def __new__(cls, *args, **kwargs):#     print('OldboyTeacher.__new__')OldboyTeacher('egon',18) #触发OldboyTeacher的类中的__call__方法的执行,进而执行self.__new__开始查找

总结,Mymeta下的__call__里的self.new__在OldboyTeacher、Foo、Bar里都没有找到__new__的情况下,会去找object里的__new,而object下默认就有一个__new__,所以即便是之前的类均未实现__new__,也一定会在object中找到一个,根本不会、也根本没必要再去找元类Mymeta->type中查找__new__

我们在元类的__call__中也可以用object.new(self)去造对象


但我们还是推荐在__call__中使用self.new(self)去创造空对象,因为这种方式会检索三个类OldboyTeacher->Foo->Bar,而object.__new__则是直接跨过了他们三个

最后说明一点

class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类n=444def __new__(cls, *args, **kwargs):obj=type.__new__(cls,*args,**kwargs) # 必须按照这种传值方式print(obj.__dict__)# return obj # 只有在返回值是type的对象时,才会触发下面的__init__return 123def __init__(self,class_name,class_bases,class_dic):print('run。。。')class OldboyTeacher(object,metaclass=Mymeta): #OldboyTeacher=Mymeta('OldboyTeacher',(object),{...})n=111school='oldboy'def __init__(self,name,age):self.name=nameself.age=agedef say(self):print('%s says welcome to the oldboy to learn Python' %self.name)print(type(Mymeta)) #<class 'type'>
# 产生类OldboyTeacher的过程就是在调用Mymeta,而Mymeta也是type类的一个对象,那么Mymeta之所以可以调用,一定是在元类type中有一个__call__方法
# 该方法中同样需要做至少三件事:
# class type:
#     def __call__(self, *args, **kwargs): #self=<class '__main__.Mymeta'>
#         obj=self.__new__(self,*args,**kwargs) # 产生Mymeta的一个对象
#         self.__init__(obj,*args,**kwargs)
#         return obj

Python面向对象—面向对象高级相关推荐

  1. week5 day4 面向对象编程高级

    week5 day4 面向对象编程高级 一. 判断是不是对象,是不是子类 二. 反射 2.1 反射机制的实现原理 2.2 四个内置函数的使用 2.3 用反射的好处 三. 内置方法 3.1 `__set ...

  2. 16.1、python初识面向对象(1)

    初识面向对象 楔子 你现在是一家游戏公司的开发人员,现在需要你开发一款叫做<人狗大战>的游戏,你就思考呀,人狗作战,那至少需要2个角色,一个是人, 一个是狗,且人和狗都有不同的技能,比如人 ...

  3. 《C#精彩实例教程》小组阅读12 -- C#面向对象技术高级应用

    本微信图文详细介绍了C#面向对象技术高级应用.

  4. Python设计模式面向对象编程

    前言 本篇文章是基于极客时间王争的<设计模式之美>做的总结和自己的理解.   说到面向对象编程,作为一个合格的Pythoner,可以说信手拈来.毕竟在Python里"万物都是对象 ...

  5. Python初识面向对象

    一.Python初识面向对象 1.1 封装 class Person:country='中国' #静态字段,可以直接调用def __init__(self,name,pwd): #Python类自带的 ...

  6. Python之面向对象类和对象

    Python之面向对象类和对象 定义一个类:class 定义类的语法: class Test(object):"""类里定义一类事物共同的技能.可以是变量,也可是函数.& ...

  7. Python之面向对象进阶

    Python之面向对象进阶 进阶有:Python 类的成员.成员修饰符.类的特殊成员. 一.类的成员 类的成员可以分为三大类:字段.方法和属性. 注:所有成员中,只有普通字段的内容保存对象中,即:根据 ...

  8. python 内存溢出能捕获吗_从0基础学习Python (19)[面向对象开发过程中的异常(捕获异常~相关)]...

    从0基础学习Python (Day19) 面向对象开发过程中的=>异常 什么是异常 ​ 当程序在运行过程中出现的一些错误,或者语法逻辑出现问题,解释器此时无法继续正常执行了,反而出现了一些错误的 ...

  9. Python之面向对象继承和派生

    Python之面向对象继承和派生 什么是继承: 继承是一种创建新的类的方法.在Python中,新建的类可以继承自一个或多个父类.原始类称为基类或超类. 新建的类称为派生类或子类. Python中类的继 ...

  10. 编程学习笔记(第一篇)面向对象技术高级课程:绪论-软件开发方法的演化与最新趋势(1)...

    软件工程的课程,对于从事大中型的软件开发是至关重要的一门课程. <面向对象技术高级课程>深入.系统.完整地讲解当今主流的面向对象软件开发方法的分析.设计.实现及重构方法,深入讲解UML语言 ...

最新文章

  1. 超详细的java生成excel文件并下载
  2. 土地一分用计算机怎么算,一分地等于多少平方米怎么算
  3. 有程序在记录你的键盘输入_12个用Java编写基础小程序amp;经典案例(收藏)
  4. SAP LSMW批导数据的几个注意点
  5. 在没有任何前端开发经验的基础上, 创建第一个 SAP Fiori Elements 应用
  6. Service Worker 的一个实战例子
  7. 爬虫结果数据完整性校验
  8. 前端学习(2156):uglifyjswebpackplugin的使用
  9. 二维教组A[12][18]采用列优先的存储方法,若每个元素各占3个存储单元,且第1个元素的地址为150,则元素A[9][7]的地址为 ( )
  10. 用mysql + node搭建一个简易工作列表网站
  11. linux指定的文件不是虚拟磁盘,linux下挂载VHD等虚拟磁盘文件的方法
  12. 打印pdf文件 vfp_新技能,如何将多份pdf电子发票文件合成一份文档打印
  13. url 的正则表达式:path-to-regexp
  14. 多个生产者多个消费者,只有5个包子
  15. 卖两本windows phone的书
  16. iOS常用---NSString,NSMutabuleString
  17. java 新浪博客_Java
  18. Matlab绘制隐式函数形成曲面的方法总结(转载)
  19. 百度旋转验证码打码模块,集成鱼刺模块类
  20. 武汉大学计算机学院 工程硕士,武汉大学计算机学院在职人员攻读工程硕士学位招生专业介绍...

热门文章

  1. ThingJS:定义3D摄像头的开发标准
  2. Pytorch拟合直线方法
  3. 迪赛智慧数——柱状图(多色柱状图):近十年上榜中国最具幸福感城市次数
  4. 只有 5000 个用户,却估值一亿美金?音频社交产品出了匹「黑马」
  5. 【正则表达式 】java 正则表达式 校验日期格式
  6. C++ 代码在内存中的存储地址
  7. 关于 PO 和 VO
  8. SSM整合,史上最详细的入门级代码,以后用的话直接往进套,复制即用
  9. 有实物销售的传销行为应如何认定
  10. Windows 无法使用139端口访问共享