Python开发之路- 面向对象进阶 attr方法 迭代器协议
1.如何取当前文件的模块对象 自己
import sys a = sys.modules[__name__] print(a)
2.isinstance(obj,cls)检查是否obj是否是类 cls 的对象
issubclass(sub, super)检查sub类是否是 super 类的派生类
3.__getattribute__ 是__getattr__的大哥 不管属性存不存在都会运行
class Foo: def __init__(self,name): self.name = name def __getattr__(self, item): print('我是小弟') def __getattribute__(self, item): print('我是大哥') raise AttributeError('出错了 小弟你来干') f1 = Foo('chris') f1.name
除非抛出异常时 才会让getattr工作 !!! 其他情况大哥在时,getattr都不会触发
4.__setitem__,__getitem,__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 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 f1['age1']=19 del f1.age1 del f1['age'] f1['name']='alex' print(f1.__dict__)
5.__str__,__repr__,__format__
(1)__str__ 必须return一个值且必须是字符串 定义你打印时输出的值 既自定义str方法
class Foo: def __init__(self,name): self.name = name def __str__(self): return '输出的是我 %s ' %self.name f1 = Foo('chris') print(f1)
此时输出的值为: 输出的是我 chris 原本应为对象的内存地址
(2)__repr__ 在解释器中实现 若与__str__共存时,会触发__str__的方法
(3)__format__字符串格式化
基础的:
a = '{0}-{1}-{2}'.format('apple','ball','class') print(a)
进阶版自定义格式:
format_dict = { 'y-m-d':'{0.year}-{0.mon}-{0.day}', 'y:m:d':'{0.year}:{0.mon}:{0.day}', } class Foo: def __init__(self,year,mon,day): self.year = year self.mon = mon self.day = day def __format__(self, format_spec): if not format_spec or format_spec not in format_dict: format_spec= 'y:m:d' fm = format_dict[format_spec] return fm.format(self) f1 = Foo(2020,3,1) print(format(f1,'am-d'))
6.__slots__ 用于省内存 定义在类变量 定义后类的内部没有了__dict__ 同时限制了实例创建的属性个数 要慎用!!
class Foo: __slots__ = ['name','age'] #等于定义了两个key 此时Foo这个类的内部就没有__dict__这个属性 f1 = Foo() f1.age = 19 print(f1.age) ##等同于--> f1.__slots__['age'] = 18
具体介绍:
''' 1.__slots__是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性) 2.引子:使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的) 3.为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__ 当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个 字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__一个不好的地方就是我们不能再给 实例添加新的属性了,只能使用在__slots__中定义的那些属性名。 4.注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该 只在那些经常被使用到 的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。 关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。 更多的是用来作为一个内存优化工具。 ''' class Foo: __slots__='x' f1=Foo() f1.x=1 f1.y=2#报错 print(f1.__slots__) #f1不再有__dict__ class Bar: __slots__=['x','y'] n=Bar() n.x,n.y=1,2 n.z=3#报错
7.__doc__ 文档属性 无法继承给子类
class Foo:
'我是文档属性'
class Boo(Foo):
pass
print(Boo.__doc__) # ---->此时输出值为None
8.__module__和__class__ 查找对象来自于哪个模块和那个类
9. __del__析构方法
析构方法,当对象在内存中被释放时,自动触发执行。
注:如果产生的对象仅仅只是python程序级别的(用户级),那么无需定义__del__,如果产生的对象的同时还会向操作系统发起系统调用,即一个对象有用户级与内核级两种资源,比如(打开一个文件,创建一个数据库链接),则必须在清除对象的同时回收系统资源,这就用到了__del__
10. __call_ 本质其实是对象在调用类是 执行了类下的__call__方法
对象后面加括号,触发执行。
注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
class Foo: def __init__(self): pass def __call__(self, *args, **kwargs): print('__call__') obj = Foo() # 执行 __init__ obj() # 执行 __call__
11.迭代器协议 1.要求对象必须是可迭代的 既含有__iter__方法 2.要求提供一个__next__方法
for 循环 本质就是在调用对象下 __iter__方法 或者说 iter()!!!
模拟列表内部for循环机制:
class Foo: i = 0 def __init__(self,num,start): self.num = list(num) self.start = start def __iter__(self): return self def __next__(self): if self.start == len(self.num): raise StopIteration('终止迭代了') a = self.num[self.start] self.start +=1 return a f1 = Foo('chris',0) for i in f1: print(i)
斐波那契数列
#斐波那契数列 class Fib: def __init__(self,a,b,stop): self.a = a self.b = b self.stop = stop def __iter__(self): return self def __next__(self): if self.b >self.stop: raise StopIteration('停止迭代了') self.a,self.b = self.b,self.a+self.b return '-->'+str(self.a) f1 = Fib(2,3,100) for i in f1: print(i)
12.描述符(__get__,__set__,__delete__) 被描述的对象才会起作用
1 描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议
__get__():调用一个属性时,触发
__set__():为一个属性赋值时,触发
__delete__():采用del删除属性时,触发
#描述符Str class Str: def __get__(self, instance, owner): print('Str调用') def __set__(self, instance, value): print('Str设置...') def __delete__(self, instance): print('Str删除...') #描述符Int class Int: def __get__(self, instance, owner): print('Int调用') def __set__(self, instance, value): print('Int设置...') def __delete__(self, instance): print('Int删除...') class People: name=Str() age=Int() def __init__(self,name,age): #name被Str类代理,age被Int类代理, self.name=name self.age=age #何地?:定义成另外一个类的类属性 #何时?:且看下列演示 p1=People('alex',18) #描述符Str的使用 p1.name p1.name='egon' del p1.name #描述符Int的使用 p1.age p1.age=18 del p1.age
描述符的使用相当于代理,要真正设值则要在set方向下定义,如下:
class Foo: def __get__(self, instance, owner): print('触发get') def __set__(self, instance, value): print('触发set') instance.__dict__['x'] = value def __delete__(self, instance): print('触发delete') class Bar: x = Foo() def __init__(self,n): self.x = n f1 = Bar(10) print(f1.__dict__) f1.x = 222 print(f1.__dict__)
描述符分两种
一 数据描述符:至少实现了__get__()和__set__()
二 非数据描述符:没有实现__set__()
注意事项:
一 描述符本身应该定义成新式类,被代理的类也应该是新式类
二 必须把描述符定义成这个类的类属性,不能为定义到构造函数中
三 要严格遵循该优先级,优先级由高到底分别是
1.类属性
2.数据描述符
3.实例属性
4.非数据描述符
5.找不到的属性触发__getattr__()
#描述符Str class Str: def __get__(self, instance, owner): print('Str调用') def __set__(self, instance, value): print('Str设置...') def __delete__(self, instance): print('Str删除...') class People: name=Str() def __init__(self,name,age): #name被Str类代理,age被Int类代理, self.name=name self.age=age #基于上面的演示,我们已经知道,在一个类中定义描述符它就是一个类属性,存在于类的属性字典中,而不是实例的属性字典 #那既然描述符被定义成了一个类属性,直接通过类名也一定可以调用吧,没错 People.name #恩,调用类属性name,本质就是在调用描述符Str,触发了__get__() People.name='egon' #那赋值呢,我去,并没有触发__set__() del People.name #赶紧试试del,我去,也没有触发__delete__() #结论:描述符对类没有作用-------->傻逼到家的结论 ''' 原因:描述符在使用时被定义成另外一个类的类属性,因而类属性比二次加工的描述符伪装而来的类属性有更高的优先级 People.name #恩,调用类属性name,找不到就去找描述符伪装的类属性name,触发了__get__() People.name='egon' #那赋值呢,直接赋值了一个类属性,它拥有更高的优先级,相当于覆盖了描述符,肯定不会触发描述符的__set__() del People.name #同上 '''
#描述符Str class Str: def __get__(self, instance, owner): print('Str调用') def __set__(self, instance, value): print('Str设置...') def __delete__(self, instance): print('Str删除...') class People: name=Str() def __init__(self,name,age): #name被Str类代理,age被Int类代理, self.name=name self.age=age p1=People('egon',18) #如果描述符是一个数据描述符(即有__get__又有__set__),那么p1.name的调用与赋值都是触发描述符的操作,于p1本身无关了,相当于覆盖了实例的属性 p1.name='egonnnnnn' p1.name print(p1.__dict__)#实例的属性字典中没有name,因为name是一个数据描述符,优先级高于实例属性,查看/赋值/删除都是跟描述符有关,与实例无关了 del p1.name 数据描述符>实例属性
class Foo: def func(self): print('我胡汉三又回来了') f1=Foo() f1.func() #调用类的方法,也可以说是调用非数据描述符 #函数是一个非数据描述符对象(一切皆对象么) print(dir(Foo.func)) print(hasattr(Foo.func,'__set__')) print(hasattr(Foo.func,'__get__')) print(hasattr(Foo.func,'__delete__')) #有人可能会问,描述符不都是类么,函数怎么算也应该是一个对象啊,怎么就是描述符了 #笨蛋哥,描述符是类没问题,描述符在应用的时候不都是实例化成一个类属性么 #函数就是一个由非描述符类实例化得到的对象 #没错,字符串也一样 f1.func='这是实例属性啊' print(f1.func) del f1.func #删掉了非数据 f1.func() 实例属性>非数据描述符
class Foo: def func(self): print('我胡汉三又回来了') def __getattr__(self, item): print('找不到了当然是来找我啦',item) f1=Foo() f1.xxxxxxxxxxx
13.软件开发规范
目录结构需要规范,有以下几层:
| ---bin/ 存放启动文件,起始文件 --- 程序的入口
| ---conf/ 存放配置文件 存放文件的路径拼接
| ---db/ 存放数据文件
| ---lib/ 公共的类、库文件 存放公共功能
| ---log/ 日志信息
| ---src/ 存放主文件 也称core目录
!!!重点!!保证在其他程序下也能运行!!!在自己文件下加入要调用文件的环境变量:
import sys,os BASE_DIR = os.path.dirname(os.path.dirname(__file__)) sys.path.append(BASE_DIR)
14.__enter__ , __exit__ 上下文管理协议
f = open('a.txt','r') 本质就是在调用open这个类的方法 进行实例化
class Open: def __init__(self,name,mode,encoding): self.name = name self.mode = mode self.encoding = encoding def __enter__(self): g = open(self.name,self.mode,encoding=self.encoding) return g def __exit__(self, exc_type, exc_val, exc_tb): g.close() return True #把异常吞了 with Open('a.txt','w+','utf-8') as g: g.write('agsafeqwgsag')
用途或者说好处:
1.使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预
2.在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处