Python元类之由浅入深
前言
元类属于python面向对象编程的深层次的魔法,非常重要,它使我们可以更好的掌控类从创建到消亡的整个生命周期过程。很多框架的源码中都使用到了元类。例如 Django Framework 中的 ORM engine.
白类 === 普通的自定义类
什么是元类
面向对象编程最重要的一句话:一切皆对象 过去我们都 是这样创建类的:
class Panda(object):
hobby= "study python"
def __init__(self, name, age): # initialize
self.name = name
self.age = age
def __str__(self): # format
return "My name=%s, age=%s"%(self.name, self.age)
然后再实例化获得 对象
suosuo = Panda("suosuo", 120)
print(suosuo) # "My name=suosuo, age=120"
print(type(suosuo)) # <class '__main__.Panda'>
都说了一切皆对象,那请问 类:<class '__main__.Panda'> 也该是一个对象吧!
答:没错,创建这个 Panda 类 的 类 我们称之为 元类, 也就是 这个 Panda 类是元类实例化得到的结果对象。
print(type(Panda)) # <class 'type'> , 即默认的唯一一个内置元类 叫 type
class 关键字创建 类 的流程分析
以前我们只知道如何使用类, 现在有必要分析底层原理。手动创建一个 类
class 关键字创建类时,必然代替我们调用了元类 Panda = type(.....), 调用时传入的参数是啥呢?
一个类有三个关键组成部分,分别是:
- 类名 class_name = "Panda"
- 基类们 class_bases = (object, )
- 类的名称空间 class_dic,---> 是由执行类体代码决定的
自定义元类 手动创建白类 Panda :
一个类没有声明自己的元类,语言底层自动指定默认元类就是 type,
当然我们可以自定义元类------》通过继承 type 元类的方式。然后使用 metaclass 关键字为一个 白类 指定元类
class Mymeta(type): # 只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
pass
class Panda(object,metaclass=Mymeta): # Panda = Mytype("Panda", (object,),{......})
hobby= "study python"
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return "My name=%s, age=%s"%(self.name, self.age)
自定义元类可以控制白类的产生过程,白类的产生过程其实就是元类调用自身 __new__(...) 方法的过程:
class Mymeta(type): # 只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
def __new__(cls, class_name, class_bases, class_dic): # def __new__(cls, *args, **kwargs):
print(Mymeta) # <class '__main__.Mymeta'>
print(cls) # <class '__main__.Mymeta'>
print(class_name) # Panda
print(class_bases) # (<class 'object'>,)
print(class_dic) # {'__module__': '__main__', '__qualname__': 'Panda', 'hobby': 'study python', '__init__': <function Panda.__init__ at 0x02AB3780>,
return super(Mymeta, cls).__new__(cls, class_name, class_bases, class_dic) # 重用父类的功能
# return super(Mymeta, cls).__new__(cls, *args, **kwargs)
def __init__(self, class_name, class_bases, class_dic): # 必须为 4 个参数,做自定制的
print(self) # <class '__main__.Panda'> 注意这里,注意这里, 注意这里
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 Panda(object,metaclass=Mymeta): # Panda = Mytype("Panda", (object,),{......})
""" 类 Panda 的文档注释 """
hobby= "study python"
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return "My name=%s, age=%s"%(self.name, self.age)
suosuo = Panda("suosuo", 120)
需注意以下几点:
三个参数: (类名, 基类们,类的名称空间) 都自动传入自定义元类的 __new__ 和 __init__ 方法。
__new__ 方法的 第一个参数 cls = 自定义元类, 而 __init__ 方法 的第一个参数 self = 白类, 也就是此自定义元类
__new__ 方法 的执行结果。
__new__ 方法 必须要有 return 语句, 调用基类的 __new__ 方法, 而 __init__ 可以没有 return 语句 (初始化)
手动控制 白类 实例化 的过程
储备知识: __call__
class Foo:
def __call__(self, *args, **kwargs):
print(self)
print(args)
print(kwargs)
obj=Foo()
# 1、要想让obj这个对象变成一个可调用的对象,需要在该对象的类中定义一个方法__call__方法,该方法会在调用对象时自动触发
# 2、调用obj的返回值就是__call__方法的返回值
res=obj(1,2,3,x=1,y=2)
由上例得知, 调用一个对象 ----》(对象 + 括号), 就是触发此对象所在类中的 __call__ 方法的执行。又因为白类 是
元类实例化的对象,故 白类 加 括号 运行 -----》白类 实例化,也必然 触发 元类中的 __call__ 方法
# 测试 ,查看一下关键的几个参数的值
class Mymeta(type):
def __call__(self, *args, **kwargs):
print(self) # <class '__main__.Panda'>
print(args) # ('suosuo',)
print(kwargs) # {'age': 120}
class Panda(object,metaclass=Mymeta): # Panda = Mytype("Panda", (object,),{......})
hobby= "study python"
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return "My name=%s, age=%s"%(self.name, self.age)
suosuo = Panda("suosuo", age=120)
print(suosuo) # 结果:None , 原因为 元类 __call__ 方法无返回值
注意以下几点:
- 调用 白类 则自动触发 元类中的 __call__ 方法
- 元类 __call__ 方法 的 self 为白类,*args 为白类实例化溢出的位置参数,**kwargs 为白类实例化溢出的关键字参数
- 白类实例化的执行结果 就是 元类 __call__ 方法的返回结果 ,
默认 白类 实例化 Panda("suosuo", age=120) -----》 元类的 __call__ 方法 会做三件事:
- 产生一个 空 对象 obj
- 调用 __init__ 方法 初始化 对象 obj
- 返回 初始化好的 obj
class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
def __call__(self, *args, **kwargs):
# 1、调用__new__产生一个空对象obj
obj = self.__new__(self) # 此处必须传参, self = 白类,代表创建一个白类对象
# 2、调用__init__初始化空对象obj#
self.__init__(obj, *args, **kwargs)
# 3、返回初始化好的对象obj
return obj
class Panda(object,metaclass=Mymeta): # Panda = Mytype("Panda", (object,),{......})
hobby= "study python"
def __init__(self, name, age):
print(self) # <__main__.Panda object at 0x0106E790>
self.name = name
self.age = age
suosuo = Panda("suosuo", age=120)
print(suosuo) # <__main__.Panda object at 0x0220E790>
上例 元类的 __call__ 方法 就相当于一个 白类实例化 创建对象的 模板 , 我们 可以在该基础上改写 __call__ 方法的 逻辑
从而 控制 白类 实例化 Panda("suosuo", age=120) 的过程。
比如将 Panda 的 对象的所有属性都变为私有的
class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
def __call__(self, *args, **kwargs):
# 1、调用__new__产生一个空对象obj
obj = self.__new__(self) # 此处必须传参, self = 白类,代表创建一个白类对象
# 2、调用__init__初始化空对象obj#
self.__init__(obj, *args, **kwargs)
# 在初始化之后,obj.__dict__里就有值了
obj.__dict__ = {'_%s__%s' % (self.__name__, k): v for k, v in obj.__dict__.items()}
# 3、返回初始化好的对象obj
return obj
class Panda(object,metaclass=Mymeta): # Panda = Mytype("Panda", (object,),{......})
hobby= "study python"
def __init__(self, name, age):
print(self)
self.name = name
self.age = age
suosuo = Panda("suosuo", age=120)
print(suosuo.__dict__) # {'_Panda__name': 'suosuo', '_Panda__age': 120}
结合 元类 属性的查找顺序
在学习完元类后,其实我们用class自定义的类也全都是对象(包括object类本身也是元类type的 一个实例,
可以用type(object)查看),我们学习过继承的实现原理,如果把类当成对象去看,
将下述继承应该说成是:对象 Panda 继承对象 Footer,对象 Footer 继承对象Skin,对象 Skin 继承对象object
class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
n = 444
def __call__(self, *args, **kwargs):
obj = self.__new__(self)
self.__init__(obj, *args, **kwargs)
return obj # 必须 有 返回值
class Skin(object):
n = 333
class Footer(Skin):
n = 222
class Panda(Footer,metaclass=Mymeta): # Panda = Mytype("Panda", (object,),{......})
n = 111
def __init__(self, name, age):
self.name = name
self.age = age
suosuo = Panda("suosuo", age=120)
print(Panda.n) # 注意是 类 属性
于是属性查找应该分成两层,一层是对象层(基于c3算法的MRO)的查找,另外一个层则是类层(即元类层)的查找
白类类名 查找顺序:
- 先白类层 Panda--> Footer -->Skin--> object
- 再元类层 Mymeta--> type
依据上述总结,我们来分析下元类Mymeta中__call__里的self.__new__ 方法的查找
class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
n = 444
def __call__(self, *args, **kwargs):
obj = self.__new__(self)
print(self.__new__ is object.__new__) #True
self.__init__(obj, *args, **kwargs)
return obj
class Skin(object):
n = 333
class Footer(Skin):
n = 222
class Panda(Footer,metaclass=Mymeta): # Panda = Mytype("Panda", (object,),{......})
n = 111
def __init__(self, name, age):
self.name = name
self.age = age
suosuo = Panda("suosuo", age=120)
print(Panda.n) # 注意是 类 属性
总结,Mymeta下的__call__里的self.__new__在 Panda、Footer、Skin 里都没有找到__new__的情况下,
会去找object里的__new__,而object下默认就有一个__new__,所以即便是之前的类均未实现__new__,
也一定会在object中找到一个,根本不会、也根本没必要再去找元类Mymeta->type中查找__new__
我们在元类的__call__中也可以用object.__new__(self) 去造空对象
但还是推荐在__call__中使用self.__new__(self)去创造空对象,因为这种方式会检索三个类Panda--->Footer--->Skin,而object.__new__则是直接跨过了他们三个
class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
n = 444
def __new__(self, *args, **kwargs):
obj=type.__new__(self, *args,**kwargs) # 必须按照这种传值方式
# return obj # 只有在返回值是type的对象时,才会触发下面的__init__
return 123
def __init__(self,class_name,class_bases,class_dic):
print('run。。。')
class Panda(object,metaclass=Mymeta): # Panda = Mytype("Panda", (object,),{......})
n = 111
def __new__(self, *args, **kwargs):
# return super().__new__(self) # 只有返回是 object的对象时,才会触发下方的__init__方法
return 456
def __init__(self, name, age):
self.name = name
self.age = age
suosuo = Panda("suosuo", age=120)
print(suosuo) # 'int' object is not callable
注意:
- 只有 元类的 __new__ 方法只有返回 type 的对象时, 才会触发 元类的 __init__ 方法, 同理。
- 只有 白类的 __new__ 方法只有返回 object 的对象时, 才会触发 白类的 __init__ 方法
- type.__new__(cls,class_name, class_bases, class_dic) 必须添加这四个参数, 而 object.__new__(self) 只能添加这一个参数,代表创建谁的对象
练习
练习一:在元类中控制把自定义类的数据属性都变成大写
class Mymetaclass(type):
def __new__(cls,name,bases,attrs):
update_attrs={}
for k,v in attrs.items():
if not callable(v) and not k.startswith('__'):
update_attrs[k.upper()]=v
else:
update_attrs[k]=v
return type.__new__(cls,name,bases,update_attrs)
class Chinese(metaclass=Mymetaclass):
country='China'
tag='Legend of the Dragon' #龙的传人
def walk(self):
print('%s is walking' %self.name)
print(Chinese.__dict__)
'''
{'__module__': '__main__',
'COUNTRY': 'China',
'TAG': 'Legend of the Dragon',
'walk': <function Chinese.walk at 0x0000000001E7B950>,
'__dict__': <attribute '__dict__' of 'Chinese' objects>,
'__weakref__': <attribute '__weakref__' of 'Chinese' objects>,
'__doc__': None}
'''
练习二:在元类中控制自定义的类无需__init__方法
- 元类帮其完成创建对象,以及初始化操作;
- 要求实例化时传参必须为关键字形式,否则抛出异常TypeError: must use keyword argument
- key作为用户自定义类产生对象的属性,且所有属性变成大写
class Mymetaclass(type):
# def __new__(cls,name,bases,attrs):
# update_attrs={}
# for k,v in attrs.items():
# if not callable(v) and not k.startswith('__'):
# update_attrs[k.upper()]=v
# else:
# update_attrs[k]=v
# return type.__new__(cls,name,bases,update_attrs)
def __call__(self, *args, **kwargs):
if args:
raise TypeError('must use keyword argument for key function')
obj = object.__new__(self) #创建对象,self为类Foo
for k,v in kwargs.items():
obj.__dict__[k.upper()]=v
return obj
class Chinese(metaclass=Mymetaclass):
country='China'
tag='Legend of the Dragon' #龙的传人
def walk(self):
print('%s is walking' %self.name)
p=Chinese(name='egon',age=18,sex='male')
print(p.__dict__)
练习三:在元类中控制自定义的类产生的对象相关的属性全部为隐藏属性
class Mymeta(type):
def __init__(self,class_name,class_bases,class_dic):
#控制类Foo的创建
super(Mymeta,self).__init__(class_name,class_bases,class_dic)
def __call__(self, *args, **kwargs):
#控制Foo的调用过程,即Foo对象的产生过程
obj = self.__new__(self)
self.__init__(obj, *args, **kwargs)
obj.__dict__={'_%s__%s' %(self.__name__,k):v for k,v in obj.__dict__.items()}
return obj
class Foo(object,metaclass=Mymeta): # Foo=Mymeta(...)
def __init__(self, name, age,sex):
self.name=name
self.age=age
self.sex=sex
obj=Foo('egon',18,'male')
print(obj.__dict__)
练习四: 基于元类实现单例模式
#步骤五:基于元类实现单例模式
# 单例:即单个实例,指的是同一个类实例化多次的结果指向同一个对象,用于节省内存空间
# 如果我们从配置文件中读取配置来进行实例化,在配置相同的情况下,就没必要重复产生对象浪费内存了
#settings.py文件内容如下
HOST='1.1.1.1'
PORT=3306
#方式一:定义一个类方法实现单例模式
import settings
class Mysql:
__instance=None
def __init__(self,host,port):
self.host=host
self.port=port
@classmethod
def singleton(cls):
if not cls.__instance:
cls.__instance=cls(settings.HOST,settings.PORT)
return cls.__instance
obj1=Mysql('1.1.1.2',3306)
obj2=Mysql('1.1.1.3',3307)
print(obj1 is obj2) #False
obj3=Mysql.singleton()
obj4=Mysql.singleton()
print(obj3 is obj4) #True
#方式二:定制元类实现单例模式
import settings
class Mymeta(type):
def __init__(self,name,bases,dic): #定义类Mysql时就触发
# 事先先从配置文件中取配置来造一个Mysql的实例出来
self.__instance = object.__new__(self) # 产生对象
self.__init__(self.__instance, settings.HOST, settings.PORT) # 初始化对象
# 上述两步可以合成下面一步
# self.__instance=super().__call__(*args,**kwargs)
super().__init__(name,bases,dic)
def __call__(self, *args, **kwargs): #Mysql(...)时触发
if args or kwargs: # args或kwargs内有值
obj=object.__new__(self)
self.__init__(obj,*args,**kwargs)
return obj
return self.__instance
class Mysql(metaclass=Mymeta):
def __init__(self,host,port):
self.host=host
self.port=port
obj1=Mysql() # 没有传值则默认从配置文件中读配置来实例化,所有的实例应该指向一个内存地址
obj2=Mysql()
obj3=Mysql()
print(obj1 is obj2 is obj3)
obj4=Mysql('1.1.1.4',3307)
#方式三:定义一个装饰器实现单例模式
import settings
def singleton(cls): #cls=Mysql
_instance=cls(settings.HOST,settings.PORT)
def wrapper(*args,**kwargs):
if args or kwargs:
obj=cls(*args,**kwargs)
return obj
return _instance
return wrapper
@singleton # Mysql=singleton(Mysql)
class Mysql:
def __init__(self,host,port):
self.host=host
self.port=port
obj1=Mysql()
obj2=Mysql()
obj3=Mysql()
print(obj1 is obj2 is obj3) #True
obj4=Mysql('1.1.1.3',3307)
obj5=Mysql('1.1.1.4',3308)
print(obj3 is obj4) #False
Python元类之由浅入深的更多相关文章
- python元类:type和metaclass
python元类:type和metaclass python中一切皆对象,所以类本身也是对象.类有创建对象的能力,那谁来创建类的呢?答案是type. 1.用tpye函数创建一个类 class A(ob ...
- Python 元类 - Metaclasses
Python 元类 - Metaclasses 默认情况下儿, classes 是有 type() 构造的. 类的结构体在一个新的 namespace 被执行, 类的名字 class name 绑定( ...
- Python进阶丨如何创建你的第一个Python元类?
摘要:通过本文,将深入讨论Python元类,其属性,如何以及何时在Python中使用元类. Python元类设置类的行为和规则.元类有助于修改类的实例,并且相当复杂,是Python编程的高级功能之一. ...
- python 元类
转载自 http://blog.jobbole.com/21351/ 类也是对象 在理解元类之前,你需要先掌握Python中的类.Python中类的概念借鉴于Smalltalk,这显得有些奇特.在大 ...
- [python]python元类
这两天在看Django框架,里面的filter实现原理搞不明白,最后发现跟python的元类有关系. 原文:http://stackoverflow.com/questions/100003/what ...
- Python元类实践--自己定义一个和collections中一样的namedtuple
大家可能很熟悉在collections模块中有一个很好用的扩展数据类型-namedtuple. 如果你还不知道这个类型,那么请翻看标准手册. 我利用元类轻松定义一个namedtuple. 先把代码贴上 ...
- python元类分析
刚開始接触到Python新式类中的元类的概念的时候非常是纠结了下..不知道这是个啥东西... 用下面几个定义来说明吧: (1)Python中,类也是对象..仅仅只是这样的对象比較的特殊,他用于创建别的 ...
- python元类理解2
恩,对元类理解又有新的收获,其实类似于装饰器,只不过装饰器是修饰函数,元类用来定制一个类. 代码如下,这是一个使用了函数做元类传递给类: input: def upper_attr(class_nam ...
- 3.python元类编程
1.1.propety动态属性 在面向对象编程中,我们一般把名词性的东西映射成属性,动词性的东西映射成方法.在python中他们对应的分别是属性self.xxx和类方法.但有时我们需要的属性需要根据 ...
随机推荐
- 神仙dcx出的一道题
题目大意 \(\;\;\)在一个坐标系上, 以\((0, 0)\)为起点, 每走一步,可以从\((x,y)\)走到\((x+1,y),(x-1,y),(x,y+1),(x,y-1)\)中的一个点上, ...
- Linux和VMware
1.1 Linux操作系统简介 是一个基于POSIX和UNIX的多用户.多任务.支持多线程和多CPU的操作系统.它能运行主要的UNIX工具软件.应用程序和网络协议.它支持32位和64位硬件.Lin ...
- django 的保护机制
- redis学习 --Hash
一:我们可以将Redis中的Hash类型看成具有String Key和String Value的map容器.所以该类型非常适合于存储值对象的信息.如Username.Password和Age等.如果H ...
- 2017 ICPC HongKong B:Black and White(扫描线+线段树)
题目描述 Consider a square map with N × N cells. We indicate the coordinate of a cell by (i, j), where 1 ...
- [CF1167D]Bicolored RBS题解
模拟两个颜色的扩号层数,贪心,如果是左括号,哪边的层数浅就放那边:如果是右括号,哪边的层数深就放那边. 至于层数的维护,两个int就做掉了 放个代码: #include <cstdio> ...
- 高级Javascript代码
Javascript是一门很吊的语言,我可能学了假的JavaScript,哈哈,大家还有什么推荐的,补充送那啥邀请码. 本文秉承着:你看不懂是你SB,我写的代码就要牛逼. 1.单行写一个评级组件 &q ...
- Linux下安装mysql教程
Linux下安装mysql MySQL官网:https://dev.mysql.com/downloads/mysql/ 到mysql官网下载mysql编译好的二进制安装包,在下载页面Select ...
- STM32 实现内部Flash的读写(HAL库版)
Flash 中文名字叫闪存,是一种长寿命的非易失性(断电数据不丢失)的存储器.可以对称为块的存储器单元块进行擦写和再编程,在进行写入操作之前必须先执行擦除.一个Nand Flash由多个块(Block ...
- [转]Scikit-learn使用总结
1 scikit-learn基础介绍 1.1 估计器(Estimator) 估计器,很多时候可以直接理解成分类器,主要包含两个函数: fit():训练算法,设置内部参数.接收训练集和类别两个参数. p ...