面向对象之魔法方法和单例模块
## 经常用到的双下方法
凡是在类内部定义,以“__开头__结尾”的方法都称之为魔法方法,又称“类的内置方法”, 这些方法会在某些条件成立时触发。
__init__: 在类加括号调用时触发。
__getattr__: 会在对象.属性时,“属性没有”的情况下才会触发;对象.__dict__[属性]不会触发__getattr__,会报keyerror。
__setattr__: 会在 “对象.属性 = 属性值” 时触发。即:设置(添加/修改)属性会触发它的执行;
__delarttr__:在删除对象属性(用del 对象.属性)时,执行该方法。
__getattribute__:会在对象.属性时触发,不管有没有该属性都会触发;在获取属性时如果存在__getattribute__则先执行该函数,如果没有拿到属性则继续调用
__del__: 手动删除对象时立马执行,或是程序运行结束时也会自动执行(运行结束垃圾回收执行)
class A:
# def __setattr__(self, key, value):
# # print(key)
# # print(value)
# print("__setattr__")
# self.__dict__[key] = value
#
# def __delattr__(self, item):
# print("__delattr__")
# print(item)
# self.__dict__.pop(item)
# pass
def __getattr__(self, item):
print("__getattr__")
return 1
def __getattribute__(self, item):
print("__getattribute__")
# return self.__dict__[item] # 递归调用__getattribute__方法
return super().__getattribute__(item)
#
a = A()
# a.name = "jack"
# # print(a.name)
#
# # del a.name
# print(a.name)
# print(a.xxx)
# a.name = "xxx"
print(a.name)
# b =A()
# b.__dict__["name"] = "jack"
# print(b.name)
__str__ 会在对象被转换为字符串时,转换的结果就是这个函数的返回值
使用场景:我们可以利用该函数来自定义,对象的打印格式
__call__: 会在对象被调用时触发。
__new__: 会在__init__执行前触发。
class Uderline_func():
x = 100
# def __new__(cls, *args, **kwargs):
#
# print('在__init__执行之前触发我,造一个空对象!')
def __init__(self):
print('类加括号调用的时候触发我!')
def __call__(self, *args, **kwargs):
print('对象加括号调用的时候触发我!')
def __str__(self):
print('对象被打印的时候触发我!')
return '必须要写return返回一个字符串!不然报错"TypeError: __str__ returned non-string (type NoneType)"'
u = Uderline_func()
u()
print(u)
[]的实现原理
__getitem__ 当你用中括号去获取属性时 执行
__setitem__ 当你用中括号去设置属性时 执行
__delitem__ 当你用中括号去删除属性时 执行
class Foo:
def __init__(self,name):
self.name=name
def __getitem__(self, item):
print(self.__dict__[item])
def __setitem__(self, key, value):
self.__dict__[key]=value
# self.age = value # 也可以给对象添加属性
def __delitem__(self, key):
print('del obj[key]时,我执行')
self.__dict__.pop(key)
def __delattr__(self, item):
print('del obj.key时,我执行')
self.__dict__.pop(item)
f1=Foo('sb')
f1['age']=18
# print(f1.__dict__)
f1['age1']=19
# del f1.age1
# del f1['age']
f1['name']='alex'
f1.xxx = 111
print(f1.__dict__) # {'name': 'alex', 'age': 18, 'age1': 19, 'xxx': 111}
__slots__是什么:是一个类属性,属性值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性),用于优化对象内存占用,优化的原理,将原本不固定的属性数量,变得固定了。这样的解释器就不会为这个对象创建名称空间,所以__dict__也没了,从而达到减少内存开销的效果 。
注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。另外当类中出现了slots时将导致这个类的对象无法在添加新的属性
# slots的使用
class Person:
__slots__ = ["name"]
def __init__(self,name):
self.name = name
p = Person("jck")
# 查看内存占用
# print(sys.getsizeof(p))
# p.age = 20 # 无法添加
# dict 没有了
print(p.__dict__)
__doc__:查看类中注释
class Foo:
'我是描述信息'
pass
print(Foo.__doc__)
class Foo:
'我是描述信息'
pass
class Bar(Foo):
pass
print(Bar.__doc__) #该属性无法继承给子类
__module__和__class__
__module__:表示当前操作的对象在那个模块
__class__:表示当前操作的对象的类是什么
class C:
def __init__(self):
self.name = ‘SB'
from lib.aa import C
obj = C()
print obj.__module__ # 输出 lib.aa,即:输出模块
print obj.__class__ # 输出 lib.aa.C,即:输出类
上下文管理
在python中,上下文可以理解为是一个代码区间,一个范围 ,例如with open 打开的文件仅在这个上下文中有效
__enter__:表示进入上下文
__exit__: 表示退出上下文
我们知道在操作文件对象的时候可以这么写
with open('a.txt') as f:
'代码块'
上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法
class Open:
def __init__(self,name):
self.name=name
def __enter__(self):
print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')
# return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('with中代码块执行完毕时执行我啊')
with Open('a.txt') as f:
print('=====>执行代码块')
# print(f,f.name)
'''
出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量
=====>执行代码块
with中代码块执行完毕时执行我啊
'''
exit()中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法执行
class MyOpen:
def __init__(self,path):
self.path = path
def __enter__(self):
self.file = open(self.path,'r',encoding='utf8')
print('enter...')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('exit...')
# print('错误类型是:%s,错误的内容是:%s,错误的跟踪信息是:%s'%(exc_type,exc_val,exc_tb))
self.file.close()
# return True
with MyOpen('a.txt') as m:
print('start...')
print(m.file.read())
阿斯蒂芬斯蒂芬
print('over...')
'''
enter...
start...
fsdfs
啦啦啦
哈哈哈哈!
exit...
Traceback (most recent call last):
错误类型是:<class 'NameError'>,错误的内容是:name '阿斯蒂芬斯蒂芬' is not defined,错误的跟踪信息是:<traceback object at 0x000000000A008048>
File "G:/Python代码日常/代码发布/day03/test.py", line 21, in <module>
阿斯蒂芬斯蒂芬
NameError: name '阿斯蒂芬斯蒂芬' is not defined
'''
如果__exit()返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行
class Open:
def __init__(self,name):
self.name=name
def __enter__(self):
print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')
def __exit__(self, exc_type, exc_val, exc_tb):
print('with中代码块执行完毕时执行我啊')
print(exc_type)
print(exc_val)
print(exc_tb)
return True
with Open('a.txt') as f:
print('=====>执行代码块')
raise AttributeError('***着火啦,救火啊***')
print('0'*100) #------------------------------->会执行
class Open:
def __init__(self,filepath,mode='r',encoding='utf-8'):
self.filepath=filepath
self.mode=mode
self.encoding=encoding
def __enter__(self):
# print('enter')
self.f=open(self.filepath,mode=self.mode,encoding=self.encoding)
return self.f
def __exit__(self, exc_type, exc_val, exc_tb):
# print('exit')
self.f.close()
return True
def __getattr__(self, item):
return getattr(self.f,item)
with Open('a.txt','w') as f:
print(f)
f.write('aaaaaa')
f.wasdf #抛出异常,交给__exit__处理
用途或者说好处:
1.使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预
2.在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处
迭代器协议
迭代器是指具有__iter__和__next__的对象,可以为对象增加这两个方法来让对象变成一个迭代器
# 案例
class MyRange:
def __init__(self,start,end,step):
self.start = start
self.end = end
self.step = step
def __iter__(self):
return self
def __next__(self):
a = self.start
self.start += self.step
if a < self.end:
return a
else:
raise StopIteration
for i in MyRange(1,10,2):
print(i)
运算符重载
当我们在使用某个符号时,python解释器都会为这个符号定义一个含义,同时调用对应的处理函数, 当我们需要自定义对象的比较规则时,就可在子类中覆盖 大于 等于 等一系列方法....
案例:
原本自定义对象无法直接使用大于小于来进行比较 ,我们可自定义运算符来实现,让自定义对象也支持比较运算符
class Student(object):
def __init__(self,name,height,age):
self.name = name
self.height = height
self.age = age
def __gt__(self, other):
# print(self)
# print(other)
# print("__gt__")
return self.height > other.height
def __lt__(self, other):
return self.height < other.height
def __eq__(self, other):
if self.name == other.name and self.age == other.age and self.height == other.height:
return True
return False
stu1 = Student("jack",180,28)
stu2 = Student("jack",180,28)
# print(stu1 < stu2)
print(stu1 == stu2)
上述代码中,other指的是另一个参与比较的对象,
大于和小于只要实现一个即可,符号如果不同 解释器会自动交换两个对象的位置
单例模式
单例模式:多次实例化的结果指向同一个实例
方式1
# @classmethod(用类绑定方法)
import settings
class MySQL:
__instance=None
def __init__(self, ip, port):
self.ip = ip
self.port = port
@classmethod
def from_conf(cls):
if cls.__instance is None:
cls.__instance=cls(settings.IP, settings.PORT)
return cls.__instance
obj1=MySQL.from_conf()
obj2=MySQL.from_conf()
obj3=MySQL.from_conf()
# obj4=MySQL('1.1.1.3',3302)
print(obj1)
print(obj2)
print(obj3)
# print(obj4)
方式2
# 用类装饰器1
import settings
def singleton(cls):
_instance=cls(settings.IP,settings.PORT)
def wrapper(*args,**kwargs):
if len(args) !=0 or len(kwargs) !=0:
obj=cls(*args,**kwargs)
return obj
return _instance
return wrapper
@singleton #MySQL=singleton(MySQL) #MySQL=wrapper
class MySQL:
def __init__(self, ip, port):
self.ip = ip
self.port = port
# obj=MySQL('1.1.1.1',3306) #obj=wrapper('1.1.1.1',3306)
# print(obj.__dict__)
obj1=MySQL() #wrapper()
obj2=MySQL() #wrapper()
obj3=MySQL() #wrapper()
obj4=MySQL('1.1.1.3',3302) #wrapper('1.1.1.3',3302)
print(obj1)
print(obj2)
print(obj3)
print(obj4)
# 用类装饰器2
class SingleTon:
_instance_dict = {}
def __init__(self, cls_name):
self.cls_name = cls_name
def __call__(self, *args, **kwargs):
if self.cls_name not in SingleTon._instance_dict:
SingleTon._instance_dict[self.cls_name] = self.cls_name(*args, **kwargs)
return SingleTon._instance_dict.get(self.cls_name)
@SingleTon # 这个语法糖相当于Student = SingleTon(Student),即Student是SingleTon的实例对象
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
stu1 = Student('jack', 18)
stu2 = Student('jack', 18)
print(stu1 is stu2)
print(stu1.__dict__, stu2.__dict__)
# 原理:在函数装饰器的思路上,将装饰器封装成类。
# 程序执行到与语法糖时,会实例化一个Student对象,这个对象是SingleTon的对象。
# 后面使用的Student本质上使用的是SingleTon的对象。
# 所以使用Student('jack', 18)来实例化对象,其实是在调用SingleTon的对象,会触发其__call__的执行
# 所以就在__call__中,判断Student类有没有实例对象了。
方式3
# 调用元类
import settings
class Mymeta(type):
def __init__(self,class_name,class_bases,class_dic): # self:<class '__main__.MySQL'>
#self=MySQL这个类
self.__instance=self(settings.IP,settings.PORT) # 触发Mymeta类中的__call__方法
def __call__(self, *args, **kwargs): # # self:<class '__main__.MySQL'>
# self=MySQL这个类
if len(args) != 0 or len(kwargs) != 0:
obj=self.__new__(self) # obj : <__main__.MySQL object at 0x0000020531202B88>
self.__init__(obj,*args, **kwargs) # self:<class '__main__.MySQL'>,MySQL类调用MySQL类中的__init__方法
return obj
else:
return self.__instance
class MySQL(metaclass=Mymeta): #MySQL=Mymeta(...)
def __init__(self, ip=None, port=None):
self.ip = ip
self.port = port
obj1=MySQL()
obj2=MySQL()
print(obj1 is obj2)
obj3=MySQL('1.1.1.3',3302)
print(obj1, obj2, obj3)
# 原理:类定义时会调用元类下的__init__,类调用(实例化对象)时会触发元类下的__call__方法
# 类定义时,给类新增一个空的数据属性,
# 第一次实例化时,实例化之后就将这个对象赋值给类的数据属性;第二次再实例化时,直接返回类的这个数据属性
# 和方式3的不同之处1:类的这个数据属性是放在元类中自动定义的,而不是在类中显示的定义的。
# 和方式3的不同之处2:类调用时触发元类__call__方法判断是否有实例化对象,而不是在类的绑定方法中判断
'''
True
<__main__.MySQL object at 0x000001FE0F7E10C8>
<__main__.MySQL object at 0x000001FE0F7E10C8>
<__main__.MySQL object at 0x000001FE0F7E1208>
'''
方式4
# 利用模块多次导入只产生一次名称空间,多次导入只沿用第一次导入成果。
def f1():
from singleton import instance
print(instance)
def f2():
from singleton import instance,My
SQL
print(instance)
obj=MySQL('1.1.1.3',3302)
print(obj)
f1()
f2()
'''
本部分主要是补充介绍多线程并发情况下,多线程高并发时,如果同时有多个线程同一时刻(极端条件下)事例化对象,那么就会出现多个对象,这就不再是单例模式了。
解决这个多线程并发带来的竞争问题,第一个想到的是加互斥锁,于是我们就用互斥锁的原理来解决这个问题。
解决的关键点,无非就是将具体示例化操作的部分加一把锁,这样同时来的多个线程就需要排队。
这样一来只有第一个抢到锁的线程实例化一个对象并保存在_instance中,同一时刻抢锁的其他线程再抢到锁后,不会进入这个判断if not cls._instance,直接把保存在_instance的对象返回了。这样就实现了多线程下的单例模式。
此时还有一个问题需要解决,后面所有再事例对象时都需要再次抢锁,这会大大降低执行效率。解决这个问题也很简单,直接在抢锁前,判断下是否有单例对象了,如果有就不再往下抢锁了(代码第11行判断存在的意义)。
'''
import threading
class Student:
_instance = None # 保存单例对象
_lock = threading.RLock() # 锁
def __new__(cls, *args, **kwargs):
if cls._instance: # 如果已经有单例了就不再去抢锁,避免IO等待
return cls._instance
with cls._lock: # 使用with语法,方便抢锁释放锁
if not cls._instance:
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance
6种方法实现单例模式:https://www.jb51.net/article/202178.htm