python基础之面向对象(进阶篇)
上篇回顾
- 面向对象是一种编程方式,此编程方式的实现是基于对 类 和 对象 的使用
- 类 是一个模板,模板中包装了多个“函数”供使用(可以讲多函数中公用的变量封装到对象中)
- 对象,根据模板创建的实例(即:对象),实例用于调用被包装在类中的函数
- 面向对象三大特性:封装、继承和多态
- 静态属性(@property) 特点:将函数属性伪装成数据属性(封装逻辑)
- 静态方法(@staticmethod) 跟类和实例无关系,名义上归属类管理,但不能使用类变量和实例变量
- 类方法(@classmethod) 跟实例没有关系,类自己调用;只能访问类的属性,不能访问实例属性,不需要self参数,自动加cls参数
- 面向对象的专业术语
本篇介绍
反射
- 根据字符串的形式去某个对象中操作它的成员
- 四个可以实现反射的函数(也适用于对象和类)
class Sea: # def __init__(self,name,country,addr): self.name = name self.country = country self.addr = addr def sea_wave(self): print("一波儿海啸正在来袭") s1 = Sea("东海","哥雅王国","风车村") print(hasattr(s1,"name")) # 判断有没有 print(getattr(s1,"name123")) # 找不到报错 print(getattr(s1,"name123",None)) # 可以设置返回值则不报错 del s1.name # 方式1 print(s1.__dict__) delattr(s1,"name") # 方式2 print(s1.__dict__) setattr(s1,"age",10000) # 设置一个东西 print(s1.__dict__)
为什么用到反射(举个简单的小例子)
有俩程序员,一个alex,一个是egon,alex在写程序的时候需要用到egon所写的类,但是egon去跟女朋友度蜜月去了,还没有完成他写的类,alex想到了反射,使用了反射机制alex可以继续完成自己的代码,等egon度蜜月回来后再继续完成类的定义并且去实现alex想要的功能。
总之反射的好处就是,可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种‘后期绑定’,什么意思?即你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能
class FtpClient: 'ftp客户端,但是还么有实现具体的功能' def __init__(self,addr): print('正在连接服务器[%s]' %addr) self.addr=addr
#from module import FtpClient f1=FtpClient('192.168.1.1') if hasattr(f1,'get'): func_get=getattr(f1,'get') func_get() else: print('---->不存在此方法') print('处理其他的逻辑') 不影响alex的代码编写
三个参数,给对象添加属性
这是python解释器底层的内置方法。当然我们也可以对它们进行一些操作
__setattr__ 添加/修改属性会触发它的执行
__delattr__ 删除属性的时候会触发
__getattr__ 只有在使用点调用属性且属性不存在的时候才会触发
作用:系统内置函数属性(你定义了就用你定义的函数属性,不定义就用系统默认的函数属性)
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__只有在使用点调用属性且属性不存在的时候才会触发 f1.xxxxxx
class Foo: x=1 def __init__(self,y): self.y=y def __getattr__(self, item): # __getattr__常用,且需要记忆 print('执行__getattr__') f1=Foo(10) print(f1.y) #没有的时候就会触发: __getattr__ print(getattr(f1,'y')) #len(str)---->str.__len__() f1.ssssssssssssssssssssssssssssss 10 10 执行__getattr__
动态导入模块
1、新建一个t.py的文件
print('---------------->') def test1(): print('test1') def _test2(): print('test2')
2、再创建:m1文件夹,再在他下面创建一个t.py
module_t=__import__('m1.t') print(module_t) module_t.t.test1() # from m1.t import * # from m1.t import test,_test2 import importlib m=importlib.import_module('m1.t') print(m) m.test1() m._test2()
----------------> <module 'm1' (namespace)> test1 <module 'm1.t' from 'D:\\python\\day26\\m1\\t.py'> test1 test2
二次加工标准类型(包装)
包装:python为大家提供了标准数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增/改写方法,这就用到了我们刚学的继承/派生知识(其他的标准类型均可以通过下面的方式进行二次加工)
1 # 二次加工标准类型 2 class List(list): 3 def append(self,p_object): 4 if type(p_object) is str: 5 super().append(p_object) 6 else: 7 print("添加的类型必须为字符串类型") 8 9 l = List(["hello","world"]) 10 l.append(1) 11 print(l) 12 13 class Me(list): 14 def remove(self,value): 15 print("什么都不能删除") 16 17 m = Me([1,2,3,4]) 18 m.remove(2)
授权
授权是包装的一个特性, 包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建,修改或删除原有产品的功能。其它的则保持原样。授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。
实现授权的关键点就是覆盖__getattr__方法
class Filehandle: def __init__(self,filename,mode="r",encoding="utf-8"): self.file = open(filename,mode,encoding=encoding) self.mode = mode self.encoding = encoding def __getattr__(self, item): print(item,type(item)) # read <class 'str'> self.file.read() # self.file 里面有read方法 return self.file,item f1=Filehandle('a.txt','r') # print(f1.file) # <_io.TextIOWrapper name='a.txt' mode='r' encoding='utf-8'> print(f1.__dict__) # 对象的属性字典中没有read方法,触发__getattr__方法 #{'file': <_io.TextIOWrapper name='a.txt' mode='r' encoding='utf-8'>, 'encoding': 'utf-8', 'mode': 'r'} print(f1.read) # 从而找到read方法 sys_F = Filehandle("b.txt","w+") # 从Filehandle中创建实例 print('---->',getattr(sys_F,'read')) # ----> (<_io.TextIOWrapper name='b.txt' mode='w+' encoding='utf-8'>, 'read')
import time class Filehandle: def __init__(self,filename,mode="r",encoding="utf-8"): self.file = open(filename,mode,encoding=encoding) self.mode = mode self.encoding = encoding def write(self,line): print("--------> ",line) # 输出打印的内容 t = time.time() t = time.strftime('%Y-%m-%d %X') self.file.write("%s %s" %(t,line)) # self.file 也有写的方法。 def __getattr__(self, item): # print(item,type(item)) # read <class 'str'> # self.file.read() # self.file 里面有read方法 return self.file,item f1=Filehandle('a.txt','r') # print(f1.file) # <_io.TextIOWrapper name='a.txt' mode='r' encoding='utf-8'> print(f1.__dict__) # 对象的属性字典中没有read方法,触发__getattr__方法 #{'file': <_io.TextIOWrapper name='a.txt' mode='r' encoding='utf-8'>, 'encoding': 'utf-8', 'mode': 'r'} print(f1.read) # 从而找到read方法 sys_F = Filehandle("b.txt","w+") # 从Filehandle中创建实例 # print('---->',getattr(sys_F,'read')) # ----> (<_io.TextIOWrapper name='b.txt' mode='w+' encoding='utf-8'>, 'read') sys_F.write("路飞\n") sys_F.write("娜美\n")
类的特殊成员
class Foo: def __init__(self): print("我是init") def __call__(self, *args, **kwargs): print("我是call") return 1 # r = Foo() 类的实例化,执行init方法 # r() 对象加括号什么鬼? 其实是调用了__call__ print(Foo()()) 我是init 我是call 1
class Foo: def __init__(self): pass def __call__(self, *args, **kwargs): pass def __getitem__(self, item): print(item) def __setitem__(self, key, value): print(key,value) def __delitem__(self, key): print(key) r = Foo() #类的实例化,执行init方法 r() #对象加括号什么鬼? 其实是调用了__call__ # print(Foo()()) r["getitem"] # __getitem__ r["gs"] = 123 # __setitem__ del r["gs"] # __delitem__ getitem gs 123 gs
对三者的补充,再通过对列表索引切片的时候,查看,赋值,删除也是对应执行它们。
通过字典的操作的方式是调用它们,而通过点的方式去取值是调用attr这些
#例子2 class Bar: def __init__(self): pass def __getitem__(self, item): print("__getitem__",item) def __setitem__(self, key, value): print("__setitem__") self.__dict__[key] = value def __delitem__(self, key): print("__delitem__") self.__dict__.pop(key) b = Bar() b["name"] = "alex" print(b.__dict__) del b["name"] print(b.__dict__) print(b["name"]) __setitem__ {'name': 'alex'} __delitem__ {} __getitem__ name None
获取类或者对象里面的所有字段
例子未补充
class C: def __init__(self,n): self.n = n def __iter__(self): return self def __next__(self): if self.n == 13: raise StopIteration self.n += 1 return self.n c = C(10) for i in c: # for循环的强大机制,捕捉到StopIteration会结束 c.__iter__() print(i) # next(i) ----> i.__next__()
class Onepiece: def __init__(self,name): self.name = name class Sea(Onepiece): def __init__(self): pass s = Sea() o = Onepiece("路飞") print(isinstance(o,Onepiece)) # 判断对象是不是该类下的 print(isinstance(s,Onepiece)) # 如果存在继承关系,则也是父类的对象 print(issubclass(Sea,Onepiece)) # 判断是否为子类 print(issubclass(Onepiece,Sea))
class Foo: x = 1 def __init__(self,y): self.y = y def __getattr__(self, item): print("执行的是getattr",item) def __getattribute__(self, item): print("--->执行的是__getattribute__",item) raise AttributeError # 抛出一个异常交给__getattr__ # 上面都是自己定义我们想要的的机制过程,如果不写,系统也有一套系统默认的这种机制。 f = Foo(10) f.xx --->执行的是__getattribute__ xx # 是大哥 执行的是getattr xx # 是小弟
属性有没有都会触发__getattribute__
1 class Fun: 2 def __init__(self,name,age): 3 self.name = name 4 self.age = age 5 6 # def __str__(self): 7 # return "%s %s " %(self.name,self.age) 8 def __repr__(self): 9 return "--->%s %s " %(self.name,self.age) 10 11 me = Fun("Tom",18) 12 print(me) # 输出<__main__.Fun object at 0x000001AC455E7400> 13 # 如果加上__str__ 14 print(me) # Tom 18 15 16 # 如果__str__和__repr__都存在 17 print(me) # str(me) >>> me.__str__() >>> me.__repr__() 顺序 18 # 如果__str__没有被定义,那么就会使用__repr__来代替输出 19 # 注意:这俩方法的返回值必须是字符串,否则抛出异常
class Pinjie: def __init__(self,day,mouth,year): self.day = day self.mouth = mouth self.year = year def __format__(self, format_spec): return "{}-{}-{}" .format(self.mouth,self.day,self.year) time = Pinjie(26,12,2017) print(format(time)) # format()就是在执行__format__ # 也可以通过字典的方式进行指定格式。在这里写的比较硬
class A: # 定义__slots__之后 就没有了实例的属性字典 __slots__ = "x" a = A() print(A.__dict__) a.x = 2 a.y = 3 #报错 # __slots__ 称之为类字典,定义类变量之后,就没有了实例的属性字典,访问会报错 # 而且所有产生的实例都只有类变量下的东西,且不能再进行添加 # 好处:节省内存 弊端:失去扩展性
输出文档注释信息,而且注释信息不能被继承
1 # __moudle__ 获取当前导入的模块名 2 3 # __class__ 获取类名
class B: def __init__(self,name): self.name = name def __del__(self): # 析构方法,当结束运行,会执行析构方法。 print("执行了!") b = B("tom") del b print("---------->") #析构方法,当对象在内存中被释放时,自动触发执行。 #注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。
#1、操作文件写法 with open('a.txt') as f: '代码块' #2、上述叫做上下文管理协议,即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中代码块执行完毕时执行我啊 #3、执行代码块 __exit__()中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法执行 没有异常的情况下,整个代码块运行完毕后去触发__exit__,它的三个参数都会执行 class Foo: def __init__(self,name): self.name=name def __enter__(self): print('执行enter') return self def __exit__(self, exc_type, exc_val, exc_tb): print('执行exit') print(exc_type) print(exc_val) print(exc_tb) with Foo('a.txt') as f: print(f) #<__main__.Foo object at 0x012E8A90> print(assfsfdsfd) print(f.name) print('00000000') 输出 执行enter Traceback (most recent call last): <__main__.Foo object at 0x01478A90> File "D:/python/day28/s1.py", line 56, in <module> 执行exit print(assfsfd) #触发__exit__ <class 'NameError'> NameError: name 'assfsfd' is not defined name 'assfsfd' is not defined <traceback object at 0x01484710> 如果__exit()返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行 #没有return True就会报错,如果有return True异常自己吃了,不报异常 #用途: #1.使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预 #2.在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处
未完待续……