面向对象高阶--方法论(第二部分)
方法论
绑定方法 & 非绑定方法
在类中定义的函数,总共有两种类型:
- 绑定方法
- 非绑定方法
绑定方法(绑定给谁,谁来调用就自动将它本身当作第一个参数传入):
1.绑定到类的方法:用classmethod装饰器装饰的方法
为类量身定制
类.boud_method(),自动将类当作第一个参数传入
(其实对象也可调用,但仍将类当作第一个参数传入)
2.绑定到对象的方法:没有被任何装饰器装饰的方法
为对象量身定制
对象.boud_method(),自动将对象当作第一个参数传入
(属于类的函数,类可以调用,但是必须按照函数的规则来,没有自动传值那么一说)
非绑定方法:用staticmethod装饰器装饰的方法
不与类或对象绑定,类和对象都可以调用,但是没有自动传值功能,仅仅是个函数工具
绑定方法详解:
绑定给类的方法(classmethod)
classmethod是给类用的,绑定到类,类在使用时会将类本身当做参数传给类方法的第一个参数(对象调用也一样)
class Foo: def __init__(self,name): self.name = name @classmethod def tell(self,name): print('My name is %s '% self.name) print('未装饰前:',Foo.tell) # 未装饰前: <function Foo.tell at 0x000001C3A1422A60> print('装饰后:',Foo.tell) # 装饰后: <bound method Foo.tell of <class '__main__.Foo'>> # 类调用 Foo.tell() # <class '__main__.Foo'> # 对象调用 f =Foo('Jack') f.tell() # <class '__main__.Foo'>
非绑定方法
在类内部用statimethod装饰器装饰的函数即为非绑定方法,就是普通函数
statimethod 不与类或对象绑定,但是两者均可调用,没有自动传值效果
import hashlib,time class MySQL: def __init__(self,host,port): self.host = host self.port = port self.id = self.creat_id() @staticmethod def creat_id(): # 此时为函数工具 m = hashlib.md5(str(time.time()).encode('utf-8')) return m.hexdigest() print(MySQL.creat_id) # <function MySQL.creat_id at 0x000001AB908C2D90> m = MySQL('127.01.11.01',6868) print(m.creat_id) # <function MySQL.creat_id at 0x000001AB908C2D90> # 查看结果均为普通函数
适用场景
import settings import hashlib,time class People: def __init__(self,name,age,sex): self.id =self.creat_id() self.name =name self.age =age self.sex =sex def tell_info(self): # 绑定到对象的方法 print('Name:%s Age:%s Sex:%s'%(self.name,self.age,self.sex)) @classmethod # 绑定到类的方法 def from_conf(cls): obj = cls( settings.name, settings.age, settings.sex ) return obj @staticmethod # 非绑定方法 def creat_id(): m = hashlib.md5(str(time.time()).encode('utf-8')) return m.hexdigest() p =People('林采儿',18,'gril') # 绑定给对象,就应该由对象调用,并自动将对象作为第一个参数传入 p.tell_info() # Name:林采儿 Age:18 Sex:gril p = People.from_conf() # from_conf(People) # 绑定给类,就应该由类调用,并自动将类作为第一个参数传入 p.tell_info() # Name:林采儿 Age:18 Sex:girl # 非绑定方法 类 和对象均可调用,但没有自动传值 p = People('林采儿',18,'gril') print(p.id) # 17078cec4cc907867984221bdcb6dfcf
内置方法
一 isinstance(obj,cls)和issubclass(sub,super)
isinstance(obj,cls):检查obj是否是类 cls 的对象
class Foo: pass obj = Foo() print(isinstance(obj,Foo)) # True
issubclass(sub, super): 检查sub类是否是 super 类的派生类
class Foo: pass class Bar(Foo): pass print(issubclass(Bar,Foo)) # True
二.反射
1.什么是反射
反射的概念是由Smith在1982年首次提出的,主要是程序能够访问、检测和修改它本身状态或行为的一种能力(自省)。
2.python面向 对象中的反射:通过字符串的形式操作对象相关的属性。python内一切皆对象(都可以使用反射)
python中 共有四种可以实现自省函数,以下方法适用于类的对象
- hasattr(object,name):判断object里是否有属性name
- getattr(object,name,default=None):获取object里的属性name的内容
- setattr(object,name,arge):设置object里的属性:name = arge
- delattr(object,name):删除object里的属性name
方法演示:
class People: country = 'china' def __init__(self,name,age): self.name =name self.age = age def tell_info(self): print('Name:%s Age:%s'%(self.name,self.age)) p =People('萧薰儿',18) # 检测属性 查看p.__dict__里的属性是否有name print(hasattr(p,'name')) # True print(hasattr(p,'tell_info')) # True print(hasattr(People,'country')) # True # 获取属性 print(getattr(p,'name')) # 萧薰儿 print(getattr(p,'tell_info')) # <bound method People.tell_info of <__main__.People object at 0x000001411BF7FC88>> # 设置属性 setattr(p,'name','legend') print(p.name) # legend # 删除属性 delattr(p,'name') print(p.__dict__) #{'age': 18}
三. item:将对象变成字典的形式,可以进行字典式的操作
class People: country = 'china' def __init__(self,name,age): self.name =name self.age = age def __getitem__(self, item): # item= ’name‘ print('get',item) return self.__dict__.get(item) # 通过对象属性空间获取对应值 def __setitem__(self, key, value): print('set',key,value) self.__dict__[key] = value def __delitem__(self, key): print('del',key) del self.__dict__[key] # 查看属性 p = People('萧薰儿',18) print(p['name']) # # 执行此句时 会触发方法__getitem__: p.name # 获取属性 p['age'] = 19 # # 执行此句时 会触发方法__setitem__ : p.age =19 print(p.__dict__) # {'name': '萧薰儿', 'age': 19} # 删除属性 del p['age'] # 执行此句时 会触发方法__delitem__ : del p.age print(p.__dict__) # {'name': '萧薰儿'}
四.__str__ 、__repr__、__format__:
改变对象的字符串显示__str__,__repr__
自定制格式化字符串__format__
format_dict = { 'nat':'{obj.name}-{obj.addr}-{obj.type}', 'tna':'{obj.type}:{obj.name}:{obj.addr}', 'tan':'{obj.type}/{obj.addr}/{obj.name}' } class School: def __init__(self,name,addr,type): self.name =name self.addr =addr self.type =type def __str__(self): return '<%s,%s>'%(self.name,self.addr) def __repr__(self): return 'shcool(%s,%s)'%(self.name,self.addr) def __format__(self, format_spec): if not format_spec or format_spec not in format_dict: # 当format_spec为空或者不在format_dict里时 format_spec ='nat' # 将格式设为nat fmt =format_dict[format_spec] print(fmt) return fmt.format(obj=self) s1 = School('蓝翔','山东','私立') # print('from rapr:',repr(s1)) # 触发方法__repr__: from rapr: shcool(蓝翔,山东) # print('from str:',str(s1)) # 触发方法__str__: from str: <蓝翔,山东> # print(s1) # 打印对象 触发方法__str__: <蓝翔,山东> ''' str函数或者print函数 --> obj.__str__() repr函数或交互解释器 --> obj.__str__() 如果str方法没有定义,那么就会使用__repr__方法来代替 注意:这两个方法的返回值必须会字符串,否则会报错(TypeError: __str__ returned non-string (type NoneType)) ''' print(format(s1,'nat')) print(format(s1,'tan')) print(format(s1,'tna')) print(format(s1,'sdfsfd')) print(format(s1)) 输出: {obj.name}-{obj.addr}-{obj.type} 蓝翔-山东-私立 {obj.type}/{obj.addr}/{obj.name} 私立/山东/蓝翔 {obj.type}:{obj.name}:{obj.addr} 私立:蓝翔:山东 {obj.name}-{obj.addr}-{obj.type} 蓝翔-山东-私立 {obj.name}-{obj.addr}-{obj.type} 蓝翔-山东-私立
五.__next__和__iter__实现迭代器协议
示例:
class Foo: def __init__(self,x): self.x =x def __iter__(self): return self def __next__(self): n =self.x self.x +=1 return self.x f= Foo(3) for i in f: print(i)
加上范围
class Foo: def __init__(self,start,stop): self.num =start self.stop =stop def __iter__(self): return self def __next__(self): if self.num>self.stop: raise StopIteration n =self.num self.num +=1 return n f= Foo(1,6) from collections import Iterable,Iterator print(isinstance(f,Iterator)) # 判断f是否为迭代器 :True for i in f: print(i) 输出: 1 2 3 4 5 6
模仿 range 方法
class Range: def __init__(self,x): self.x =x self.min =0 # 设定最小值为0 self.max = self.x-1 # 设置最大值为x-1 def __iter__(self): return self def __next__(self): if self.min >self.max: raise StopIteration n =self.min self.min +=1 return n for i in Range(5): print(i) 输出: 0 1 2 3 4
斐波那契数列
class Foo: def __init__(self,x): self.x =x self.a =0 self.b=1 def __iter__(self): return self def __next__(self): if self.a > self.x: raise StopIteration self.b =self.a+self.b if self.a ==0: self.a =2 else: self.a += 1 return self.b f = Foo(5) for i in f: print(i) 输出: 1 3 6 10 15
六.__doc__:类的描述信息
class Foo: ''' 描述信息 ''' pass print(Foo.__doc__) # 描述信息
该属性无法被继承
class Foo: ''' 描述信息 ''' pass class Bar(Foo): pass print(Foo.__doc__) # 描述信息 print(Bar.__doc__) # None
七.__del__
析构方法,当对象在内存中被释放时,自动触发执行。
注:如果产生的对象仅仅只是python程序级别的(用户级),那么无需定义__del__,如果产生的对象的同时还会向操作系统发起系统调用,即一个对象有用户级与内核级两种资源,比如(打开一个文件,创建一个数据库链接),则必须在清除对象的同时回收系统资源,这就用到了__del__。
示例:
class Foo: def __del__(self): print('执行此处') f1=Foo() del f1 print('------->') #输出执行此处 ------->
典型的应用场景:
创建数据库类,用该类实例化出数据库链接对象,对象本身是存放于用户空间内存中,而链接则是由操作系统管理的,存放于内核空间内存中
当程序结束时,python只会回收自己的内存空间,即用户态内存,而操作系统的资源则没有被回收,这就需要我们定制__del__,在对象被删除前向操作系统发起关闭数据库链接的系统调用,回收资源
这与文件处理是一个道理:
f=open('info.txt') #做了两件事,在用户空间拿到一个f变量,在操作系统内核空间打开一个文件 del f #只回收用户空间的f,操作系统的文件还处于打开状态 #所以我们应该在del f之前保证f.close()执行,即便是没有del,程序执行完毕也会自动del清理资源,于是文件操作的正确用法应该是 f=open('a.txt') 读写... f.close() 很多情况下大家都容易忽略f.close,这就用到了with上下文管理
八.__enter__和__exit__
在学习文件操作时,除了常规的打开方式外,还有一种方式:
With open('test.txt') as f: 代码块
上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法
上下文管理协议
class Open: def __init__(self,name): self.name =name def __enter__(self): print('出现with语句,对象的方法__enter__就会被触发,有返回值的话就把返回值赋予变量f') def __exit__(self, exc_type, exc_val, exc_tb): print('with 语句代码块执行完毕后,执行此处!') with open('test.txt') as f: print('执行with语句代码块') print(f.name) # test.txt print(f) # <_io.TextIOWrapper name='test.txt' mode='r' encoding='cp936'>
方法__enter__中的三个参数分别代表异常类型、异常值和追溯信息。with语句的代码块出现问题,则with后的代码都无法执行
class Open: def __init__(self,name): self.name =name def __enter__(self): print('出现with语句,对象的就会被触发,有返回值的话就把返回值赋予变量f') def __exit__(self, exc_type, exc_val, exc_tb): print('with 语句代码块执行完毕后,执行此处!') print(exc_type) print(exc_val) print(exc_tb) with open('test.txt') as f: print('执行with语句代码块') # print(f.name) # test.txt # print(f) # <_io.TextIOWrapper name='test.txt' mode='r' encoding='cp936'> raise AttributeError('啦啦') # 触发异常,则代码终止 print('after with') 输出: 执行with语句代码块 Traceback (most recent call last): AttributeError: 啦啦
如果__exit__返回值为True,那么异常将会清空,with之后 的代码继续执行
class Open: def __init__(self,name): self.name =name def __enter__(self): print('出现with语句,对象的就会被触发,有返回值的话就把返回值赋予变量f') def __exit__(self, exc_type, exc_val, exc_tb): print('with 语句代码块执行完毕后,执行此处!') print(exc_type) print(exc_val) print(exc_tb) return True # 返回值为True 异常清空继续执行后面代码 with open('test.txt') as f: print('执行with语句代码块')> raise AttributeError('啦啦') # 触发异常,则代码终止 print('after with')
模拟Open
class Open: def __init__(self, filepath, mode='r', encoding='utf-8'): self.filepath = filepath self.mode = mode self.encoding = encoding def __enter__(self): self.f=open(self.filepath,mode=self.mode,encoding=self.encoding) return self.f def __exit__(self, exc_type, exc_val, exc_tb): # 处理异常 self.f.close() return True def __getattr__(self, item): return getattr(self.f,item) with Open('test2.txt','w') as f: f.write('hello') f.age # 抛出异常,交给__exit__处理 print('after with')
九.__module__和__class__
__module__ 表示当前操作的对象在那个模块
__class__ 表示当前操作的对象的类是什么
setting.py
class Test: def __init__(self): pass
导入setting,py里的类Test
from sht.settings import Test obj = Test() print(obj.__class__) # 当前操作的类 print(obj.__module__) # 当前操作的模块 输出: <class 'sht.settings.Test'> sht.settings
十.__call__
方法call仅当对象后面加括号,才会被触发执行
注:构造方法的执行是由创建对象触发的,即:对象=类(),而__call__ 方法是由对象后面加括号触发的。即:对象()或 类()
class Test: def __init__(self,name,age): self.name = name self.age =age def __call__(self, *args, **kwargs): print(self) print(args) print(kwargs) t =Test('Jack',18) # 触发__init__方法 t() # 触发__call__方法 输出: # 由于调用t时未传值,arge,kwargs 所以两者均为空 <__main__.Test object at 0x000001A9538DFBE0> () {}
元类
一.相关知识
exec 三个参数 参数一:字符串形式的命令 参数二:全局作用域(字典形式),如果不指定,默认为globals() 参数三:局部作用域(字典形式),如果不指定,默认为locals()
exec的使用
可一把exec命令的执行当做一个函数的执行,会将执行期间产生的名字放在局部名车空间
g ={ 'x':1, 'y':2, } l ={} exec(''' global x,m # 声明调用全局变量 x= 6 # 修改 m =8 # 新建 s =666 ''',g,l) print(g) # {'x': 6, 'y': 2 ,'s':666} print(l) # {'s': 666}
python中一切皆对象,类本身也是一个对象 ,当使用关键字class的时候,python解释器在加载class的时候就会创建一个对象(这里的对象值得是类,而非类的实例)。因而我们可以把类当做一个对象去使用同样满足第一类对象的概念:
- 可以把类赋值给一个变量
- 可以把类作为函数参数进行传入
- 可以把类当做返回值
- 可以运行时动态创建参数
class Foo: def __init__(self): pass f= Foo() # f 为F实例化对象 即将类Foo赋值给变量f
从代码可以看出f 为F实例化对象,而类Foo本身也是 对象,那类Foo是由哪个类产生的呢?
# 可以用函数type来查看类型,也可以查看类和对象 print(type(Foo)) # <class 'type'> print(type(f)) # <class '__main__.Foo'>
二.什么是元类
元类是类的模板
元类是用来控制如何创建类的,正如类创建对象一样。而元类的主要目的是用来控制类的创建行为
元类的实例化结果为我们用class定义的类,正如类的 实例为对象(f为类Foo的对象,Foo为元类type的对象)
type是python中内置元类,用来直接控制生成类,而python中任何class定义的类都是由type类实例化的对象
三.创建类的两种方式
1.使用class关键字创建
class Chinese(object): country='China' def __init__(self,name,age): self.name=name self.age=age def talk(self): print('%s is talking' %self.name)
2.手动模拟class创建类的过程:将创建类的步骤拆分开,手动去创建
#准备工作: #创建类主要分为三部分 1 类名 2 类的父类 3 类体 #类名 class_name='Chinese' #类的父类 class_bases=(object,) #类体 class_body=""" country='China' def __init__(self,name,age): self.name=name self.age=age def talk(self): print('%s is talking' %self.name)
步骤一(先处理类体-->名称 空间):
类体创建的名字会存放于类的名称空间 中(一个局部作用域)。我们可以实现定义一个空字典,然后用exec去执行类体的代码(exec产生名称空间的过程和class类似,只是后者会变成以__开头的属性),生成类的名称空间,填充字典
class_dict ={} exec(class_body,globals(),class_dict) print(class_dict) 输出: {'country': 'China', '__init__': <function __init__ at 0x00000236978E29D8>, 'talk': <function talk at 0x00000236978E2A60>}
步骤二:调用元类type(也可自定义),来产生类Chinese
Foo = type(class_name,class_bases,class_dict) # 实例化type得到对象Foo print(Foo) print(type(Foo)) print(isinstance(Foo,type)) 输出: <class '__main__.Chinese'> <class 'type'> True
我们看到,type 接收三个参数:
- 第 1 个参数是字符串 ‘Foo’,表示类名
- 第 2 个参数是元组 (object, ),表示所有的父类
- 第 3 个参数是字典,这里是一个空字典,表示没有定义属性和方法
补充:若Foo类有继承,即class Foo(Bar):.... 则等同于type('Foo',(Bar,),{})
四.自定义元类控制类的行为
一个类没有声明自己的元类,那么就默认它的元类是type。除了使用元类type,用户也可通过继承type来自定义元类,通过以下步骤来看下实现过程
#知识储备: #产生的新对象 = object.__new__(继承object类的子类) #:如果说People=type(类名,类的父类们,类的名称空间),那么我们定义元类如下,来控制类的创建 class Mymeta(type): # 继承默认元类的一堆属性 def __init__(self,class_name,class_bases,class_dict): if '__doc__' not in class_dict or not class_dict.get('__doc__').strip(): raise TypeError('必须为类写文档注释') if not class_name.istitle(): raise TypeError('首字母必须大写') super().__init__(class_name,class_bases,class_dict) def __call__(self, *args, **kwargs): # print(self) # print(args) # print(kwargs) # 1.调用__new__方法新建对象obj obj =object.__new__(self) # 2.初始化obj self.__init__(self,*args, **kwargs) # 3.返回obj return obj class Peopel(object,metaclass=Mymeta): country = 'china' def __init__(self,name,age): self.name =name self.age =age def talk(self): print('%s is talking'%self.name) p =Peopel('Jack',18)
#步骤二:如果我们想控制类实例化的行为,那么需要先储备知识__call__方法的使用 class People(object,metaclass=type): def __init__(self,name,age): self.name=name self.age=age def __call__(self, *args, **kwargs): print(self,args,kwargs) # 调用类People,并不会出发__call__ obj=People('egon',18) obj(1,2,3,a=1,b=2,c=3) 输出: <__main__.People object at 0x000001DE5631FBE0> (1, 2, 3) {'a': 1, 'b': 2, 'c': 3} #总结:如果说类People是元类type的实例,那么在元类type内肯定也有一个__call__,会在调用People('egon',18)时触发执行,然后返回一个初始化好了的对象obj
#步骤三:自定义元类,控制类的调用(即实例化)的过程 class Mymeta(type): #继承默认元类的一堆属性 def __init__(self,class_name,class_bases,class_dic): if not class_name.istitle(): raise TypeError('类名首字母必须大写') super(Mymeta,self).__init__(class_name,class_bases,class_dic) def __call__(self, *args, **kwargs): #self=People print(self,args,kwargs) #<class '__main__.People'> ('egon', 18) {} #1、实例化People,产生空对象obj obj=object.__new__(self) #2、调用People下的函数__init__,初始化obj self.__init__(obj,*args,**kwargs) #3、返回初始化好了的obj return obj class People(object,metaclass=Mymeta): country='China' def __init__(self,name,age): self.name=name self.age=age def talk(self): print('%s is talking' %self.name) obj=People('egon',18) print(obj.__dict__) #{'name': 'egon', 'age': 18}
class Mymeta(type): #继承默认元类的一堆属性 def __init__(self,class_name,class_bases,class_dic): if not class_name.istitle(): raise TypeError('类名首字母必须大写') super(Mymeta,self).__init__(class_name,class_bases,class_dic) def __call__(self, *args, **kwargs): #self=People print(self,args,kwargs) #<class '__main__.People'> ('egon', 18) {} #1、调用self,即People下的函数__new__,在该函数内完成:1、产生空对象obj 2、初始化 3、返回obj obj=self.__new__(self,*args,**kwargs) #2、一定记得返回obj,因为实例化People(...)取得就是__call__的返回值 return obj class People(object,metaclass=Mymeta): country='China' def __init__(self,name,age): self.name=name self.age=age def talk(self): print('%s is talking' %self.name) def __new__(cls, *args, **kwargs): obj=object.__new__(cls) cls.__init__(obj,*args,**kwargs) return obj obj=People('egon',18) print(obj.__dict__) #{'name': 'egon', 'age': 18}
#基于元类实现单例模式,比如数据库对象,实例化时参数都一样,就没必要重复产生对象,浪费内存 class Mysql: __instance=None def __init__(self,host='127.0.0.1',port='3306'): self.host=host self.port=port @classmethod def singleton(cls,*args,**kwargs): if not cls.__instance: cls.__instance=cls(*args,**kwargs) return cls.__instance obj1=Mysql() obj2=Mysql() print(obj1 is obj2) #False obj3=Mysql.singleton() obj4=Mysql.singleton() print(obj3 is obj4) #True
#应用:定制元类实现单例模式 class Mymeta(type): def __init__(self,name,bases,dic): #定义类Mysql时就触发 self.__instance=None super().__init__(name,bases,dic) def __call__(self, *args, **kwargs): #Mysql(...)时触发 if not self.__instance: self.__instance=object.__new__(self) #产生对象 self.__init__(self.__instance,*args,**kwargs) #初始化对象 #上述两步可以合成下面一步 # self.__instance=super().__call__(*args,**kwargs) return self.__instance class Mysql(metaclass=Mymeta): def __init__(self,host='127.0.0.1',port='3306'): self.host=host self.port=port obj1=Mysql() obj2=Mysql() print(obj1 is obj2)