元类
一、储备知识:exec函数
exec函数有三个参数:
参数1:字符串形式的命令
参数2:全局作用域(字典形式),如果不指定默认就使用globals()
参数3:局部作用域(字典形式),如果不指定默认就使用locals()
来看下面的例子:
g = {"x": 1, "y": 2} # 全局
l = {} # 局部
exec("""
global x,m
x=10
m=100
z=3
""", g, l)
print(g) # 输出一大堆,里面有 'x': 10, 'y': 2 还有 'm': 100
print(l) # 输出:{'z': 3}
程序通过global设定了两个全局变量x和m,分别给x和m赋了新的值,又设定了一个z=3是局部变量,然后通过exec执行后,查看全局作用域字段g可以看到里面有 'x': 10, 'y': 2 还有 'm': 100 ,查看局部作用域字典 l 可以看到里面有 'z': 3
二、元类介绍
我们知道Python中一切皆对象,那究竟对象具有什么样的特征,对象又可以怎么用呢?
1.都可以被引用,比如 x=obj
2.都可以当做函数的参数传入
3.都可以当做函数的返回值
4.都可以当做容器类的元素,如:li=[func,time,obj,123]
来看例子:
# 类也是对象,Foo=type(....)
class Foo:
pass
obj = Foo()
print(type(obj)) # 输出:<class '__main__.Foo'>
print(type(Foo)) # 输出:<class 'type'>
可以看到obj是类Foo的对象,我们说python中一切皆对象,那么Foo类就也是一个对象,可以看到Foo类其实是type的对象,Foo的本质相当于 Foo=type(....)
这就引出了元类的概念:产生类的类称之为元类,默认所有用class定义的类,他们的元类是type
定义类的两种方式:
方式一:class
# 方式一:class
class Chinese: # 相当于 Chinese=type(....)
country = "China"
def __init__(self, name, age):
self.name = name
self.age = age
def talk(self):
print("%s os talking" % self.name)
print(Chinese) # 输出:<class '__main__.Chinese'>
方式二:type
定义类的三要素:类名,类的基类们,类的名称空间
我们不依赖class,来通过type元类创建一个类。
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 os talking" % self.name)
""" # 类体
class_dict = {} # 类的名称空间
exec(class_body, globals(), class_dict) # 生成名称空间
print(
class_dict) # 输出:{'country': 'China', '__init__': <function __init__ at 0x000002012ED198C8>, 'talk': <function talk at 0x000002012ED19A60>}
Chinese1 = type(class_name, class_bases, class_dict) # 通过type生成类
print(Chinese1) # 输出:<class '__main__.Chinese'>
obj1 = Chinese1("alex", 18)
print(obj1) # 输出:<__main__.Chinese object at 0x00000210E2E09860>
print(obj1.name, obj1.age) # 输出:alex 18
三、自定义元类
1. 自定义元类控制类的行为
# 自定义元类控制类的行为
class Mymeta(type): # 要继承到type的一些属性
def __init__(self, class_name, class_bases, class_dict):
print("进入Mymeta了...") # 程序启动时 自动进入此类了,输出:进入Mymeta了...
if not class_name.istitle(): # 检查类名必须为首字母大写
raise TypeError("类名称首字母必须大写") # 抛出异常,程序终止
if "__doc__" not in class_dict or not class_dict["__doc__"].strip(): # 检查类里必须有注释,且注释不能为空
raise TypeError("类里必须添加注释,且注释不能为空") # 抛出异常,程序终止
super(Mymeta, self).__init__(class_name, class_bases, class_dict) # 重用父类的属性
class Chinese(object, metaclass=Mymeta): # 指定元类为Mymeta
"""
中国人的类
"""
country = "China"
def __init__(self, name, age):
self.name = name
self.age = age
def talk(self):
print("%s os talking" % self.name)
# Chinese = type(class_name,class_bases,class_dict)
# 由于类默认为type所产生,而type需要3个参数,所以我们自定义元类也需要3个参数
2. 自定义元类控制类的实例化行为
来知识储备一下:__call__方法
class Foo:
pass
obj = Foo() # 得到一个对象
obj() # 默认obj不能调用,报错:TypeError: 'Foo' object is not callable
上面obj对象,默认是不能够加括号调用的,要报错。但是python中一切皆对象,他应该是能够加括号调用的。
Foo是对象,他能够加括号调用,那么obj也应该可以,需要通过一些手段来实现obj对象可以加括号调用,怎么实现呢,在对象的类累不加上__call__方法。
class Foo:
def __call__(self, *args, **kwargs):
print(self) # self=<__main__.Foo object at 0x00000112138B8208>
print(args) # args=()
print(kwargs) # kwargs={}
obj = Foo()
obj() # 触发__call__方法执行,可调用了
obj(1, 2, 3, a=1, b=2, c=3) # 相当于 obj.__call__(1, 2, 3, a=1, b=2, c=3)
# obj(1, 2, 3, a=1, b=2, c=3) 输出如下:
<__main__.Foo object at 0x000001B1C3438208>
(1, 2, 3)
{'a': 1, 'b': 2, 'c': 3}
元类内部也应该有一个__call__方法,会在调用Foo时触发执行
Foo(1,2,x=1) # 相当于 Foo.__call__(Foo,1,2,x=1)
# 自定义元类控制类的实例化行为
class Mymeta(type): # 要继承到type的一些属性
def __init__(self, class_name, class_bases, class_dict):
print("进入Mymeta了...") # 程序启动时 自动进入此类了,输出:进入Mymeta了...
if not class_name.istitle(): # 检查类名必须为首字母大写
raise TypeError("类名称首字母必须大写") # 抛出异常,程序终止
if "__doc__" not in class_dict or not class_dict["__doc__"].strip(): # 检查类里必须有注释,且注释不能为空
raise TypeError("类里必须添加注释,且注释不能为空") # 抛出异常,程序终止
super(Mymeta, self).__init__(class_name, class_bases, class_dict) # 重用父类的属性
def __call__(self, *args, **kwargs):
print(args)
print(kwargs)
# 第一件事:造成一个空对象 obj
obj = object.__new__(self)
# 第二件事:初始化obj
self.__init__(obj, *args, **kwargs)
# 第三件事:返回obj
return obj
class Chinese(object, metaclass=Mymeta): # 指定元类为Mymeta
"""
中国人的类
"""
country = "China"
def __init__(self, name, age):
self.name = name
self.age = age
def talk(self):
print("%s is talking" % self.name)
obj = Chinese("alex", age=18) # 相当于 Chinese.__call__(Chinese, "alex", age=18)
print(obj.__dict__) # 输出:{'name': 'alex', 'age': 18}
3. 自定义元类控制类的实例化行为的应用
知识储备:单例模式
来看个例子:
# 单例模式
class MySQL:
def __init__(self):
self.host = "127.0.0.1"
self.port = 3306
def conn(self):
pass
def execute(self):
pass
obj1 = MySQL()
obj2 = MySQL()
obj3 = MySQL()
print(obj1) # 输出:<__main__.MySQL object at 0x0000022006589390>
print(obj2) # 输出:<__main__.MySQL object at 0x00000220065893C8>
print(obj3) # 输出:<__main__.MySQL object at 0x0000022006589438>
如上例子,实例化出来的3个对象,都有用同样的 host和port,但是3个对象想却需要独占3个空间,很浪费资源,我们可以把他改进一下,就要利用到 单例模式,如下:
# 单例模式
class MySQL:
__instance = None
def __init__(self):
self.host = "127.0.0.1"
self.port = 3306
@classmethod
def singleton(cls):
if not cls.__instance:
obj = cls()
cls.__instance = obj
return cls.__instance
def conn(self):
pass
def execute(self):
pass
obj1 = MySQL.singleton()
obj2 = MySQL.singleton()
obj3 = MySQL.singleton()
print(obj1) # 输出:<__main__.MySQL object at 0x00000262FDFC9780>
print(obj2) # 输出:<__main__.MySQL object at 0x00000262FDFC9780>
print(obj3) # 输出:<__main__.MySQL object at 0x00000262FDFC9780>
经过改进后的对象,都是使用的同一块空间,节省空间。
# 自定义元类控制类的实例化行为的应用
class Mymeta(type): # 要继承到type的一些属性
def __init__(self, class_name, class_bases, class_dict):
if not class_name.istitle(): # 检查类名必须为首字母大写
raise TypeError("类名称首字母必须大写") # 抛出异常,程序终止
if "__doc__" not in class_dict or not class_dict["__doc__"].strip(): # 检查类里必须有注释,且注释不能为空
raise TypeError("类里必须添加注释,且注释不能为空") # 抛出异常,程序终止
super(Mymeta, self).__init__(class_name, class_bases, class_dict) # 重用父类的属性
self.__instance = None
def __call__(self, *args, **kwargs):
if not self.__instance:
obj = object.__new__(self)
self.__init__(obj)
self.__instance = obj
return self.__instance
class Mysql(object, metaclass=Mymeta):
"""
Mysql
"""
def __init__(self):
self.host = "127.0.0.1"
self.port = 3306
def conn(self):
pass
def execute(self):
pass
obj1 = Mysql()
obj2 = Mysql()
obj3 = Mysql()
print(obj1) # 输出:<__main__.Mysql object at 0x0000025FC2679940>
print(obj2) # 输出:<__main__.Mysql object at 0x0000025FC2679940>
print(obj3) # 输出:<__main__.Mysql object at 0x0000025FC2679940>
print(obj1.__dict__) # 输出:{'host': '127.0.0.1', 'port': 3306}
共有 0 条评论