面向对象之魔法方法和单例模块

## 经常用到的双下方法

	凡是在类内部定义,以“__开头__结尾”的方法都称之为魔法方法,又称“类的内置方法”,  这些方法会在某些条件成立时触发。
__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

posted on 2019-10-20 01:17  jueyuanfengsheng  阅读(122)  评论(0编辑  收藏  举报