一、元类
元类是什么
有一句话在编程语言圈中流行,python一切皆对象。在之前初学面向对象的时候,我们举过例子,之前使用的函数都是通过类造出来的对象。但是我们没有思考过,既然一切皆对象,那么类是否也是一个对象?如果他是一个对象,那么他又是被哪个类造出来的?
这个就是今天要学习的内容,元类,即类这个对象的类.
我们查看某个对象属于哪个类,是利用type,那么我们可以使用相同方法,查看类的类是什么。
class MyClass:
def __init__(self,name,age):
self.name = name
self.age = age
obj = MyClass('yang',18)
print(type(obj))
print(type(MyClass))
----------------------
<class '__main__.MyClass'>
<class 'type'> # 此处可知元类是type
默认的元类是type,在默认情况下,我们用class关键字定义的类都是由type产生。
模拟class关键字
对于一个类来说,组成部分有三个,类名,基类,类体代码。在下面,我们模拟class关键字产生一个类。
# 1、先拿到一个类名
class_name = "Teacher"
# 2、然后拿到类的父类
class_bases = (object,) # 如果未继承类,则默认为object
# 3、再运行类体代码,将产生的名字放到名称空间中
class_dic = {}
class_body = """
school = 'qinghua'
def __init__(self, name, age):
self.name = name
self.age = age
def say(self):
print('%s says welcome' % self.name)
"""
exec(class_body,{},class_dic) # exec 执行储存在字符串或文件中的Python语句,相比于eval,exec可以执行更复杂的 Python 代码。
# print(class_dic)
# 4、调用元类(传入类的三大要素:类名、基类、类的名称空间)得到一个元类的对象,然后将元类的对象赋值给变量名Teacher,Teacher就是我们用class自定义的那个类
MyClass = type(class_name,class_bases,class_dic)
自定义元类
既然知道了class关键字是如何创建类的,那么我们就清楚了类的实现过程,那么我们可以自定义一个元类了,自定义元类的好处有很多,在下面的例子中你可以看到.
基本格式
class Mymeta(type): # 必须要继承type
pass
class MyClass(metaclass=Mymeta): # 指明利用自定义元类创建类
pass
obj = MyClass()
print(obj)
----------------------
<__main__.MyClass object at 0x7fd442328c70>
在自定义元类中,我们可以加以对类创建的控制,如名字必须大写开头,必须要有类的注释,把利用此类生成的对象的所有属性和方法都设置为私有等等。在这里我们使用到了raise,主动抛出异常。
import re
class Mymeta(type): # 只有继承了type类的类才是自定义的元类
def __init__(self, class_name, class_bases, class_dic):
if not re.match("[A-Z]", class_name):
raise BaseException("类名必须用驼峰体")
if len(class_bases) == 0:
raise BaseException("至少继承一个父类")
# print("文档注释:",class_dic.get('__doc__'))
doc=class_dic.get('__doc__')
if not (doc and len(doc.strip()) > 0):
raise BaseException("必须要有文件注释,并且注释内容不为空")
class Teacher(object,metaclass=Mymeta):
"""
文档注释必须有,不然要抛出异常
"""
addr = 'Shanghai'
def __init__(self, name, age):
self.name = name
self.age = age
def say(self):
print('%s says welcome to the oldboy to learn Python' % self.name)
obj = Teacher('yang',18)
print(obj)
-----------------------------
<__main__.Teacher object at 0x7fb8c4428b80>
一个对象加括号,会触发魔术方法__call__方法,既然类作为一个对象,肯定在加括号运行时候也触发了这个方法,所以我们可以推测在type中必定有__call__方法,我们也可以自己写这个方法,来控制类加括号运行时发生的事情。比如,将一个在实例化的时候就把所有的属性和方法都设置为私有。
import re
class Mymeta(type): # 只有继承了type类的类才是自定义的元类
def __init__(self, class_name, class_bases, class_dic):
if not re.match("[A-Z]", class_name):
raise BaseException("类名必须用驼峰体")
if len(class_bases) == 0:
raise BaseException("至少继承一个父类")
# print("文档注释:",class_dic.get('__doc__'))
doc = class_dic.get('__doc__')
if not (doc and len(doc.strip()) > 0):
raise BaseException("必须要有文件注释,并且注释内容不为空")
def __call__(self, *args, **kwargs):
# 1、先创建一个老师的空对象,利用__new__魔术方法,利用父类创建对象
tea_obj = object.__new__(self)
# 2、调用老师类内的__init__函数,然后将老师的空对象连同括号内的参数的参数一同传给__init__
self.__init__(tea_obj, *args, **kwargs)
# 利用字典生成式,直接一行代码解决私有化
tea_obj.__dict__ = {"_%s__%s" %(self.__name__,k): v for k, v in tea_obj.__dict__.items()}
# 3、将初始化好的老师对象赋值给变量名res
return tea_obj
class Teacher(object, metaclass=Mymeta):
"""
必须写注释不然要报错
"""
school = 'MIT'
def __init__(self, name, age):
self.name = name
self.age = age
def say(self):
print('%s says welcome to the oldboy to learn Python' % self.name)
res = Teacher('egon', 18)
print(res.__dict__)
----------------------------------
{'_Teacher__name': 'egon', '_Teacher__age': 18}
二、单例模式***
单例模式,是一个设计思路,既然是一种设计思路,那么肯定就有很多种实现方法。
首先介绍一下单例模式的思路,我们的目的是确保某一个类只有一个实例存在,减少内存的损耗。比如我们要读取一个配置文件的时候,可能很多地方都需要读取这个文件,那么可能会创建出十几个对象都在读这个文件,并且他们的功能都是相同的。那么我们不需要创建那么多对象了,每次都把相同的对象返回给他。
单例模式常用写法:懒汉式,饿汉式,注册式,序列化式。
本文介绍四种实现单例模式的方法。
纯单例模式
利用__new__魔术方法
在调用的时候若没有这个对象再临时创建这个对象,属于懒汉式
class MyClass:
__obj = None
def __new__(cls, *args, **kwargs):
if cls.__obj:
return cls.__obj
cls.__obj = object.__new__(cls)
return cls.__obj
obj1 = MyClass()
obj2 = MyClass()
print(obj1)
print(obj2)
---------------------------
<__main__.MyClass object at 0x7f9e22a45460>
<__main__.MyClass object at 0x7f9e22a45460>
直接利用类方法
在调用的时候若没有这个对象再临时创建这个对象,属于懒汉式
class MyClass:
__obj = None
@classmethod
def singleton(cls):
if cls.__obj:
return cls.__obj
cls.__obj = cls()
return cls.__obj
obj1 = MyClass.singleton()
obj2 = MyClass.singleton()
print(obj1)
print(obj2)
------------------------
<__main__.MyClass object at 0x7fb321340460>
<__main__.MyClass object at 0x7fb321340460>
利用元类
思路是在调用前就先把这个对象创建出来,然后要调用的时候直接返回这个对象。属于饿汉式
class MyMeta(type):
__obj = None
def __init__(self,class_name,class_bases,class_dic):
self.__obj = object.__new__(self) # 先把对象创建出来
def __call__(self, *args, **kwargs):
return self.__obj # 不用判断是否有这个对象了,直接创建即可
class MyClass(metaclass=MyMeta):
pass
obj1 = MyClass()
obj2 = MyClass()
print(obj1)
print(obj2)
------------------------
<__main__.MyClass object at 0x7ffbc4a28c70>
<__main__.MyClass object at 0x7ffbc4a28c70>
利用装饰器
思路是在调用前就先把这个对象创建出来,然后要调用的时候直接返回这个对象。属于饿汉式
def wrapper(func):
__obj = func()
def inner(*args,**kwargs):
return __obj
return inner
@wrapper
class MyClass():
pass
obj1 = MyClass()
obj2 = MyClass()
print(obj1)
print(obj2)
--------------------
<__main__.MyClass object at 0x7fefc632db80>
<__main__.MyClass object at 0x7fefc632db80>
# 这个单例的分析思路:
博主曾经被这个单例困惑了几个月,直到现在和朋友讨论才发现了这个单例的实现流程,博主的问题是:
我们用装饰器的时候,装饰器里面的代码肯定是会走的,而我们执行了两次MyClass(),也就是__obj = func(),那么在wrapper这个
装饰器里,func()这个代码应该是走两次的,那么生成的对象肯定应该是两个不一样的,但是结果却是实现了单例。
在解决这个困惑的过程中,加入了一些代码:
def wrapper(func):
__obj = func()
print(1)
def inner(*args,**kwargs):
print(2)
return __obj
return inner
@wrapper
class MyClass():
pass
上述的代码,执行结果是,1走了一遍,2走了两遍。当然,只有这样的结果才是符合单例的,但是却和我们的思路不一样。
然后我把@wrapper拆分了出来
代码就变成了
def wrapper(func):
__obj = func()
def inner(*args,**kwargs):
return __obj
return inner
class MyClass():
pass
MyClass = wrapper(MyClass)
obj1 = MyClass()
obj2 = MyClass()
print(obj1)
print(obj2)
然后我瞬间懂了,那个print(1),是在MyClass = wrapper(MyClass)走的,然后,这个MyClass其实变成了inner
后面其实就是直接执行inner,根本就和wrapper没关系了。
记住单例的实现思路,无论看什么方法实现单例都是一样的。首先在类中定义一个属性如x为空,然后再调用类的时候就可以判断了,如果是第一次进来,x必定为空,那么就正常的创建一个对象给他。那么我们类中的x属性现在就不是空了,而是一个对象,因为我们把创建出来的对象赋值给了x,那么之后每次进来,因为x已经不是空了,所以都不会走创建对象的分支,而是走直接return x的分支,我们又没有对x进行修改,他永远都是第一次创建出来的对象。
按照需求实现单例模式
以下也是一种设计的方案,当使用者实例化时传了参数,那么说明他想创建一个新的对象,如果不传参,我们默认使用单例模式。下面提供了三种方式。
利用类方法
IP = 1.1.1.1
PORT = 3306
class MySQL:
__instance = None
def __init__(self, ip, port):
self.ip = ip
self.port = port
@classmethod
def singleton(cls):
if cls.__instance:
return cls.__instance
cls.__instance = cls(IP, PORT)
return cls.__instance
obj1=MySQL("1.1.1.1",3306)
obj2=MySQL("1.1.1.2",3306)
obj3 = MySQL.singleton()
obj4 = MySQL.singleton()
print(obj1)
print(obj2)
print(obj3)
print(obj4)
利用装饰器
IP = "192.168.11.10"
PORT = 3306
import settings
def outter(func): # func = MySQl类的内存地址
_instance = func(IP,PORT)
def wrapper(*args,**kwargs):
if args or kwargs:
res=func(*args,**kwargs)
return res
else:
return _instance
return wrapper
@outter # MySQL=outter(MySQl类的内存地址) # MySQL=》wrapper
class MySQL:
def __init__(self, ip, port):
self.ip = ip
self.port = port
obj1 = MySQL("1.1.1.1", 3306)
obj2 = MySQL("1.1.1.2", 3306)
obj3 = MySQL()
obj4 = MySQL()
print(obj1)
print(obj2)
print(obj3 is obj4)
利用元类
IP = 1.1.1.1
PORT = 3306
class Mymeta(type):
__instance = None
def __init__(self,class_name,class_bases,class_dic):
self.__instance=object.__new__(self) # Mysql类的对象
self.__init__(self.__instance,IP,PORT)
def __call__(self, *args, **kwargs):
if args or kwargs:
obj = object.__new__(self)
self.__init__(obj, *args, **kwargs)
return obj
else:
return self.__instance
class MySQL(metaclass=Mymeta):
def __init__(self, ip, port):
self.ip = ip
self.port = port
obj1 = MySQL("1.1.1.1", 3306)
obj2 = MySQL("1.1.1.2", 3306)
obj3 = MySQL()
obj4 = MySQL()
print(obj1)
print(obj2)
print(obj3 is obj4)
'''
关于为何要在Mymeta中书写__call__方法,那是因为我们现在要控制Mymeta实现单例模式,那么如果不自己实现__call__就会使用type的__call__方法,type是不可能帮我们实现单例模式的。
'''