Day 30
元类
控制类的产生(写底层代码时)
1、python中一切皆对象,包括类,
class Teacher:
def __init__(self,name ,age):
self.name = name
self.age = age
def teach(self):
print("我正在教书!")
teacher = Teacher("dota", 18) # 调用Teacher类 得到 对象teaccher
# 调用元类 得到 Teacher 类
print(type(teacher))
print(type(Teacher))
print(type(teacher.teach))
------------------------------------
>>> <class '__main__.Teacher'>
>>> <class 'type'>
>>> <class 'method'>
例子中`type(Teacher)`说明`Teacher`本身也是一个对象,可以将其赋值给其他对象,对其添加属性,将其作为函数参数传递等等。
结论:默认的元类是type
,默认情况下我们用class关键字定义的类都是由type产生的
那么class关键字都做了那些事情呢?
1、先拿到一个类名
class_name = 'Teacher'
2、然后拿到类的父类
class_bases = (object, )
根据多继承原则,这里设置为元组
3、在运行类体代码,将产生的名字放在名称空间中
class_dic = {}
class_body = """
def __init__(self,name ,age):
self.name = name
self.age = age
def teach(self):
print("我正在教书!")
"""
exec(class_body, {}, class_dic)
print(class_dic)
-----------------------------------------
>>>
4、调用元类(传入类的三大要素:类名、基类、类的名称空间)得到一个元类的对象,然后将元类的对象赋值给变量名Teacher就是我们用class自定义的哪个类
Teacher = type(class_name, class_bases, class_dic)
自定义元类
我们知道了类是通过type方法定义出来的,那么我们可不可以自己定义一个元类呢?答案是肯定的
class Mymeta(type): # 只有继承了type类的类才是自定义的元类
pass
这只是自定义元类的一个模板
自定义元类来控制Teacher类的产生,但是我们要给他定义一下规范,那么开始吧。
1、类名必须用驼峰体
2、至少继承一个父类
3、必须要有文件注释,并且注释内容不为空
import re
class Mymeta(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("必须继承一个父类!")
doc = class_dic.get("__doc__") # 获取注释
if not (doc and len(doc.strip()) > 0):
# 注释为空 和 有注释,但是注释全部是空格
raise BaseException("必须要有注释,且注释不能为空!")
class Teacher(object, metaclass=Mymeta):
"""
注释不能为空!
"""
def __init__(self,name, age):
self.name = name
self.age = age
def teach(self):
print("我正在讲......")
自定义元类来控制Teacher类的调用
对象是否能被调用,取决于是否有
__call__
方法
此时我们的元类Mymeta是没有__call__方法的,所以我们要给他定义一个
调用Teacher类做的事情:
- 先创建一个老师的空对象
- 调用老师类内的__init__方法,然后将老师的空对象连同括号内的参数的参数一同传给__init__
- 将初始化好的老师对象赋值给变量名res
class Mymeta(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("必须继承一个父类!")
doc = class_dic.get("__doc__") # 获取注释
if not (doc and len(doc.strip()) > 0):
# 注释为空 和 有注释,但是注释全部是空格
raise BaseException("必须要有注释,且注释不能为空!")
def __call__(self, *args, **kwargs):
# 创建一个老师的空对象
teacher_obj = object.__new__(self)
# 调用老师类内的__init__函数,然后将老师的空对象连同括号内的参数一同传给__init__
self.__init__(teacher_obj, *args, **kwargs)
return teacher_obj
--------------------------------------------------------
注意这里的__call__
方法其实底层已经定义好了,我们自定义只是为他附加额外的功能,就像下面的私有属性。不定义其实也没有影响,不定义就是按照默认的功能实现
补充:如何将类的对象全部设为私有
通过覆盖实现属性私有
class Mymeta(type):
def __init__(self, class_name, class_base, class_dic):
if not re.match("[A-Z]", class_name):
raise BaseException("命名应该为驼峰体!")
if len(class_base) == 0:
raise BaseException("至少继承一个父类!")
doc = class_dic.get("__doc__")
if not (doc and len(doc.strip()) > 0):
raise BaseException("必须要有文件注释,并且注释内容不为空。")
# res = OldboyTeacher("egon", 18)
def __call__(self, *args, **kwargs):
# 1. 先创建一个老师的空对象
teacher_obj = object.__new__(self)
# 2.调用老师类内的 __init__函数,然后将老师的空对象连同括号内的参数的参数一同传给__init__
self.__init__(teacher_obj, *args, **kwargs)
# 2.1 获取对象的字典
# teacher_obj.__dict__
# 2.2 创建新的对象字典
# dic = {k:v for k,v in teacher_obj.__dict__}
# 2.3 将方法(k)设置为私有
# dic = {"_%s__%s"%(self.__name__, k):v}
# 2.4 将原来的字典覆盖
teacher_obj.__dict__ = {"_%s__%s" % (self.__name__, k): v for k, v in teacher_obj.__dict__}
# 3.将初始化好的老师对象赋值给变量名res
return teacher_obj
单例模式
单例是一种设计模式,应用该模式的类指挥生成一个示例,单个实例,永远指向一个对象,创建单例模式节约内存
创建单例模式的三种方法
1、基于@classmethod
class MySQL:
_instance = None # 初始化单例对象
def __init__(self, ip, port): # 自定义的ip和port
self.ip = ip
self.port = port
@classmethod # 类方法
def settings(cls):
if cls._instance: # 如果存在单例对象
pass 返回单例对象
else: # 没有的话使用自定义的ip和port
cls._instance = cls("127.0.0.1", 3306)
return cls._instance
# 这里因为需要两个不同的的ip所以 创建两个对象
my1 = MySQL("123.0.0.1", 3306)
my2 = MySQL("123.0.0.2", 3306)
print(my1, my2)
# 这里就直接使用默认的了
my3 = MySQL.settings()
my4 = MySQL.settings()
print(my3, my4)
---------------------------------
>>> <__main__.MySQL object at 0x00000255CDF0CFD0> <__main__.MySQL object at 0x00000255CDF0CF60> # 不同对象
>>> <__main__.MySQL object at 0x00000255CDF0CE10> <__main__.MySQL object at 0x00000255CDF0CE10> # 相同对象
我们来看看没有 _instance 会怎么样!
def settings(cls):
# if cls.__instance:
# pass
# else:
cls.__instance = cls("127.0.0.1", 3306)
return cls.__instance
my3 = MySQL.settings()
my4 = MySQL.settings()
---------------------------------
>>> <__main__.MySQL object at 0x000001B6C7B8DF98> <__main__.MySQL object at 0x000001B6C7B8DF60> # 不同
其实后面两种方法实现的原理是相通的
基于元类
class Mymeta(type):
_instance = None # 默认在类内加 _ 表示不想让外界访问
def __init__(self, class_name, class_bases, class_dic):
self._instance = object.__new__(self) # Mysql 类的对象
self.__init__(self._instance, "120.0.0.1", 3306)
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
my1 = MySQL("123.0.0.1", 3306)
my2 = MySQL("123.0.0.2", 3306)
print(my1, my2)
my3 = MySQL()
my4 = MySQL()
print(my3, my4)
--------------------------------
>>> <__main__.MySQL object at 0x0000023595A1DB38> <__main__.MySQL object at 0x0000023595A1DC18>
>>> <__main__.MySQL object at 0x0000023586A6F2B0> <__main__.MySQL object at 0x0000023586A6F2B0>
基于装饰器
def outter(func):
_instance = func("120.0.0.1", 3306)
def wrapper(*args, **kwargs):
if args or kwargs:
result = func(*args, **kwargs)
return result
else:
return _instance
return wrapper
@outter
class MySQL():
def __init__(self, ip, port):
self.ip = ip
self.port = port
my1 = MySQL("123.0.0.1", 3306)
my2 = MySQL("123.0.0.2", 3306)
print(my1, my2)
my3 = MySQL()
my4 = MySQL()
print(my3, my4)
-----------------------------------
>>> <__main__.MySQL object at 0x00000196906FEBA8> <__main__.MySQL object at 0x00000196906FEC88>
>>> <__main__.MySQL object at 0x00000196FFFDF278> <__main__.MySQL object at 0x00000196FFFDF278>