面向对象 ,特殊成员和魔法方法
目录
特殊成员,类的双下__方法:
一: isinstance 和 issubclass
class Foo(object):
pass
obj = Foo()
print(isinstance(obj, Foo)) #判断这个类,是不是这个对象的实例
class Foo(object):
pass
class Bar(Foo):
pass
print(issubclass(Bar, Foo)) #判断子类与父类的继承关系
二: ==item系列: getitem, setitem, delitem
class Foo:
def __getitem__(self, item): #这个item是直接就出来的,必须要有这个
print('getitem a ')
return self.__dict__[item] #self是指f1实例化的自己,__dict__是指存在那个空间里的字典,
这个item就是传进来的'name'字符串形式.
def __setitem__(self, key, value):
print('setitem b ')
self.__dict__[key] = value
def __delitem__(self, key):
print('delitem c')
self.__dict__.pop(key)#
f1 = Foo()
print(f1.__dict__) #开始的时候__dict__里面并没有存放任何的值
# # 赋值操作
f1['name'] = 'egon'#相当于字典的赋值,就直接触发__setitem__方法,还需要再__setitem__里面设置一下, self.__dict__[key] = value这样就将['name'] = 'egon'赋值进去了
print(f1.__dict__) #此时打印的结果就为 setitem b {'name': 'egon'}
f1['age'] = 18 #再来增加一个
print(f1.__dict__) #{'name': 'egon', 'age': 18}
# # 删除操作
del f1['age'] #同样需要删除时,就直接触发__delitem__方法,还需要再__delitem__里面设置一下,self.__dict__.pop(key),这样字典age这个key对于的value值就被删除了
print(f1.__dict__) #{'name': 'egon'}
# 访问,获取
print(f1['name']) #以对象和字典key的结合方式,触发__getitem__方法. 还需要再__getitem__里面设置一下, return self.__dict__[item],这样就通过字典的形式访问到值
# 总结:
# 所以item系列是有访问,赋值,删除的基本功能的.但是它具有有个特点是必须要通过字典的方式来操作.
# 而__hasattr__,__getattr__,__setattr__,__delattr__ 这attr系列也同样具备访问,赋值,删除的基本功能的.
# 但是attr系列是 通常叫反射:是通过字符串映射到对象的属性,是通过字符串访问到类,对象属性的一种方法,注意区分
# 点的方式操作属性就是跟getattr相关,中括号的方式操作属性就是跟item相关
三: attr系列: getattr, setattr, delattr
class Foo:
x=1
def __init__(self,y):
self.y=y
def __getattr__(self, item):
print('----> from getattr:你找的属性不存在')
def __setattr__(self, key, value):
print('----> from setattr')
# self.key=value #这就无限递归了,你好好想想
# self.__dict__[key]=value #应该使用它
def __delattr__(self, item):
print('----> from delattr')
# del self.item #无限递归了
self.__dict__.pop(item)
#__setattr__添加/修改属性会触发它的执行
f1=Foo(10)
print(f1.__dict__) # 因为你重写了__setattr__,凡是赋值操作都会触发它的运行,你啥都没写,就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值
f1.z=3
print(f1.__dict__)
#__delattr__删除属性的时候会触发
f1.__dict__['a']=3#我们可以直接修改属性字典,来完成添加/修改属性的操作
del f1.a
print(f1.__dict__)
#__getattr__只有在使用点调用属性
四:___getattr__和 _getattribute
1.getattr :单独作为的方法使用时,是调用不存在的属性时,会自动触发getattr方法,并运用getattr里的方法.
class Foo:
def __init__(self,x):
self.x=x
def __getattr__(self, item):
print('执行的是我')
# return self.__dict__[item]
f=Foo(10)
print(f.x,)
f.xxxxxx #不存在的属性访问,触发__getattr__
2.getattribute:
class Foo1:
def __init__(self,x):
self.x=x
def __getattribute__(self, item):
print('不管是否存在,我都会执行')
f1=Foo1(10)
f1.x
f1.xxxxxx1 #不存在的属性访问,触发__getattr__
3.俩者同时出现:
class Foo1:
def __init__(self,x):
self.x=x
def __getattr__(self, item):
print('执行的是我')
# return self.__dict__[item]
def __getattribute__(self, item):
print('不管是否存在,我都会执行')
f1=Foo1(10)
f1.xxxxxx1 #当__getattribute__与__getattr__同时存在,只会执行__getattrbute__
4.先执行__getattribute__
class Foo2:
def __init__(self,x):
self.x=x
def __getattr__(self, item):
print('执行的是我')
# return self.__dict__[item]
def __getattribute__(self, item):
print('不管是否存在,我都会执行')
raise AttributeError('哈哈')
f1=Foo2(10)
f1.xxxxxx2
打印结果:
不管是否存在,我都会执行
执行的是我
总结:#当__getattribute__与__getattr__同时存在,只会执行__getattrbute__
# 除非__getattribute__在执行过程中抛出异常AttributeError
五: __repr__方法和 __str__方法
repr_与__str__都是用来.控制输出de,输出的结果是return的值,此时return的值必须为字符串的形式
repr方法
class B:
def __init__(self,age):
self.age = age
def __repr__(self ):
return self.age
b = B('18')
print(b.__dict__) #{'age': '18'}
print( b) #repr(b)---> b.__repr__() #意思是说在方法里面有__repr__方法之后,你实例化一个对象,会自动触发这个repr方法
__str__方法
class People:
def __init__(self,name,age):
self.name = name
self.age = age
def __str__(self):
return self.name #返回一个值
# return 'name:%s ,age:%s' % (self.name,self.age) #返回俩个值
obj1 = People('egon',18)
print(obj1)
# print(obj1.__dict__) #{'name': 'egon', 'age': 18}
#这样打印对象的时候,就自动触发__str__方法,将obj对象里面的值调出来
# 如果不加__str__方法,直接打印对象会<__main__.People object at 0x00000000027FEBE0>,只是一个内存地址而已
# 加了__str__方法后,就会直接打印通过对象直接打印他的值
那么要是俩种方法都同时存在于呢,优先级是哪个,看例1:
class B:
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return '第三季度涉及到'
def __str__(self ):
return self.age
f2 = B('egon','18')#必须为字符串
print(f2)
# !!!__repr_与__str__都是用来.控制输出de,输出的结果是return的值,此时return的值必须为字符串的形式
# str函数或者print函数--->obj.__str__()
# repr或者交互式解释器--->obj.__repr__()
# 如果__str__没有被定义,那么就会使用__repr__来代替输出
# 注意:这俩方法的返回值必须是字符串,否则抛出异常
# #实例化对象的时候,都必须为字符串
# # 一般情况下建议用repr
例2:
class Course:#创建一个课程类,他有自己的属性,但是创建课程这个动作是管理员来完成的,所以是由管理员来做这个事情
def __init__(self, name, price, period, kind):
self.name = name
self.price = price
self.period = period
self.kind = kind
def __repr__(self):
s = ''
for k in self.__dict__: #循环这个课程类的信息,他存在内存的dict字典
s += '%s:%s\n'%(k,self.__dict__[k])
return s
print(Course) #他会自动调用__reor__方法
六:format
1.用法1
class Date:
def __init__(self,mon,day,year):
self.year = year
self.mon = mon
self.day = day
def __format__(self, format_spec): #自动出现format_spec
return '{0.year}{0.mon}{0.day}'.format(self)
d1= Date(2018,8,8)
print(d1) #<__main__.Date object at 0x00000000027FD940>
print(format(d1)) # 820188
return self.day #这样写是错误的
ypeError: __format__ must return a str, not int会报错,是因为return的是必须是字符串,而self.day,实例化的是int类型
ormat_dict1 = {
'ymd':'{0.year}{0.mon}{0.day}',
'm-d-y':'{0.mon}:{0.day}:{0.year}',
'y-m-d':'{0.year}-{0.mon}-{0.day}'
} #定义这样三种格式的字典
class Date:
def __init__(self,mon,day,year):
self.mon = mon
self.day = day
self.year = year
def __format__(self, format_spec): #如果下面不传参数的话,那format_spec就会变成空格也不会报错,但是要是传参数呢
fm = format_dict1[format_spec] #如果他的选择是'ymd'的话,fm就等于是从format_dict的字典里面,去选择.取到最终的格式而ymd==format_spec
return fm.format(self) #就返回fm里面的格式
d2= Date(2018,8,8)
print(format(d2,'ymd')) #用户如果传的是ymd,那么就传给了format_spec ,即ymd==format_spec
七: len
class A:
def __init__(self):
self.a = 1
self.b = 2
def __len__(self):
return len(self.__dict__)
a = A()
print(len(a))
#类内部定义了__len__方法后,打印对象的长度.eg:print(len(对象名))就触发内部的__len__方法.
八: call,对象后面加括号,触发执行,执行时调用这个类下面的__cal__l方法==
class Foo:
def __init__(self):
pass
def __call__(self, *args, **kwargs):
print('__call__',pppp)
obj = Foo() # 执行 __init__
obj() # 执行 __call__
#类内部定义__call__方法后,直接实例化一个对象后,直接用对象名(),
就可以触发__call__,打印__call__里的所有信息
九: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,即:输出类
十:__del 析构方法只在实例被删除的时候才会触发__del__方法
f = open('txt文件名')
f.read()
f.close() 回收操作系统的资源
# 拿到一个f 的对象的话,涉及到俩方面的资源
# 1.如果是一个赋值,那一定是应用程序的资源
# 2.后边的是一个操作系统的资源,在程序关闭之前把操作系统的资源关闭掉
# 就是我们在学习文件操作的时候一定要记得关闭文件,如果程序结束了,操作系统的资源即文件没有关掉,那么这资源就剩下了,就占内存空间
对于# __del__方法的引导
'''
# __del__方法,具体如下
# class Open:
# def __init__(self,filename):
# print('open file...') #模拟打开文件
# self.filename = filename
#
# def __del__(self):
# print('回收操作系统资源')
#
# f = Open('settings.py') #f是变量
#
# print('======表示程序结束====')
'''
open file...
======表示程序结束====
回收操作系统资源
看打印的顺序,简单结论说:
当f这个实例化对象去执行文件打开操作的时候,就模拟打开文件了,所以打印 open file...[就是去打开一个文件执行操作]
然后打开文件后,执行了相关的操作之后,句模拟打印======表示程序结束====,程序结束之后,程序自动的会回收f这个变量,但是遗留下文件资源
但是这个文件还没有关闭呀,所以,造类里面设置了__del__之后就自动的将文件关闭,这样一个功能,为了节省内存空间嘛
老师总结:在对象被删除的时候,会自动先触发__del__方法,再把对象删掉
十一:迭代器协议: _iter 和 next
例子:说明类对象得到一个数,通过内置的迭代器方法,将得到的数进行迭代输出,并设置到一定数之后就停止输出
class Bar:
def __init__(self,a):
self.a = a
def __iter__(self):
return self
def __next__(self): #执行一次就返回前俩个的和
self.a += 1 #让a得到的数字17进行迭代+1,或者说是进行累加1
if self.a == 21: #如果a得到的数等于21
raise StopIteration('终止了') #就停止+1的操作,抛出异常,这个异常并不是报错,只是跟程序本身说停止叠加1的操作,并不是真的报错
return self.a
c = Bar(17)
for i in c:
print(i)
把一个类对象变成一个可迭代的,且类里面必须要有__iter__方法,表示可迭代,要知道光可以迭代并没有实际用处,
根据之前学习迭代器的理论,得知,next是取下一个值的,所以此时运用到类对象的时候,就提供了相应的内置方法,__next__来取值.
实例1.实现斐波那契数列
class Fib:
def __init__(self):
self.a = 1
self.b = 1
def __iter__(self):
return self
def __next__(self):
if self.a > 100:
raise StopIteration('可以停止了')
self.a, self.b = self.b, self.a + self.b # 主要在这里,结合a,b = b, a理解,等于一个交换值
return self.a
f1 = Fib()
print(next(f1))
print(next(f1))
print(next(f1))
print(next(f1))
print(next(f1))
print('===================================')
for i in f1:
print(i) #如果单纯这样写出来的结果就是无限循环的数列.所以需要再__next__方法里面加上判断
十二: 单例模式: new
创造一个对象,一个类始终只有一个实例
# __new__: 单例模式,创造一个对象,一个类始终只有一个实例
# 当你第一次实例化这个类的时候,就创建一个实例化的类,当你再来 实例化的时候,就用之前创建的对象
# (如果之前已经有创建一个对象了,就不再创建了,会在__new__里去控制
,如果你来一个新的不同的对象,你来一个我就new一个,可以理解为创造一个具体的实物手机,你来一个实物手机我就创建一个实物手机,但你要在同一个实物手机的基础上做不同的处理,比如换手机壳的操作,手机壳只能戴一个,你换各种颜色的手机壳可以,但是你只能讲前面一个手机壳去掉,这里就理解为覆盖掉)
class A:
_instance = False
def __init__(self,name,age):
self.name = name
self.age = age
def __new__(cls, *args, **kwargs):
if not cls._instance: #如果没有创建这个对象
cls._instance = object.__new__(A) #就去创建一个新的对象
return cls._instance #将创建的对象返回
else: #如果有这个对象,就不再创建新的对象,且如果有不同对象有相同属性的话,那么后一个对象的属相会覆盖前一个对象相同的属性,相当于相同属性之间,只有一块内存空间
return cls._instance
egon = A('egon',18)
nezha = A('nezha',25)
print(nezha)
print(egon)
print(egon.name) #nezha
print(nezha.name)#nezha 不同对象会覆盖前一个相同属性的值,
egon.cloth = '衣服'
print(nezha.cloth)
print(egon.cloth)
十三: eq
判断俩个对象的属性的值是否相等,用==去触发__eq__的执行
class A:
def __init__(self):
self.a = 1
self.b = 2
def __eq__(self,obj):
if self.a == obj.a and self.b == obj.b:
return True
a = A()
b = A()
print(a == b)
有__eq__,判断的是俩个对象的属性是否相等
无__eq__,判断的是内存地址
十四: __hash__判断一个对象是否可哈希,且是根据内存地址去哈希的
class A:
def __init__(self):
self.a = 1
self.b = 2
def __hash__(self):
return hash(str(self.a)+str(self.b))
a = A()
print(hash(a))