元类和单例模式
一. 元类
1.1 引入
一切都源于一句话:一切皆对象。
即可调用类实例化产生对象,也可以调用元类实例化产生类。
1.2 什么是元类?
元类就是用类来实例化产生类的类。
关系:元类—实例化—>类(People)—实例化—>对象(obj)
class People:
def __init__(self,name,age)
self.name=name
self.age=age
def say(self):
print('{}-{}'.format(self.name,self.age))
# 如何得到对象:
obj=调用类()
obj=People('wth',20)
# 如何得到类
People = 调用类(...)
print(type(obj)) # ---> <class 'People'>
# 查看内置的元类:
# 1. type是内置的元类
# 2. 我们用class关键字定义的所有的类以及内置的类都是由元类type实例化产生的
print(type(People)) # ---> <class 'type'>
print(type(int)) # ---> <class 'type'>
查看内置的元类
1. type是内置的类
2. 我们用class关键字定义的所有的类以及内置的类都是由元类type实例化产生的
1.3 class关键字创造类People的步骤
1.3.1 exec的用法介绍
"""
参数一:包含一系列python代码的字符串
参数二:全局作用域(字典形式),如果不指定,默认为globals()
参数三:局部作用域(字典形式),如果不指定,默认为locals()
可以把exec命令的执行当成是一个函数的执行,会将执行期间产生的名字存放于局部名称空间中
"""
global_dic = {
'x':1,
'y':2
}
local_dic = {}
exec("""
global x,z # 这里在局部名称空间local_dic中拿到全局名称空间中的x,z进行修改
x = 100
z = 200
m = 300 # 这里在局部名称空间local_dic中定义了{"m":300}这个名字
""" ,global_dic, local_dic)
# print(global_dic) # {'x':100, 'y':2, 'z':200,......}
print(local_dic) # {'m':300}
1.3.2 实例:由exec来创建类的名称空间
类有三大特征:
-
类名
class_name = 'People'
-
类的基类
class_bases = (object,)
-
执行类体代码拿到类的名称空间
class_dic={} class_body = """ def __init__(self,name,age): self.name = name self.age = age def say(self): print(f'{self.name}---{self.age}') """ # 类体代码 全局名称空间 类的名称空间 exec(class_body, {}, class_dic) # print(class_dict) # {'__init__': <function __init__ at 0x000001FB9A58C940>, 'say': <function say at 0x000001FB9A58CA60>}
-
调用元类实例化产生类(在拿到了前面三者的基础上,传参调用)
# 类名 类的基类 类的名称空间 print(type(class_name, class_bases, class_dic)) # ---> <class '__main__.People'> # People只是类名,值才是真正的类 People = type(class_name, class_bases, class_dic)
1.4 如何自定义元类来控制类的产生
class Mymeta(type): # 只有继承了type类的类才是元类,否则就是一个普通的自定义类
# cls就是当前所在的类Mymeta # *args,**kwargs调用Mymeta时括号内的参数,返回一个空对象
def __new__(cls, *args, **kwargs):
# return type.__new__(cls, *args, **kwargs)
# return super(Mymeta, cls).__new__(cls, *args, **kwargs)
return super().__new__(cls, *args, **kwargs)
# self=执行__new__创建的空对象
# class_name=People
# class_bases=()
# class_dic=执行类体代码产生的名称空间.===>People.__dict__
# 空对象,'People',(object,),{...}
def __init__(self, calss_name, class_bases, class_dict):
# type.__init__(self,class_name,class_bases,class_dict)
super().__init__(class_name,class_bases,class_dict)
# 自定义元类控制类People产生!!!必须有注释文档+类名首字母大写+类中必须定义func才可以调用函数
if class_name.istitle():
raise NameError('类名必须大写')
# '__doc__' not in class_dict 判断文档注释不存在
# class_dict['__doc__'].strip(' \n') 判断文档注释存在且为空和换行的情况
# 提示:为什么这里使用’字典[]‘取值不会出现’__doc__‘没有报错的情况,由成员运算or的短路运算发现运行到第一个条件只有为False的情况才会执行后面的第二个条件,那么第一个条件成立表明'__doc__'是一定存在的,所以后面才可以这样使用
if '__doc__' not in class_dict or not class_dicct['__doc__'].strip(' \n'):
raise TypeError('类中必须指定文档注释,且不能为空!')
if 'func' not in class_list or not callable(class_dict['func']):
raise NameError('类中必须定义func这个可调用函数')
# People=Mymeta('People',(object,),{执行类体代码产生的名称空间})
# 调用Mymeta发生的三件事:调用Mymeta就是调用type.__call__
'''
1. 调用Mymeta类内的__new__方法造了一个空对象 ==> People
2. 调用Mymeta类内的__init__方法,完成刚刚造好的空对象初始化的操作
3. 返回初始化好的对象
'''
class People(metaclass=Mymeta):
def __init__(self,name,age):
'''文档注释'''
self.name = name
self.age = age
def func(self):
print(f'{self.name}-{self.age}')
# 强调!!!
'''
只要是调用类,那么就是一次调用
1.类内的__new__
2.类内的__init__
'''
1.5 __call__
使用介绍
# call触发条件:在对象的类(爸爸)中定义,实现在对象被调用(儿子)时触发。
calss People:
def __init__(self, name):
self.name = name
def __call__(self,*args,**kwargs):
print(self) # <__main__.People object at 0x000001A79667F490>
print(args) # (1, 2, 3)
print(kwargs) # {'a': 4}
return 111
obj=People('egon')
# 如果想让一个’对象‘可以加括号调用,需要在该’对象‘的类中添加一个方法__call__
res=obj(1,2,3,a=4)
print(res)
# 总结:
"""
对象()-->类内定义了__call__
类()-->自定义类内定义了__call__
自定义元类()-->内置元类内定义了__call__
"""
1.6 自定义元类控制类的调用(类的对象的产生)
class Mymeta(type):
# 调用People触发了Mymeta中定义的__call__的执行
def __call__(self,*args,**kwargs):
# 1.Mymeta.__call__函数会先调用People内的__new__,实例化产生一个空对象people_obj
print(self) # <class '__main__.People'>
print(args,kwargs) # ('egon', 18) {}
people_obj=self.__new__(self) # People.__new__
# people_obj=self.__new__(self) # 不推荐使用,推荐使用上面的形式,这种形式直接指名道姓的获取object中的__new__,即使People类中定义了__new__想用自己的都用不了.
print(self.__new__ is object.__new__) # False ==> People.__new__ is object.__new__
# 2.Mymeta.__call__函数调用People内的__init__完成空对象people_obj的初始化操作
# super(Mymeta,self).__init__(people_obj,*args,**kwargs)
self.__init__(people_obj,*args,**kwargs)
# 自定义元类控制类的对象产生这一步可以对对象的属性进行操作!!!!!!
# 将对象初始化完成之前对对象中所有的属性进行隐藏操作(注意:考虑对象中可能默认就指定的隐藏属性)
people_obj.__dict__={attr if attr.startswith(f"_{self.__name__}__") else f'_{self.__name__}__{attr}':value for attr,value in obj.__dict__.items()}
# 3.Mymeta.__call__函数会返回一个初始化好的对象people__obj
return people_obj
# 调用自定义元类Mymeta,产生类People,发生了三件事:
"""
People=Mymeta(class_name,class_bases,class_dict)===> type.__call__干了三件事
1.type.__call__函数会先调用Mymeta中的__new__,产生一个空类对象people_obj
2.type.__call__函数会调用Mymeta中的__init__,为这个people_obj空类对象进行初始化
3.type.__call__函数会返回一个返回值,返回值会返回一个初始化好的people_obj空类对象
"""
class People(metaclass=Mymeta):
def __init__(self, name, age):
self.name = name
self.__age = age
self._score = {}
def __new__(cls, *args, **kwargs):
return object.__new__(cls)
# 调用类People,产生obj对象,发生了三件事:
"""
obj=People('egon',18) ==> Mymeta._-call__干了三件事
1.Mymeta.__call__函数会先调用People内的__new__,产生了一个空对象obj
2.Mymeta.__call__函数接着调用People内的__init__,为obj进行初始化
3.Mymeta.__call__函数会返回一个返回值,返回值为初始化好的obj对象
"""
obj=People('egon',18)
print(obj.__dict__)
1.7 属性查找
# 属性查找的原则:对象-》类-》父类 切记!!!父类不是元类
"""
属性查找:
1.对象的属性查找:只找到了object,不会找元类
2.类的属性查找:在找到了object没有找到时,回去元类中找
使用__new__创建空对象的两种方式:
obj=object.__new__(self)
obj=self.__new__(self) # 推荐使用下面这种,这种查找方式会按照继承的方式去找,而第一种是指名道姓朝object要__new__方法。如果需要使用自己爹__new__方法或者父类的__new__方法则无法使用,有局限性
"""
class Mymeta(type):
n=444
def __call__(self,*args,**kwargs): # self=<class '__main__.Teacher'>
print(self.__new__)
obj=self.__new__(self) # Teacher.__new__
# obj=object.__new__(self)
# print(self.__new__ is object.__new__) # True
self.___init__(obj,*args,**kwargs)
return obj
class Bar(object):
# n=333
# def __new__(cls,*args,**kwargs):
# print('Bar.__new__')
pass
class Foo(Bar):
# n=222
# def __new__(cls,*args,**kwargs):
# print('Foo.__new__')
pass
class Teacher(Foo,metaclass=Mymeta):
# n=111
def __init__(self,name,age):
self.name=name
self.age=age
# def __new__(self,name,age):
# print('Foo.__new__')
obj=Teacher('lili',18)
# 对象的属性查找
# print(obj.__dict__) # {'name':'lili','age':18}
# print(obj.n)
# 类的属性查找
print(Teacher.n)
二、实现单例模式的六种方式(面试必要会三种)
2.0 单例模式
什么是单例模式?
即单个实例,指的是同一个类实例化多次的结果指向同一个对象,用于节省内存空间
什么时候使用?
如果我们从配置文件中读取配置来进行实例化,在配置相同的情况下,就没必要重复产生对象浪费内存了. 也就是说你调用类实例化对象,为对象进行了复制,
单例模式是一个软件的设计模式,为了保证一个类,
实现的思路:
单例的本质就是调用类去实例化出指向同一内存地址的对象,所以我们这里的关注点就是基于这个类.
2.1 类方法classmethod实现
# settings.py文件内容如下
"""
HOST = '127.0.0.1'
PORT = 8080
"""
import settings
class MySql:
__instance = None # instance 实例
def __init__(self, ip, port):
self.ip = ip
self.port = port
@classmethod
def singleton(cls): # singleton 单例模式
if not cls.__instance:
cls.__instance = cls(settings.IP, settings.PORT)
return cls.__instance
obj1 = MySql('127.0.0.1', 8080)
obj2 = MySql('127.0.0.1', 8080)
print(obj1 is obj2) # False
# 使用单例模式
obj1 = MySql.singleton()
obj2 = MySql.singleton()
print(obj1 is obj2) # True
2.2 通过调用类实例化对象时自动触发的__new__来实现
class MySql:
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = object.__new__(cls)
return cls._instance
obj1 = MySql()
obj2 = MySql()
print(obj1 is obj2) # True
2.3 自定义类实现
"""
思路
利用元类特性: 使用自定义元类实例化类的时候为类绑定一个默认的对象
"""
import settings
class MyMeta(type):
def __init__(self, class_name, class_bases, class_dict):
# self._instance = object.__new__(self)
# self.__init__(self._instance, settings.IP, settings.PORT)
# 上述两步可以合成下面一步
self._instance = super().__call__(settings.IP, settings.PORT)
super().__init__(class_name, class_bases, class_dict)
# 控制Ftp的调用形式: 如果调用Ftp传参了,那么将不是一个单例模式. 如果只是Ftp直接加括号调用, 那么就返回文件中拿到的单例.
def __call__(self, *args, **kwargs):
if args or kwargs:
obj = self.__new__(self)
self.__init__(obj, *args, **kwargs)
return obj
return self._instance
# MySql = MyMeta('MyMeta', (), {执行类体代码产生的名称空间})
class MySql(metaclass=MyMeta):
def __init__(self, ip, port):
self.ip = ip
self.port = port
obj1 = MySql('127.0.0.1', 8080)
obj2 = MySql('127.0.0.1', 8080)
print(obj1 is obj2) # False
obj1 = MySql()
obj2 = MySql()
print(obj1 is obj2) # True
2.4 函数装饰器的实现
'''
利用装饰器语法糖的特性: MySql = wrapper = singleton(MySql) 其中MySql就是被装饰的类.
'''
import settings
def singleton(cls):
_instance = cls(settings.IP, settings.PORT)
def wrapper(*args, **kwargs):
if args or kwargs:
return cls(*args, **kwargs)
return _instance
return wrapper
@singleton # MySql = wrapper = singleton(MySql)
class MySql:
def __init__(self, ip, port):
self.ip = ip
self.port = port
obj1 = MySql('127.0.0.1', 8080)
obj2 = MySql('127.0.0.1', 8080)
print(obj1 is obj2) # False
obj1 = MySql()
obj2 = MySql()
print(obj1 is obj2) # True
2.5 类装饰器的实现
'''
利用装饰器语法糖的特性: MySql = Singleton(MySql), 装饰MySql类就是对Singleton类的实例化. 调用MySql时就会触发实例化该对象的类Singleton中的__call__方法. (提示: self.cls就是MySql.cls --> 这里的cls就是原来的类名)
'''
import settings
class Singleton:
_instance = None
def __init__(self, cls):
self.cls = cls
def __call__(self, *args, **kwargs):
if args or kwargs:
return self.cls(*args, **kwargs)
if not self._instance:
self._instance = self.cls(settings.IP, settings.PORT)
return self._instance
@Singleton # MySql = Singleton(MySql)
class MySql:
def __init__(self, ip, port):
self.ip = ip
self.port = port
obj1 = MySql('127.0.0.1', 8080)
obj2 = MySql('127.0.0.1', 8080)
print(obj1 is obj2) # False
obj1 = MySql()
obj2 = MySql()
print(obj1 is obj2) # True
2.6 导入模块实现
"""
利用模块只要导入一次, 多次导入都是以第一次为准.
"""
# 创建singleton.py文件放入以下内容:
'''
import settings
class MySql:
def __init__(self, ip, port):
self.ip = ip
self.port = port
instance = MySql(settings.IP, settings.PORT)
'''
import singleton
obj1 = singleton.MySql('127.0.0.1', 8080)
obj2 = singleton.MySql('127.0.0.1', 8080)
print(obj1 is obj2) # False
obj1 = singleton.instance
obj2 = singleton.instance
print(obj1 is obj2) # True
三、练习
3.1 在元类中控制把自定义类的数据属性都变成大写(提示: 内置数据属性不修改)
"""
思路: 明白包含类的名称空间在自定义元类中只有__init__方法和__new__方法. 而__init__方法只能控制类的产生或者对类添加新的属性. 因此我们这里用__new__方法
参数介绍:
__new__方法的第一个参数是自定义元类本身cls
__new__方法的*args, **kwargs其中只有args有值. 其中args拿到的是(类名class_name, 类继承的基类class_bases, 类的名称空间class_dict),
返回值: __new__方法必须返回一个对象(自定的类). 因为本题是争类的数据属性的修改, 所以可以对类的名称空间class_dict进程操作, 操作完毕以后再创建的新对象(自定义的类)的class_dict是更新过后的calss_dict.
"""
class MyMeta(type):
# cls=MyMeta args=('School', (), {执行类体代码产生的名称空间}) kwargs={}
def __new__(cls, *args, **kwargs):
class_name, class_bases, class_dict = args
# 判断:
# 1) 如果属性值是可被掉用的则是函数属性, 不是则是数据属性
# 2) 或者如果是以__开头的属性则是内置的属性, 不是则是自定义的属性
class_dict = {attr if callable(class_dict[attr]) or attr.startswith('__') else attr.upper(): value for attr, value in class_dict.items()}
return super().__new__(cls, class_name, class_bases, class_dict)
# School = MyMeta('School', (), {执行类体代码产生的名称空间})
class School(metaclass=MyMeta):
school_name = '老男孩'
def func(self):
pass
print(School.__dict__)
3.2 在元类中控制自定义的类无需`__init__方法
'''
思路: 明白调用对象(自定义的类)就是触发自定义元类的__call__方法.
参数介绍:
__call__方法的第一个参数self是被调用对象(自定义的类),
__call__方法的接下来的*args, **kwargs会拿到被调用对象(自定义的类)时括号内的值.
'''
class MyMeta(type):
# self=People args=() kwargs={'name': 'egon', ....}
def __call__(self, *args, **kwargs):
if args:
raise TypeError('must use keyword argument!')
people_obj = self.__new__(self)
# self.__init__(people_obj, *args, **kwargs)
for attr, value in kwargs.items():
# people_obj.__dict__[attr.upper()] = value
setattr(people_obj, attr.upper(), value)
return people_obj
class People(metaclass=MyMeta):
pass
# MyMeta.__call__(People, name='egon', age=18):
obj = People(name='egon', age=18)
print(obj.__dict__) # {'NAME': 'egon', 'AGE': 18}
3.3 在元类中控制自定义的类产生的对象相关的属性全部为隐藏属性
# 解题思路: 在自定义元类中使用__call__.
class MyMeta(type):
# self=Student args=(name值, age值) 或 kwargs={'name': 值, 'age': 值}
def __call__(self, *args, **kwargs):
student_obj = self.__new__(self)
self.__init__(student_obj, *args, **kwargs)
# 注意: student_obj对象独有属性中可能已经指定了隐藏属性, 我们需要判断
student_obj.__dict__ = {attr if attr.startswith(f'_{self.__name__}') else f'_{self.__name__}__{attr}': value for attr, value in student_obj.__dict__.items()}
return student_obj
class Student(metaclass=MyMeta):
def __init__(self, name, age):
self.name = name
self.age = age
self.__balance = 0
self._score = {}
# MyMeta.__call__(Student, 'egon', 18):
obj = Student('egon', 18)
print(obj.__dict__)
3.4 基于元类实现单例模式
3.5 写一个类,有个name属性,如果name赋值为非字符串,就不让放
# 提示: __setattr__, __getattr__, __delattr__系列都是点拦截属性(注意: 反射的本质也是通过点)
# 实现方式一: __setitem__和__setattr__综合运用
"""
class Person:
def __init__(self, name):
self.name = name
# __setitem__: 将`对象.属性=值`的操作构造成`对象['属性']=值`的这种字典的增值操作
def __setitem__(self, key, value):
setattr(self, key, value)
# __setattr__: 添加/修改属性就会触发它的执行. (注意: 实例化是属性的添加)
def __setattr__(self, key, value):
if isinstance(value, str):
self.__dict__[key] = value
else:
raise TypeError("对不起, 请输入字符串!")
# p = Person(111) # TypeError: 对不起, 请输入字符串!
p = Person('egon')
p['name'] = 'lqz'
print(p.name) # lqz
"""
# 实现方式二: 自定义错误类型 +
class NotStrTypeError(BaseException):
def __init__(self, value):
self.value = value
super(NotStrTypeError, self).__init__()
def __str__(self):
return self.value
class Person:
def __setattr__(self, key, value):
if isinstance(value, str):
# self.__dict__[key] = value
super().__setattr__(key, value)
else:
raise NotStrTypeError('对不起, 请输入字符串类型')
p = Person()
try:
p.name = 1
except NotStrTypeError as e:
# e就是异常对象, 通过声明__str__方法, 实现打印对象展示异常的文本提示
print(type(e), e)
3.6 通过上下文管理器写一个mysql的连接,通过with管理
import pymysql
class MySQL:
def __init__(self, *args, **kwargs):
self.conn = pymysql.connect(*args, **kwargs)
self.cursor = self.conn.cursor(cursor=pymysql.cursors.DictCursor)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.cursor.close()
self.conn.close()
if exc_type:
print(exc_type)
print(exc_val)
print(exc_tb)
return True
with MySQL(
host='127.0.0.1',
port=3306,
user='root',
password='123',
database='db1',
charset='utf8',
# autocommit=True
) as self:
# 查
'''
sql = 'select username from user where password=123'
affected_rows = self.cursor.execute(sql)
print('affected_rows:', affected_rows)
fetchall = self.cursor.fetchall()
print(fetchall) # [{'username': 'yang'}, {'username': 'egon'}]
self.cursor.scroll(-2, 'relative')
fetchone = self.cursor.fetchone()
print(fetchone) # {'username': 'yang'}
# self.cursor.scroll(-1)
self.cursor.scroll(0, 'absolute')
fetchmany = self.cursor.fetchmany(2)
print(fetchmany) # [{'username': 'yang'}, {'username': 'egon'}]
'''
# 增
"""
user_info = [
('lqz', '222'),
('wxx', '333'),
]
sql = 'insert into user(username, password) values(%s, %s)'
try:
affected_rows = self.cursor.executemany(sql, user_info)
print('affected_rows:', affected_rows)
self.conn.commit() # 提交
except Exception as e:
print(e)
self.conn.rollback()
self.cursor.execute('select * from user')
fetchall = self.cursor.fetchall()
print(fetchall)
'''
[{'id': 1, 'username': 'yang', 'password': '123'},
{'id': 2, 'username': 'egon', 'password': '123'},
{'id': 17, 'username': 'lqz', 'password': '222'},
{'id': 18, 'username': 'wxx', 'password': '333'}]
'''
"""
# 删
"""
sql = 'delete from user where id=18'
try:
self.cursor.execute(sql)
self.conn.commit() # 提交
except Exception as e:
print(e)
self.conn.rollback()
self.cursor.execute('select * from user')
fetchall = self.cursor.fetchall()
print(fetchall)
'''
[{'id': 1, 'username': 'yang', 'password': '123'},
{'id': 2, 'username': 'egon', 'password': '123'},
{'id': 17, 'username': 'lqz', 'password': '222'}]
'''
"""
# 改
"""
sql = 'update user set username="egonDSB" where id=2'
try:
self.cursor.execute(sql)
self.conn.commit() # 提交
except Exception as e:
print(e)
self.conn.rollback()
self.cursor.execute('select * from user')
fetchall = self.cursor.fetchall()
print(fetchall)
'''
[{'id': 1, 'username': 'yang', 'password': '123'},
{'id': 2, 'username': 'egonDSB', 'password': '123'},
{'id': 17, 'username': 'lqz', 'password': '222'}]
'''
"""