一、接口与归一化设计
1、什么是接口
1 =================第一部分:Java 语言中的接口很好的展现了接口的含义: IAnimal.java 2 /* 3 * Java的Interface接口的特征: 4 * 1)是一组功能的集合,而不是一个功能 5 * 2)接口的功能用于交互,所有的功能都是public,即别的对象可操作 6 * 3)接口只定义函数,但不涉及函数实现 7 * 4)这些功能是相关的,都是动物相关的功能,但光合作用就不适宜放到IAnimal里面了 */ 8 9 package com.oo.demo; 10 public interface IAnimal { 11 public void eat(); 12 public void run(); 13 public void sleep(); 14 public void speak(); 15 } 16 17 =================第二部分:Pig.java:猪”的类设计,实现了IAnnimal接口 18 package com.oo.demo; 19 public class Pig implements IAnimal{ //如下每个函数都需要详细实现 20 public void eat(){ 21 System.out.println("Pig like to eat grass"); 22 } 23 24 public void run(){ 25 System.out.println("Pig run: front legs, back legs"); 26 } 27 28 public void sleep(){ 29 System.out.println("Pig sleep 16 hours every day"); 30 } 31 32 public void speak(){ 33 System.out.println("Pig can not speak"); } 34 } 35 36 =================第三部分:Person2.java 37 /* 38 *实现了IAnimal的“人”,有几点说明一下: 39 * 1)同样都实现了IAnimal的接口,但“人”和“猪”的实现不一样,为了避免太多代码导致影响阅读,这里的代码简化成一行,但输出的内容不一样,实际项目中同一接口的同一功能点,不同的类实现完全不一样 40 * 2)这里同样是“人”这个类,但和前面介绍类时给的类“Person”完全不一样,这是因为同样的逻辑概念,在不同的应用场景下,具备的属性和功能是完全不一样的 */ 41 42 package com.oo.demo; 43 public class Person2 implements IAnimal { 44 public void eat(){ 45 System.out.println("Person like to eat meat"); 46 } 47 48 public void run(){ 49 System.out.println("Person run: left leg, right leg"); 50 } 51 52 public void sleep(){ 53 System.out.println("Person sleep 8 hours every dat"); 54 } 55 56 public void speak(){ 57 System.out.println("Hellow world, I am a person"); 58 } 59 } 60 61 =================第四部分:Tester03.java 62 package com.oo.demo; 63 64 public class Tester03 { 65 public static void main(String[] args) { 66 System.out.println("===This is a person==="); 67 IAnimal person = new Person2(); 68 person.eat(); 69 person.run(); 70 person.sleep(); 71 person.speak(); 72 73 System.out.println(" ===This is a pig==="); 74 IAnimal pig = new Pig(); 75 pig.eat(); 76 pig.run(); 77 pig.sleep(); 78 pig.speak(); 79 } 80 } 81 82 java中的interface
ps:此时的接口指的是:提供给使用者来调用自己功能的方式方法入口
2、为何要用接口
接口提取了一群类共同的函数,可以把接口当做一个函数的集合。
然后让子类去实现接口中的函数。
这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。
归一化的好处在于:
1、归一化让使用者无需关心对象的类是什么,只需要知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
2、归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合
2.1、就好像linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和块设备,然后做出针对性设计:细致到什么程度,视需求而定。)
2.2、再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车类,大众汽车类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样。
3、模仿interface
在python中根本就没有一个叫做interface的关键字,如果非要去模仿接口的概念
可以借助第三方模块:
http://pypi.python.org/pypi/zope.infterface
twisted的twistedintemetinterface.py里使用zope.interface
文档https://zopeinterface.readthedocs.io/en/latest
设计模式:https://github.com/faif/python-patterns
也可以使用继承:
继承的两种用途
一、继承基类的方法,并且做出自己的改变或者扩展(代码重用):实践中,继承的这种用途意义并不很大,甚至常常是有害的,因为它使得子类与基类出现强耦合。
二、声明某个子类兼容于某基类,定义一个接口类(模仿java的interface),接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能。
1 class Interface:#定义接口Interface类来模仿接口的概念,python中压根就没有interface关键字来定义一个接口。 2 def read(self): #定接口函数read 3 pass 4 5 def write(self): #定义接口函数write 6 pass 7 8 9 class Txt(Interface): #文本,具体实现read和write 10 def read(self): 11 print('文本数据的读取方法') 12 13 def write(self): 14 print('文本数据的读取方法') 15 16 class Sata(Interface): #磁盘,具体实现read和write 17 def read(self): 18 print('硬盘数据的读取方法') 19 20 def write(self): 21 print('硬盘数据的读取方法') 22 23 class Process(Interface): 24 def read(self): 25 print('进程数据的读取方法') 26 27 def write(self): 28 print('进程数据的读取方法')
上面的代码只是看起来像接口,其实并没有起到接口的作用,子类完全可以不用去实现接口,这就用到了抽象类。
二、抽象类
1、什么是抽象类
与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在与只能被继承,不能被实例化。
2、为什么要有抽象类
如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆中抽取相同的内容而来的,内容包括数据属性和函数属性。
比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子......永远无法吃到一个叫做水果的东西。
从设计角度去看,如果类是从实现对象抽象而来的,那么抽象类就是基于类抽象而来的。
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法,这一点与接口有点类似,但其实是不同的,即将揭晓答案。
3、在python中实现抽象类
1 #_*_coding:utf-8_*_ 2 __author__ = 'Linhaifeng' 3 #一切皆文件 4 import abc #利用abc模块实现抽象类 5 6 class All_file(metaclass=abc.ABCMeta): 7 all_type='file' 8 @abc.abstractmethod #定义抽象方法,无需实现功能 9 def read(self): 10 '子类必须定义读功能' 11 pass 12 13 @abc.abstractmethod #定义抽象方法,无需实现功能 14 def write(self): 15 '子类必须定义写功能' 16 pass 17 18 # class Txt(All_file): 19 # pass 20 # 21 # t1=Txt() #报错,子类没有定义抽象方法 22 23 class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法 24 def read(self): 25 print('文本数据的读取方法') 26 27 def write(self): 28 print('文本数据的读取方法') 29 30 class Sata(All_file): #子类继承抽象类,但是必须定义read和write方法 31 def read(self): 32 print('硬盘数据的读取方法') 33 34 def write(self): 35 print('硬盘数据的读取方法') 36 37 class Process(All_file): #子类继承抽象类,但是必须定义read和write方法 38 def read(self): 39 print('进程数据的读取方法') 40 41 def write(self): 42 print('进程数据的读取方法') 43 44 wenbenwenjian=Txt() 45 46 yingpanwenjian=Sata() 47 48 jinchengwenjian=Process() 49 50 #这样大家都是被归一化了,也就是一切皆文件的思想 51 wenbenwenjian.read() 52 yingpanwenjian.write() 53 jinchengwenjian.read() 54 55 print(wenbenwenjian.all_type) 56 print(yingpanwenjian.all_type) 57 print(jinchengwenjian.all_type)
4、抽象类与接口
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性 的相似性。
抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计。
三、多态
多态指的是一类事物有多种形态
动物有多种形态:人,狗,猪
1 import abc 2 class Animal(metaclass=abc.ABCMeta): #同一类事物:动物 3 @abc.abstractmethod 4 def talk(self): 5 pass 6 7 class People(Animal): #动物的形态之一:人 8 def talk(self): 9 print('say hello') 10 11 class Dog(Animal): #动物的形态之二:狗 12 def talk(self): 13 print('say wangwang') 14 15 class Pig(Animal): #动物的形态之三:猪 16 def talk(self): 17 print('say aoao')
文件有多种形态:文件文本,可执行文件
1 import abc 2 class File(metaclass=abc.ABCMeta): #同一类事物:文件 3 @abc.abstractmethod 4 def click(self): 5 pass 6 7 class Text(File): #文件的形态之一:文本文件 8 def click(self): 9 print('open file') 10 11 class ExeFile(File): #文件的形态之二:可执行文件 12 def click(self): 13 print('execute file')
四、多态性
1、什么是多态动态绑定(在继承的背景下使用时,有时也称为多态性)
多态性是指在不考虑实例类型的情况下使用实例
1 在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息(!!!obj.func():是调用了obj的方法func,又称为向obj发送了一条消息func),不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。 2 3 比如:老师.下课铃响了(),学生.下课铃响了(),老师执行的是下班操作,学生执行的是放学操作,虽然二者消息一样,但是执行的效果不同
多态性分为静态多态性和动态多态性
静态多态性:如任何类型都可以用运算符+进行运算
动态多态性:如下
1 peo=People() 2 dog=Dog() 3 pig=Pig() 4 5 #peo、dog、pig都是动物,只要是动物肯定有talk方法 6 #于是我们可以不用考虑它们三者的具体是什么类型,而直接使用 7 peo.talk() 8 dog.talk() 9 pig.talk() 10 11 #更进一步,我们可以定义一个统一的接口来使用 12 def func(obj): 13 obj.talk()
1 #多态:同一种事物的多种形态 2 class Animal: #同一类事物:动物 3 def talk(self): 4 pass 5 6 class People(Animal): #动物的形态之一:人 7 def talk(self): 8 print('say hello') 9 10 class Dog(Animal): #动物的形态之二:狗 11 def talk(self): 12 print('say wangwang') 13 14 class Pig(Animal): #动物的形态之三:猪 15 def talk(self): 16 print('say aoao') 17 18 class Cat(Animal): 19 def talk(self): 20 print('say miaomiao') 21 22 23 class Bird: 24 def talk(self): 25 print('jijiji') 26 27 #多态性:可以在不考虑实例类型的前提下使用实例 28 p1=People() 29 d=Dog() 30 p2=Pig() 31 c=Cat() 32 b=Bird() 33 34 p1.talk() 35 d.talk() 36 p2.talk() 37 c.talk() 38 b.talk() 39 40 def Talk(animal): 41 animal.talk() #p1.talk() 42 43 Talk(p1) 44 Talk(d) 45 Talk(p2) 46 Talk(c) 47 Talk(b)
2、为什么要用多态性(多态性的好处)
其实大家从上面多态性的例子可以看出,也就是说python本身就是支持多态性的,这么做的好处是什么呢?
a、增加了程序的灵活性
以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)
b、增加了程序可扩展性
通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用
1 >>> class Cat(Animal): #属于动物的另外一种形态:猫 2 ... def talk(self): 3 ... print('say miao') 4 ... 5 >>> def func(animal): #对于使用者来说,自己的代码根本无需改动 6 ... animal.talk() 7 ... 8 >>> cat1=Cat() #实例出一只猫 9 >>> func(cat1) #甚至连调用方式也无需改变,就能调用猫的talk功能 10 say miao 11 12 ''' 13 这样我们新增了一个形态Cat,由Cat类产生的实例cat1,使用者可以在完全不需要修改自己代码的情况下。使用和人、狗、猪一样的方式调用cat1的talk方法,即func(cat1) 14 '''
五、鸭子类型
python崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’,python程序员通常根据这种行为来编写程序,例如,如果想编写现有对象的自定义版本,可以继承该对象也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序逐渐的松耦合度。
例1:利用标准库中定义的各种‘与文件类似’的对象,尽管这些对象的工作方式像文件,但他们没有继承内置文件对象的方法。
1 #二者都像鸭子,二者看起来都像文件,因而就可以当文件一样去用 2 class TxtFile: 3 def read(self): 4 pass 5 6 def write(self): 7 pass 8 9 class DiskFile: 10 def read(self): 11 pass 12 def write(self): 13 pass
例2:序列类型有多态形态:字符串,列表,元组,但他们直接没有直接的继承关系
1 #str,list,tuple都是序列类型 2 s=str('hello') 3 l=list([1,2,3]) 4 t=tuple((4,5,6)) 5 6 #我们可以在不考虑三者类型的前提下使用s,l,t 7 s.__len__() 8 l.__len__() 9 t.__len__() 10 11 len(s) 12 len(l) 13 len(t)
六、面向对象之封装
6.1、引子
从封装本身的意思去理解,封装就好像是拿来一个麻袋,把小猫,小狗一起装进麻袋,然后把麻袋封上口子。照这种逻辑看,封装=‘隐藏’,这种理解是相当片面的。
6.2、先看如何隐藏
在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)
1 #其实这仅仅这是一种变形操作 2 #类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式: 3 4 class A: 5 __N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N 6 def __init__(self): 7 self.__X=10 #变形为self._A__X 8 def __foo(self): #变形为_A__foo 9 print('from A') 10 def bar(self): 11 self.__foo() #只有在类内部才可以通过__foo的形式访问到. 12 13 #A._A__N是可以访问到的,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形
这种自动变形的特点:
1、类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。
2、这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。
3、在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头属性在继承给子类时,子类是无法覆盖的。
这种变形需要注意的问题是:
1、这种机制也没有真正意义上的限制我们从外部直接访问属性,知道了类名和属性名就可以拼出,名字:_类名__属性,然后就可以访问了,如a._A__N
1 print(Foo.__dict__) 2 print(f.__dict__) 3 print(f._Foo__Name) 4 print(f._Foo__N)
2、变形的过程只在类定义是发生一次,在定义后的赋值操作,不会变形
1 Foo.__x=123123123123123123123123123123123123123123 2 print(Foo.__dict__) 3 print(Foo.__x) 4 f.__x=123123123 5 print(f.__dict__) 6 print(f.__x)
3、在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
1 class Foo: 2 def __f1(self): #_Foo__f1 3 print('Foo.f1') 4 5 def f2(self): 6 self.__f1() #self._Foo_f1 7 8 class Bar(Foo): 9 def __f1(self): #_Bar__f1 10 print('Bar.f1') 11 12 b=Bar() 13 b.f2()
6.3、封装不是单纯意义的隐藏
1、封装数据:将数据隐藏起来这不是目的,隐藏起来然后对外提供操作该数据的接口,然后我们可以在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格控制。
1 class Teacher: 2 def __init__(self,name,age): 3 self.__name=name 4 self.__age=age 5 6 def tell_info(self): 7 print('姓名:%s,年龄:%s' %(self.__name,self.__age)) 8 def set_info(self,name,age): 9 if not isinstance(name,str): 10 raise TypeError('姓名必须是字符串类型') 11 if not isinstance(age,int): 12 raise TypeError('年龄必须是整型') 13 self.__name=name 14 self.__age=age 15 16 t=Teacher('egon',18) 17 t.tell_info() 18 19 t.set_info('egon',19) 20 t.tell_info()
2、封装方法:目的是隔离复杂度
封装方法举例:
1. 你的身体没有一处不体现着封装的概念:你的身体把膀胱尿道等等这些尿的功能隐藏了起来,然后为你提供一个尿的接口就可以了(接口就是你的。。。,),你总不能把膀胱挂在身体外面,上厕所的时候就跟别人炫耀:hi,man,你瞅我的膀胱,看看我是怎么尿的。 2. 电视机本身是一个黑盒子,隐藏了所有细节,但是一定会对外提供了一堆按钮,这些按钮也正是接口的概念,所以说,封装并不是单纯意义的隐藏!!! 3. 快门就是傻瓜相机为傻瓜们提供的方法,该方法将内部复杂的照相功能都隐藏起来了 提示:在编程语言里,对外提供的接口(接口可理解为了一个入口),可以是函数,称为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合体。
1 #取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱 2 #对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做 3 #隔离了复杂度,同时也提升了安全性 4 5 class ATM: 6 def __card(self): 7 print('插卡') 8 def __auth(self): 9 print('用户认证') 10 def __input(self): 11 print('输入取款金额') 12 def __print_bill(self): 13 print('打印账单') 14 def __take_money(self): 15 print('取款') 16 17 def withdraw(self): 18 self.__card() 19 self.__auth() 20 self.__input() 21 self.__print_bill() 22 self.__take_money() 23 24 a=ATM() 25 a.withdraw() 26 27 隔离复杂度的例子
3、了解
python并不会真的阻止你访问私有的属性,模块也遵循这种约定,如果模块名以单下划线开头,那么from module import *时不能被导入,但是你from module import _private_module依然是可以导入的 其实很多时候你去调用一个模块的功能时会遇到单下划线开头的(socket._socket,sys._home,sys._clear_type_cache),这些都是私有的,原则上是供内部调用的,作为外部的你,一意孤行也是可以用的,只不过显得稍微傻逼一点点 python要想与其他编程语言一样,严格控制属性的访问权限,只能借助内置方法如__getattr__,详见面向对象进阶
4、特性(静态属性property)
什么是特性property
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
例一:BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)
成人的BMI数值: 过轻:低于18.5 正常:18.5-23.9 过重:24-27 肥胖:28-32 非常肥胖, 高于32 体质指数(BMI)=体重(kg)÷身高^2(m) EX:70kg÷(1.75×1.75)=22.86
1 class People: 2 def __init__(self,name,weight,height): 3 self.name=name 4 self.weight=weight 5 self.height=height 6 @property 7 def bmi(self): 8 return self.weight / (self.height**2) 9 10 p1=People('egon',75,1.85) 11 print(p1.bmi)
例二:圆的周长和面积
1 import math 2 class Circle: 3 def __init__(self,radius): #圆的半径radius 4 self.radius=radius 5 6 @property 7 def area(self): 8 return math.pi * self.radius**2 #计算面积 9 10 @property 11 def perimeter(self): 12 return 2*math.pi*self.radius #计算周长 13 14 c=Circle(10) 15 print(c.radius) 16 print(c.area) #可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值 17 print(c.perimeter) #同上 18 ''' 19 输出结果: 20 314.1592653589793 21 62.83185307179586 22 '''
#注意:此时的特性arear和perimeter不能被赋值 c.area=3 #特性area赋值 ''' 抛出异常: AttributeError:cat't ser attribute '''
为什么要用property
将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法觉察自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则。
除此之外,看下
ps:面向对象的封装有三种方式:
【public】
这种其实就是不封装,是对外公开的
【protected】
这种封装方式对外不公开,但对朋友(friend)或者子类(形象的说法是“儿子”,但我不知道为什么大家 不说“女儿”,就像“parent”本来是“父母”的意思,但中文都是叫“父类”)公开
【private】
这种封装对谁都不公开
python并没有在语法上把它们三个内建到自己的class机制中,在c++里一般会将所有的数据都设置为私有的,然后提供set和get方法(接口)去设置和获取,在python中通过property方法可以实现
class Foo: def __init__(self,val): self.__NAME=val #将所有的数据属性都隐藏起来 @property def name(self): return self.__NAME #obj.name访问的是self.__NAME(这也是真实值的存放位置) @name.setter def name(self,value): if not isinstance(value,str): #在设定值之前进行类型检查 raise TypeError('%s must be str' %value) self.__NAME=value #通过类型检查后,将值value存放到真实的位置self.__NAME @name.deleter def name(self): raise TypeError('Can not delete') f=Foo('egon') print(f.name) # f.name=10 #抛出异常'TypeError: 10 must be str' del f.name #抛出异常'TypeError: Can not delete'
1 class Foo: 2 def __init__(self,val): 3 self.__NAME=val #将所有的数据属性都隐藏起来 4 5 def getname(self): 6 return self.__NAME #obj.name访问的是self.__NAME(这也是真实值的存放位置) 7 8 def setname(self,value): 9 if not isinstance(value,str): #在设定值之前进行类型检查 10 raise TypeError('%s must be str' %value) 11 self.__NAME=value #通过类型检查后,将值value存放到真实的位置self.__NAME 12 13 def delname(self): 14 raise TypeError('Can not delete') 15 16 name=property(getname,setname,delname) #不如装饰器的方式清晰 17 18 了解:一种property的古老用法
1 #访问,设置,删除(了解) 2 class Foo: 3 def __init__(self,x): 4 self.__Name=x 5 6 @property 7 def name(self): 8 return self.__Name 9 10 @name.setter 11 def name(self,val): 12 if not isinstance(val,str): 13 raise TypeError 14 self.__Name=val 15 16 @name.deleter 17 def name(self): 18 # print('=-====>') 19 # del self.__Name 20 raise PermissionError 21 22 f=Foo('egon') 23 # print(f.name) 24 # 25 # # f.name='Egon' 26 # f.name=123123123213 27 # print(f.name) 28 29 del f.name 30 print(f.name)
七、封装与扩展性
封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响调用者的代码,而外部使用者只知道一个接口(函数),只要接口(函数)名,参数不变,使用者的代码永远无需改变,这就提供了一个良好的合作基础,或者说,只要接口这个基础约定不变,则代码改变不足为虑。
1 #类的设计者 2 class Room: 3 def __init__(self,name,owner,width,length,high): 4 self.name=name 5 self.owner=owner 6 self.__width=width 7 self.__length=length 8 self.__high=high 9 def tell_area(self): #对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积 10 return self.__width * self.__length 11 12 13 #使用者 14 >>> r1=Room('卧室','egon',20,20,20) 15 >>> r1.tell_area() #使用者调用接口tell_area 16 17 18 #类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码 19 class Room: 20 def __init__(self,name,owner,width,length,high): 21 self.name=name 22 self.owner=owner 23 self.__width=width 24 self.__length=length 25 self.__high=high 26 def tell_area(self): #对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了,只需求修该下列一行就可以很简答的实现,而且外部调用感知不到,仍然使用该方法,但是功能已经变了 27 return self.__width * self.__length * self.__high 28 29 30 #对于仍然在使用tell_area接口的人来说,根本无需改动自己的代码,就可以用上新功能 31 >>> r1.tell_area()
八、面向对象的高级应用
8.1、反射
1、什么是反射
反射的概念是由smith在1982年首次提出的,主要是指程序可以访问、检查和修改它本身状态或行为的一种能力(自身),这一概念的提出很快引发了计算机领域关于应用反射性的研究,它首先被程序语言的设计领域所采用,并在lisp和面向对象方面取得了成绩。
2、python面向对象中的反射:通过字符串的形式,操作对象相关的属性。
python中的一切事物都是对象(都可以使用反射)
四个函数实现自省的函数
下列方法适用于类和对象(一切皆对象,类本身也是一个对象)
1 class Foo: 2 x=1 3 def __init__(self,name): 4 self.name=name 5 6 def f1(self): 7 print('from f1') 8 9 10 # print(Foo.x) #Foo.__dict__['x'] 11 12 f=Foo('egon') 13 # print(f.__dict__) 14 # 15 # #1: 16 # print(f.name) 17 18 #2: 19 print(f.__dict__['name']) 20 21 #hasattr 判断对象是否有某个属性 22 print(hasattr(f,'name')) #f.name 23 print(hasattr(f,'f1')) #f.f1 24 print(hasattr(f,'x')) #f.x 25 26 27 # #setattr 设置属性 28 setattr(f,'age',18)#f.age=18 29 print(f.__dict__) #{'name': 'egon', 'age': 18} 30 31 # #getattr 获取属性 32 print(getattr(f,'name'))#f.name 33 # print(getattr(f,'abc'))#f.abc #报错 34 print(getattr(f,'name',None))#f.abc 找不到返回None 35 36 func=getattr(f,'f1')#f.f1 37 print(func) 38 func() 39 # 40 # 41 # #delattr 设置属性 42 delattr(f,'name')# del f.name 43 print(f.__dict__)
1 class Foo(object): 2 3 staticField = "old boy" 4 5 def __init__(self): 6 self.name = 'wupeiqi' 7 8 def func(self): 9 return 'func' 10 11 @staticmethod 12 def bar(): 13 return 'bar' 14 15 print getattr(Foo, 'staticField') 16 print getattr(Foo, 'func') 17 print getattr(Foo, 'bar') 18 19 类也是对象
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import sys 5 6 7 def s1(): 8 print 's1' 9 10 11 def s2(): 12 print 's2' 13 14 15 this_module = sys.modules[__name__] 16 17 hasattr(this_module, 's1') 18 getattr(this_module, 's2')
1 class Ftpserver: 2 def __init__(self,host,port): 3 self.host=host 4 self.port=port 5 6 def run(self): 7 while True: 8 cmd=input('>>: ').strip() 9 if not cmd:continue 10 if hasattr(self,cmd): 11 func=getattr(self,cmd) 12 func() 13 def get(self): 14 print('get func') 15 16 def put(self): 17 print('put func')
导入其他模块,利用反射查找该模块是否存在某个方法
#!/usr/bin/env python # -*- coding:utf-8 -*- def test(): print('from the test')
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 """ 5 程序目录: 6 module_test.py 7 index.py 8 9 当前文件: 10 index.py 11 """ 12 13 import module_test as obj 14 15 #obj.test() 16 17 print(hasattr(obj,'test')) 18 19 getattr(obj,'test')()
3、为什么用反射,反射的好处
好处一:实现可插拔机制
有俩程序员,一个lili,一个是egon,lili在写程序的时候需要用到egon所写的类,但是egon去跟女朋友度蜜月去了,还没有完成他写的类,lili想到了反射,使用了反射机制lili可以继续完成自己的代码,等egon度蜜月回来后再继续完成类的定义并且去实现lili想要的功能。
总之反射的好处就是,可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种‘后期绑定’,什么意思?即你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能
class FtpClient: 'ftp客户端,但是还么有实现具体的功能' def __init__(self,addr): print('正在连接服务器[%s]' %addr) self.addr=addr
1 #from module import FtpClient 2 f1=FtpClient('192.168.1.1') 3 if hasattr(f1,'get'): 4 func_get=getattr(f1,'get') 5 func_get() 6 else: 7 print('---->不存在此方法') 8 print('处理其他的逻辑') 9 10 不影响lili的代码编写
好处二:动态导入模块(基于反射当前模块成员)
九、__setattr__,__delattr__,__getattr__
1 class Foo: 2 x=1 3 def __init__(self,y): 4 self.y=y 5 6 def __getattr__(self, item): 7 print('----> from getattr:你找的属性不存在') 8 9 10 def __setattr__(self, key, value): 11 print('----> from setattr') 12 # self.key=value #这就无限递归了,你好好想想 13 # self.__dict__[key]=value #应该使用它 14 15 def __delattr__(self, item): 16 print('----> from delattr') 17 # del self.item #无限递归了 18 self.__dict__.pop(item) 19 20 #__setattr__添加/修改属性会触发它的执行 21 f1=Foo(10) 22 print(f1.__dict__) # 因为你重写了__setattr__,凡是赋值操作都会触发它的运行,你啥都没写,就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值 23 f1.z=3 24 print(f1.__dict__) 25 26 #__delattr__删除属性的时候会触发 27 f1.__dict__['a']=3#我们可以直接修改属性字典,来完成添加/修改属性的操作 28 del f1.a 29 print(f1.__dict__) 30 31 #__getattr__只有在使用点调用属性且属性不存在的时候才会触发 32 f1.xxxxxx 33 34 三者的用法演示
十、二次加工标准类型(包装)
包装:python为大家提供了标准数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增/改写方法,这就用到了我们刚学的继承/派生知识(其他的标准类型均可以通过下面的方式进行二次加工)
1 class List(list): #继承list所有的属性,也可以派生出自己新的,比如append和mid 2 def append(self, p_object): 3 ' 派生自己的append:加上类型检查' 4 if not isinstance(p_object,int): 5 raise TypeError('must be int') 6 super().append(p_object) 7 8 @property 9 def mid(self): 10 '新增自己的属性' 11 index=len(self)//2 12 return self[index] 13 14 l=List([1,2,3,4]) 15 print(l) 16 l.append(5) 17 print(l) 18 # l.append('1111111') #报错,必须为int类型 19 20 print(l.mid) 21 22 #其余的方法都继承list的 23 l.insert(0,-123) 24 print(l) 25 l.clear() 26 print(l) 27 28 二次加工标准类型(基于继承实现)
1 class List(list): 2 def __init__(self,item,tag=False): 3 super().__init__(item) 4 self.tag=tag 5 def append(self, p_object): 6 if not isinstance(p_object,str): 7 raise TypeError 8 super().append(p_object) 9 def clear(self): 10 if not self.tag: 11 raise PermissionError 12 super().clear() 13 14 l=List([1,2,3],False) 15 print(l) 16 print(l.tag) 17 18 l.append('saf') 19 print(l) 20 21 # l.clear() #异常 22 23 l.tag=True 24 l.clear() 25 26 练习(clear加权限限制)
授权:授权是包装的一个特性,包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建,修改或删除原有产品的功能,其它的则保持原样,授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。
实现授权的关键点就是覆盖__getattr__方法
1 import time 2 class FileHandle: 3 def __init__(self,filename,mode='r',encoding='utf-8'): 4 self.file=open(filename,mode,encoding=encoding) 5 def write(self,line): 6 t=time.strftime('%Y-%m-%d %T') 7 self.file.write('%s %s' %(t,line)) 8 9 def __getattr__(self, item): 10 return getattr(self.file,item) 11 12 f1=FileHandle('b.txt','w+') 13 f1.write('你好啊') 14 f1.seek(0) 15 print(f1.read()) 16 f1.close() 17 18 授权示范一
1 #_*_coding:utf-8_*_ 2 __author__ = 'Linhaifeng' 3 #我们来加上b模式支持 4 import time 5 class FileHandle: 6 def __init__(self,filename,mode='r',encoding='utf-8'): 7 if 'b' in mode: 8 self.file=open(filename,mode) 9 else: 10 self.file=open(filename,mode,encoding=encoding) 11 self.filename=filename 12 self.mode=mode 13 self.encoding=encoding 14 15 def write(self,line): 16 if 'b' in self.mode: 17 if not isinstance(line,bytes): 18 raise TypeError('must be bytes') 19 self.file.write(line) 20 21 def __getattr__(self, item): 22 return getattr(self.file,item) 23 24 def __str__(self): 25 if 'b' in self.mode: 26 res="<_io.BufferedReader name='%s'>" %self.filename 27 else: 28 res="<_io.TextIOWrapper name='%s' mode='%s' encoding='%s'>" %(self.filename,self.mode,self.encoding) 29 return res 30 f1=FileHandle('b.txt','wb') 31 # f1.write('你好啊啊啊啊啊') #自定制的write,不用在进行encode转成二进制去写了,简单,大气 32 f1.write('你好啊'.encode('utf-8')) 33 print(f1) 34 f1.close() 35 36 授权示范二
1 #练习一 2 class List: 3 def __init__(self,seq): 4 self.seq=seq 5 6 def append(self, p_object): 7 ' 派生自己的append加上类型检查,覆盖原有的append' 8 if not isinstance(p_object,int): 9 raise TypeError('must be int') 10 self.seq.append(p_object) 11 12 @property 13 def mid(self): 14 '新增自己的方法' 15 index=len(self.seq)//2 16 return self.seq[index] 17 18 def __getattr__(self, item): 19 return getattr(self.seq,item) 20 21 def __str__(self): 22 return str(self.seq) 23 24 l=List([1,2,3]) 25 print(l) 26 l.append(4) 27 print(l) 28 # l.append('3333333') #报错,必须为int类型 29 30 print(l.mid) 31 32 #基于授权,获得insert方法 33 l.insert(0,-123) 34 print(l) 35 36 37 38 39 40 #练习二 41 class List: 42 def __init__(self,seq,permission=False): 43 self.seq=seq 44 self.permission=permission 45 def clear(self): 46 if not self.permission: 47 raise PermissionError('not allow the operation') 48 self.seq.clear() 49 50 def __getattr__(self, item): 51 return getattr(self.seq,item) 52 53 def __str__(self): 54 return str(self.seq) 55 l=List([1,2,3]) 56 # l.clear() #此时没有权限,抛出异常 57 58 59 l.permission=True 60 print(l) 61 l.clear() 62 print(l) 63 64 #基于授权,获得insert方法 65 l.insert(0,-123) 66 print(l) 67 68 练习题(授权)
十一、描述符(__get__,__set__,__delete__)
1、描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议
__get__():调用一个属性时,触发
__set__():为一个属性赋值时,触发
__delete__():采用del删除属性时,触发
1 class Foo: #在python3中Foo是新式类,它实现了三种方法,这个类就被称作一个描述符 2 def __get__(self, instance, owner): 3 pass 4 def __set__(self, instance, value): 5 pass 6 def __delete__(self, instance): 7 pass 8 9 定义一个描述符
2、描述符是干什么的:描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)
1 class Foo: 2 def __get__(self, instance, owner): 3 print('触发get') 4 def __set__(self, instance, value): 5 print('触发set') 6 def __delete__(self, instance): 7 print('触发delete') 8 9 #包含这三个方法的新式类称为描述符,由这个类产生的实例进行属性的调用/赋值/删除,并不会触发这三个方法 10 f1=Foo() 11 f1.name='egon' 12 f1.name 13 del f1.name 14 #疑问:何时,何地,会触发这三个方法的执行
1 #描述符Str 2 class Str: 3 def __get__(self, instance, owner): 4 print('Str调用') 5 def __set__(self, instance, value): 6 print('Str设置...') 7 def __delete__(self, instance): 8 print('Str删除...') 9 10 #描述符Int 11 class Int: 12 def __get__(self, instance, owner): 13 print('Int调用') 14 def __set__(self, instance, value): 15 print('Int设置...') 16 def __delete__(self, instance): 17 print('Int删除...') 18 19 class People: 20 name=Str() 21 age=Int() 22 def __init__(self,name,age): #name被Str类代理,age被Int类代理, 23 self.name=name 24 self.age=age 25 26 #何地?:定义成另外一个类的类属性 27 28 #何时?:且看下列演示 29 30 p1=People('alex',18) 31 32 #描述符Str的使用 33 p1.name 34 p1.name='egon' 35 del p1.name 36 37 #描述符Int的使用 38 p1.age 39 p1.age=18 40 del p1.age 41 42 #我们来瞅瞅到底发生了什么 43 print(p1.__dict__) 44 print(People.__dict__) 45 46 #补充 47 print(type(p1) == People) #type(obj)其实是查看obj是由哪个类实例化来的 48 print(type(p1).__dict__ == People.__dict__)
3、描述符分两种
a、数据描述符:至少实现了__get__()和__set__()
1 class Foo: 2 def __set__(self, instance, value): 3 print('set') 4 def __get__(self, instance, owner): 5 print('get')
b、非数据描述符:没有实现__set__()
1 class Foo: 2 def __get__(self, instance, owner): 3 print('get')
4、注意事项:
1)、描述符本身应该定义成新式类,被代理的类也应该是新式类
2)、必须把描述符定义成这个类的类属性,不能定义到构造函数中
3)、要严格遵循该优先级,优先级由高到低分别是:
a、类属性
b、数据描述符
c、实例属性
d、非数据描述符
e、找不到的属性触发__getattr__()
1 #描述符Str 2 class Str: 3 def __get__(self, instance, owner): 4 print('Str调用') 5 def __set__(self, instance, value): 6 print('Str设置...') 7 def __delete__(self, instance): 8 print('Str删除...') 9 10 class People: 11 name=Str() 12 def __init__(self,name,age): #name被Str类代理,age被Int类代理, 13 self.name=name 14 self.age=age 15 16 17 #基于上面的演示,我们已经知道,在一个类中定义描述符它就是一个类属性,存在于类的属性字典中,而不是实例的属性字典 18 19 #那既然描述符被定义成了一个类属性,直接通过类名也一定可以调用吧,没错 20 People.name #恩,调用类属性name,本质就是在调用描述符Str,触发了__get__() 21 22 People.name='egon' #那赋值呢,我去,并没有触发__set__() 23 del People.name #赶紧试试del,我去,也没有触发__delete__() 24 #结论:描述符对类没有作用-------->傻逼到家的结论 25 26 ''' 27 原因:描述符在使用时被定义成另外一个类的类属性,因而类属性比二次加工的描述符伪装而来的类属性有更高的优先级 28 People.name #恩,调用类属性name,找不到就去找描述符伪装的类属性name,触发了__get__() 29 30 People.name='egon' #那赋值呢,直接赋值了一个类属性,它拥有更高的优先级,相当于覆盖了描述符,肯定不会触发描述符的__set__() 31 del People.name #同上 32 '''
1 #描述符Str 2 class Str: 3 def __get__(self, instance, owner): 4 print('Str调用') 5 def __set__(self, instance, value): 6 print('Str设置...') 7 def __delete__(self, instance): 8 print('Str删除...') 9 10 class People: 11 name=Str() 12 def __init__(self,name,age): #name被Str类代理,age被Int类代理, 13 self.name=name 14 self.age=age 15 16 17 p1=People('egon',18) 18 19 #如果描述符是一个数据描述符(即有__get__又有__set__),那么p1.name的调用与赋值都是触发描述符的操作,于p1本身无关了,相当于覆盖了实例的属性 20 p1.name='egonnnnnn' 21 p1.name 22 print(p1.__dict__)#实例的属性字典中没有name,因为name是一个数据描述符,优先级高于实例属性,查看/赋值/删除都是跟描述符有关,与实例无关了 23 del p1.name
1 class Foo: 2 def func(self): 3 print('我胡汉三又回来了') 4 f1=Foo() 5 f1.func() #调用类的方法,也可以说是调用非数据描述符 6 #函数是一个非数据描述符对象(一切皆对象么) 7 print(dir(Foo.func)) 8 print(hasattr(Foo.func,'__set__')) 9 print(hasattr(Foo.func,'__get__')) 10 print(hasattr(Foo.func,'__delete__')) 11 #有人可能会问,描述符不都是类么,函数怎么算也应该是一个对象啊,怎么就是描述符了 12 #笨蛋哥,描述符是类没问题,描述符在应用的时候不都是实例化成一个类属性么 13 #函数就是一个由非描述符类实例化得到的对象 14 #没错,字符串也一样 15 16 17 f1.func='这是实例属性啊' 18 print(f1.func) 19 20 del f1.func #删掉了非数据 21 f1.func()
1 class Foo: 2 def __set__(self, instance, value): 3 print('set') 4 def __get__(self, instance, owner): 5 print('get') 6 class Room: 7 name=Foo() 8 def __init__(self,name,width,length): 9 self.name=name 10 self.width=width 11 self.length=length 12 13 14 #name是一个数据描述符,因为name=Foo()而Foo实现了get和set方法,因而比实例属性有更高的优先级 15 #对实例的属性操作,触发的都是描述符的 16 r1=Room('厕所',1,1) 17 r1.name 18 r1.name='厨房' 19 20 21 22 class Foo: 23 def __get__(self, instance, owner): 24 print('get') 25 class Room: 26 name=Foo() 27 def __init__(self,name,width,length): 28 self.name=name 29 self.width=width 30 self.length=length 31 32 33 #name是一个非数据描述符,因为name=Foo()而Foo没有实现set方法,因而比实例属性有更低的优先级 34 #对实例的属性操作,触发的都是实例自己的 35 r1=Room('厕所',1,1) 36 r1.name 37 r1.name='厨房'
1 class Foo: 2 def func(self): 3 print('我胡汉三又回来了') 4 5 def __getattr__(self, item): 6 print('找不到了当然是来找我啦',item) 7 f1=Foo() 8 9 f1.xxxxxxxxxxx
5、描述符使用
众所周知,python是弱类型语言,即参数的赋值没有类型限制,下面我们通过描述符机制来实现类型限制功能
1 class Str: 2 def __init__(self,name): 3 self.name=name 4 def __get__(self, instance, owner): 5 print('get--->',instance,owner) 6 return instance.__dict__[self.name] 7 8 def __set__(self, instance, value): 9 print('set--->',instance,value) 10 instance.__dict__[self.name]=value 11 def __delete__(self, instance): 12 print('delete--->',instance) 13 instance.__dict__.pop(self.name) 14 15 16 class People: 17 name=Str('name') 18 def __init__(self,name,age,salary): 19 self.name=name 20 self.age=age 21 self.salary=salary 22 23 p1=People('egon',18,3231.3) 24 25 #调用 26 print(p1.__dict__) 27 p1.name 28 29 #赋值 30 print(p1.__dict__) 31 p1.name='egonlin' 32 print(p1.__dict__) 33 34 #删除 35 print(p1.__dict__) 36 del p1.name 37 print(p1.__dict__) 38 39 牛刀小试
1 class Str: 2 def __init__(self,name): 3 self.name=name 4 def __get__(self, instance, owner): 5 print('get--->',instance,owner) 6 return instance.__dict__[self.name] 7 8 def __set__(self, instance, value): 9 print('set--->',instance,value) 10 instance.__dict__[self.name]=value 11 def __delete__(self, instance): 12 print('delete--->',instance) 13 instance.__dict__.pop(self.name) 14 15 16 class People: 17 name=Str('name') 18 def __init__(self,name,age,salary): 19 self.name=name 20 self.age=age 21 self.salary=salary 22 23 #疑问:如果我用类名去操作属性呢 24 People.name #报错,错误的根源在于类去操作属性时,会把None传给instance 25 26 #修订__get__方法 27 class Str: 28 def __init__(self,name): 29 self.name=name 30 def __get__(self, instance, owner): 31 print('get--->',instance,owner) 32 if instance is None: 33 return self 34 return instance.__dict__[self.name] 35 36 def __set__(self, instance, value): 37 print('set--->',instance,value) 38 instance.__dict__[self.name]=value 39 def __delete__(self, instance): 40 print('delete--->',instance) 41 instance.__dict__.pop(self.name) 42 43 44 class People: 45 name=Str('name') 46 def __init__(self,name,age,salary): 47 self.name=name 48 self.age=age 49 self.salary=salary 50 print(People.name) #完美,解决 51 52 拔刀相助
1 class Str: 2 def __init__(self,name,expected_type): 3 self.name=name 4 self.expected_type=expected_type 5 def __get__(self, instance, owner): 6 print('get--->',instance,owner) 7 if instance is None: 8 return self 9 return instance.__dict__[self.name] 10 11 def __set__(self, instance, value): 12 print('set--->',instance,value) 13 if not isinstance(value,self.expected_type): #如果不是期望的类型,则抛出异常 14 raise TypeError('Expected %s' %str(self.expected_type)) 15 instance.__dict__[self.name]=value 16 def __delete__(self, instance): 17 print('delete--->',instance) 18 instance.__dict__.pop(self.name) 19 20 21 class People: 22 name=Str('name',str) #新增类型限制str 23 def __init__(self,name,age,salary): 24 self.name=name 25 self.age=age 26 self.salary=salary 27 28 p1=People(123,18,3333.3)#传入的name因不是字符串类型而抛出异常
1 class Typed: 2 def __init__(self,name,expected_type): 3 self.name=name 4 self.expected_type=expected_type 5 def __get__(self, instance, owner): 6 print('get--->',instance,owner) 7 if instance is None: 8 return self 9 return instance.__dict__[self.name] 10 11 def __set__(self, instance, value): 12 print('set--->',instance,value) 13 if not isinstance(value,self.expected_type): 14 raise TypeError('Expected %s' %str(self.expected_type)) 15 instance.__dict__[self.name]=value 16 def __delete__(self, instance): 17 print('delete--->',instance) 18 instance.__dict__.pop(self.name) 19 20 21 class People: 22 name=Typed('name',str) 23 age=Typed('name',int) 24 salary=Typed('name',float) 25 def __init__(self,name,age,salary): 26 self.name=name 27 self.age=age 28 self.salary=salary 29 30 p1=People(123,18,3333.3) 31 p1=People('egon','18',3333.3) 32 p1=People('egon',18,3333)
大刀阔斧之后我们已然能实现功能了,但是问题是,如果我们的类有很多属性,你仍然采用在定义一堆类属性的方式去实现,low.
1 def decorate(cls): 2 print('类的装饰器开始运行啦------>') 3 return cls 4 5 @decorate #无参:People=decorate(People) 6 class People: 7 def __init__(self,name,age,salary): 8 self.name=name 9 self.age=age 10 self.salary=salary 11 12 p1=People('egon',18,3333.3)
1 def typeassert(**kwargs): 2 def decorate(cls): 3 print('类的装饰器开始运行啦------>',kwargs) 4 return cls 5 return decorate 6 @typeassert(name=str,age=int,salary=float) #有参:1.运行typeassert(...)返回结果是decorate,此时参数都传给kwargs 2.People=decorate(People) 7 class People: 8 def __init__(self,name,age,salary): 9 self.name=name 10 self.age=age 11 self.salary=salary 12 13 p1=People('egon',18,3333.3)
1 class Typed: 2 def __init__(self,name,expected_type): 3 self.name=name 4 self.expected_type=expected_type 5 def __get__(self, instance, owner): 6 print('get--->',instance,owner) 7 if instance is None: 8 return self 9 return instance.__dict__[self.name] 10 11 def __set__(self, instance, value): 12 print('set--->',instance,value) 13 if not isinstance(value,self.expected_type): 14 raise TypeError('Expected %s' %str(self.expected_type)) 15 instance.__dict__[self.name]=value 16 def __delete__(self, instance): 17 print('delete--->',instance) 18 instance.__dict__.pop(self.name) 19 20 def typeassert(**kwargs): 21 def decorate(cls): 22 print('类的装饰器开始运行啦------>',kwargs) 23 for name,expected_type in kwargs.items(): 24 setattr(cls,name,Typed(name,expected_type)) 25 return cls 26 return decorate 27 @typeassert(name=str,age=int,salary=float) #有参:1.运行typeassert(...)返回结果是decorate,此时参数都传给kwargs 2.People=decorate(People) 28 class People: 29 def __init__(self,name,age,salary): 30 self.name=name 31 self.age=age 32 self.salary=salary 33 34 print(People.__dict__) 35 p1=People('egon',18,3333.3)
6、描述符总结
描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod,@staticmethd,@property甚至是__slots__属性。
描述符是很多高级库和框架的重要工具之一,描述符通常使用到装饰器或者元类的大型框架中的一个组件。
7、利用描述符原理完成一个自定制@property,实现延迟计算(本质就是把一个函数属性利用装饰器原理做成一个描述符:类的属性字典中函数名为key,value为描述符类产生的对象)
1 class Room: 2 def __init__(self,name,width,length): 3 self.name=name 4 self.width=width 5 self.length=length 6 7 @property 8 def area(self): 9 return self.width * self.length 10 11 r1=Room('alex',1,1) 12 print(r1.area)
1 class Lazyproperty: 2 def __init__(self,func): 3 self.func=func 4 def __get__(self, instance, owner): 5 print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()') 6 if instance is None: 7 return self 8 return self.func(instance) #此时你应该明白,到底是谁在为你做自动传递self的事情 9 10 class Room: 11 def __init__(self,name,width,length): 12 self.name=name 13 self.width=width 14 self.length=length 15 16 @Lazyproperty #area=Lazyproperty(area) 相当于定义了一个类属性,即描述符 17 def area(self): 18 return self.width * self.length 19 20 r1=Room('alex',1,1) 21 print(r1.area)
1 class Lazyproperty: 2 def __init__(self,func): 3 self.func=func 4 def __get__(self, instance, owner): 5 print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()') 6 if instance is None: 7 return self 8 else: 9 print('--->') 10 value=self.func(instance) 11 setattr(instance,self.func.__name__,value) #计算一次就缓存到实例的属性字典中 12 return value 13 14 class Room: 15 def __init__(self,name,width,length): 16 self.name=name 17 self.width=width 18 self.length=length 19 20 @Lazyproperty #area=Lazyproperty(area) 相当于'定义了一个类属性,即描述符' 21 def area(self): 22 return self.width * self.length 23 24 r1=Room('alex',1,1) 25 print(r1.area) #先从自己的属性字典找,没有再去类的中找,然后出发了area的__get__方法 26 print(r1.area) #先从自己的属性字典找,找到了,是上次计算的结果,这样就不用每执行一次都去计算
1 #缓存不起来了 2 3 class Lazyproperty: 4 def __init__(self,func): 5 self.func=func 6 def __get__(self, instance, owner): 7 print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()') 8 if instance is None: 9 return self 10 else: 11 value=self.func(instance) 12 instance.__dict__[self.func.__name__]=value 13 return value 14 # return self.func(instance) #此时你应该明白,到底是谁在为你做自动传递self的事情 15 def __set__(self, instance, value): 16 print('hahahahahah') 17 18 class Room: 19 def __init__(self,name,width,length): 20 self.name=name 21 self.width=width 22 self.length=length 23 24 @Lazyproperty #area=Lazyproperty(area) 相当于定义了一个类属性,即描述符 25 def area(self): 26 return self.width * self.length 27 28 print(Room.__dict__) 29 r1=Room('alex',1,1) 30 print(r1.area) 31 print(r1.area) 32 print(r1.area) 33 print(r1.area) #缓存功能失效,每次都去找描述符了,为何,因为描述符实现了set方法,它由非数据描述符变成了数据描述符,数据描述符比实例属性有更高的优先级,因而所有的属性操作都去找描述符了
8、利用描述符原理完成一个自定制@classmethod
1 class ClassMethod: 2 def __init__(self,func): 3 self.func=func 4 5 def __get__(self, instance, owner): #类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身, 6 def feedback(): 7 print('在这里可以加功能啊...') 8 return self.func(owner) 9 return feedback 10 11 class People: 12 name='linhaifeng' 13 @ClassMethod # say_hi=ClassMethod(say_hi) 14 def say_hi(cls): 15 print('你好啊,帅哥 %s' %cls.name) 16 17 People.say_hi() 18 19 p1=People() 20 p1.say_hi() 21 #疑问,类方法如果有参数呢,好说,好说 22 23 class ClassMethod: 24 def __init__(self,func): 25 self.func=func 26 27 def __get__(self, instance, owner): #类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身, 28 def feedback(*args,**kwargs): 29 print('在这里可以加功能啊...') 30 return self.func(owner,*args,**kwargs) 31 return feedback 32 33 class People: 34 name='linhaifeng' 35 @ClassMethod # say_hi=ClassMethod(say_hi) 36 def say_hi(cls,msg): 37 print('你好啊,帅哥 %s %s' %(cls.name,msg)) 38 39 People.say_hi('你是那偷心的贼') 40 41 p1=People() 42 p1.say_hi('你是那偷心的贼')
9、利用描述符原理完成一个自定制的@staticmethod
1 class StaticMethod: 2 def __init__(self,func): 3 self.func=func 4 5 def __get__(self, instance, owner): #类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身, 6 def feedback(*args,**kwargs): 7 print('在这里可以加功能啊...') 8 return self.func(*args,**kwargs) 9 return feedback 10 11 class People: 12 @StaticMethod# say_hi=StaticMethod(say_hi) 13 def say_hi(x,y,z): 14 print('------>',x,y,z) 15 16 People.say_hi(1,2,3) 17 18 p1=People() 19 p1.say_hi(4,5,6)
十二、静态属性函数property
一个静态属性property本质就是实现了get,set,delet三种方法
1 class Foo: 2 @property 3 def AAA(self): 4 print('get的时候运行我啊') 5 6 @AAA.setter 7 def AAA(self,value): 8 print('set的时候运行我啊') 9 10 @AAA.deleter 11 def AAA(self): 12 print('delete的时候运行我啊') 13 14 #只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter 15 f1=Foo() 16 f1.AAA 17 f1.AAA='aaa' 18 del f1.AAA
1 class Foo: 2 def get_AAA(self): 3 print('get的时候运行我啊') 4 5 def set_AAA(self,value): 6 print('set的时候运行我啊') 7 8 def delete_AAA(self): 9 print('delete的时候运行我啊') 10 AAA=property(get_AAA,set_AAA,delete_AAA) #内置property三个参数与get,set,delete一一对应 11 12 f1=Foo() 13 f1.AAA 14 f1.AAA='aaa' 15 del f1.AAA
怎么用?
1 class Goods: 2 3 def __init__(self): 4 # 原价 5 self.original_price = 100 6 # 折扣 7 self.discount = 0.8 8 9 @property 10 def price(self): 11 # 实际价格 = 原价 * 折扣 12 new_price = self.original_price * self.discount 13 return new_price 14 15 @price.setter 16 def price(self, value): 17 self.original_price = value 18 19 @price.deleter 20 def price(self): 21 del self.original_price 22 23 24 obj = Goods() 25 obj.price # 获取商品价格 26 obj.price = 200 # 修改商品原价 27 print(obj.price) 28 del obj.price # 删除商品原价
1 #实现类型检测功能 2 3 #第一关: 4 class People: 5 def __init__(self,name): 6 self.name=name 7 8 @property 9 def name(self): 10 return self.name 11 12 # p1=People('alex') #property自动实现了set和get方法属于数据描述符,比实例属性优先级高,所以你这面写会触发property内置的set,抛出异常 13 14 15 #第二关:修订版 16 17 class People: 18 def __init__(self,name): 19 self.name=name #实例化就触发property 20 21 @property 22 def name(self): 23 # return self.name #无限递归 24 print('get------>') 25 return self.DouNiWan 26 27 @name.setter 28 def name(self,value): 29 print('set------>') 30 self.DouNiWan=value 31 32 @name.deleter 33 def name(self): 34 print('delete------>') 35 del self.DouNiWan 36 37 p1=People('alex') #self.name实际是存放到self.DouNiWan里 38 print(p1.name) 39 print(p1.name) 40 print(p1.name) 41 print(p1.__dict__) 42 43 p1.name='egon' 44 print(p1.__dict__) 45 46 del p1.name 47 print(p1.__dict__) 48 49 50 #第三关:加上类型检查 51 class People: 52 def __init__(self,name): 53 self.name=name #实例化就触发property 54 55 @property 56 def name(self): 57 # return self.name #无限递归 58 print('get------>') 59 return self.DouNiWan 60 61 @name.setter 62 def name(self,value): 63 print('set------>') 64 if not isinstance(value,str): 65 raise TypeError('必须是字符串类型') 66 self.DouNiWan=value 67 68 @name.deleter 69 def name(self): 70 print('delete------>') 71 del self.DouNiWan 72 73 p1=People('alex') #self.name实际是存放到self.DouNiWan里 74 p1.name=1
十三、__setitem__,__getitem__,__delitem__
1 class Foo: 2 def __init__(self,name): 3 self.name=name 4 5 def __getitem__(self, item): 6 print(self.__dict__[item]) 7 8 def __setitem__(self, key, value): 9 self.__dict__[key]=value 10 def __delitem__(self, key): 11 print('del obj[key]时,我执行') 12 self.__dict__.pop(key) 13 def __delattr__(self, item): 14 print('del obj.key时,我执行') 15 self.__dict__.pop(item) 16 17 f1=Foo('sb') 18 f1['age']=18 19 f1['age1']=19 20 del f1.age1 21 del f1['age'] 22 f1['name']='alex' 23 print(f1.__dict__)
十四、打印对象信息__str__
改变对象的字符串显示__str__
1 class People: 2 def __init__(self,name,age,sex): 3 self.name=name 4 self.age=age 5 self.sex=sex 6 7 def __str__(self): #在对象被打印时触发执行 8 return '<name:%s age:%s sex:%s>' %(self.name,self.age,self.sex) 9 10 p1=People('egon',18,'male') 11 p2=People('alex',38,'male') 12 13 print(p1) 14 print(p2)
十五、析构方法__del__
析构方法,当对象在内存中被释放时,自动触发执行。
注:如果产生的对象仅仅只是python程序级别的(用户级),那么无需定义__del__,如果产生的对象的同时还会向操作系统发起系统调用,即一个对象有用户级与内核级两种资源,比如(打开一个文件,创建一个数据库链接),则必须在清除对象的同时回收系统资源,这就用到了__del__
1 class Foo: 2 3 def __del__(self): 4 print('执行我啦') 5 6 f1=Foo() 7 del f1 8 print('------->') 9 10 #输出结果 11 执行我啦 12 ------->
1 class Foo: 2 3 def __del__(self): 4 print('执行我啦') 5 6 f1=Foo() 7 # del f1 8 print('------->') 9 10 #输出结果 11 -------> 12 执行我啦 13 14 15 16 17 18 #为何啊???
典型的应用场景:
创建数据库类,用该类实例化出数据库链接对象,对象本身是存放于用户空间内存中,而链接则是由操作系统管理的,存放于内核空间内存中
当程序结束时,python只会回收自己的内存空间,即用户态内存,而操作系统的资源则没有被回收,这就需要我们定制__del__,在对象被删除前向操作系统发起关闭数据库链接的系统调用,回收资源。
1 f=open('a.txt') #做了两件事,在用户空间拿到一个f变量,在操作系统内核空间打开一个文件 2 del f #只回收用户空间的f,操作系统的文件还处于打开状态 3 4 #所以我们应该在del f之前保证f.close()执行,即便是没有del,程序执行完毕也会自动del清理资源,于是文件操作的正确用法应该是 5 f=open('a.txt') 6 读写... 7 f.close() 8 很多情况下大家都容易忽略f.close,这就用到了with上下文管理
十六、异常处理
1、什么是异常
异常就是程序运行时发生错误的信号(在程序出现错误时,则会产生一个异常,若程序没有处理它,则会抛出该异常,程序的运行也随之终止),在python中,错误触发的异常如下:
而错误分成两种
1 #语法错误示范一 2 if 3 #语法错误示范二 4 def test: 5 pass 6 #语法错误示范三 7 class Foo 8 pass 9 #语法错误示范四 10 print(haha
1 #TypeError:int类型不可迭代 2 for i in 3: 3 pass 4 #ValueError 5 num=input(">>: ") #输入hello 6 int(num) 7 8 #NameError 9 aaa 10 11 #IndexError 12 l=['egon','aa'] 13 l[3] 14 15 #KeyError 16 dic={'name':'egon'} 17 dic['age'] 18 19 #AttributeError 20 class Foo:pass 21 Foo.x 22 23 #ZeroDivisionError:无法完成计算 24 res1=1/0 25 res2=1+'str'
2、异常的种类
在python中不同的异常可以用不同的类型(在python中统一了类与类型,类型即类)去标识,一个异常标识一种错误
1 AttributeError 试图访问一个对象没有的树形,比如foo.x,但是foo没有属性x 2 IOError 输入/输出异常;基本上是无法打开文件 3 ImportError 无法引入模块或包;基本上是路径问题或名称错误 4 IndentationError 语法错误(的子类) ;代码没有正确对齐 5 IndexError 下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5] 6 KeyError 试图访问字典里不存在的键 7 KeyboardInterrupt Ctrl+C被按下 8 NameError 使用一个还未被赋予对象的变量 9 SyntaxError Python代码非法,代码不能编译(个人认为这是语法错误,写错了) 10 TypeError 传入对象类型与要求的不符合 11 UnboundLocalError 试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量, 12 导致你以为正在访问它 13 ValueError 传入一个调用者不期望的值,即使值的类型是正确的
1 ArithmeticError 2 AssertionError 3 AttributeError 4 BaseException 5 BufferError 6 BytesWarning 7 DeprecationWarning 8 EnvironmentError 9 EOFError 10 Exception 11 FloatingPointError 12 FutureWarning 13 GeneratorExit 14 ImportError 15 ImportWarning 16 IndentationError 17 IndexError 18 IOError 19 KeyboardInterrupt 20 KeyError 21 LookupError 22 MemoryError 23 NameError 24 NotImplementedError 25 OSError 26 OverflowError 27 PendingDeprecationWarning 28 ReferenceError 29 RuntimeError 30 RuntimeWarning 31 StandardError 32 StopIteration 33 SyntaxError 34 SyntaxWarning 35 SystemError 36 SystemExit 37 TabError 38 TypeError 39 UnboundLocalError 40 UnicodeDecodeError 41 UnicodeEncodeError 42 UnicodeError 43 UnicodeTranslateError 44 UnicodeWarning 45 UserWarning 46 ValueError 47 Warning 48 ZeroDivisionError
3、异常处理
为了保证程序的健壮性与容错性,即在遇到错误时程序不会崩溃,我们需要对异常进行处理
如果错误发生的条件是可预知的,我们需要用if进行处理:在错误发生之前进行预防
1 AGE=10 2 while True: 3 age=input('>>: ').strip() 4 if age.isdigit(): #只有在age为字符串形式的整数时,下列代码才不会出错,该条件是可预知的 5 age=int(age) 6 if age == AGE: 7 print('you got it') 8 break
如果错误发生的条件是不可预知的,则需要用到try.....exceot:在错误发生之后进行处理
1 #基本语法为 2 try: 3 被检测的代码块 4 except 异常类型: 5 try中一旦检测到异常,就执行这个位置的逻辑 6 #举例 7 try: 8 f=open('a.txt') 9 g=(line.strip() for line in f) 10 print(next(g)) 11 print(next(g)) 12 print(next(g)) 13 print(next(g)) 14 print(next(g)) 15 except StopIteration: 16 f.close()
1 #1 异常类只能用来处理指定的异常情况,如果非指定异常则无法处理。 2 s1 = 'hello' 3 try: 4 int(s1) 5 except IndexError as e: # 未捕获到异常,程序直接报错 6 print e 7 8 #2 多分支 9 s1 = 'hello' 10 try: 11 int(s1) 12 except IndexError as e: 13 print(e) 14 except KeyError as e: 15 print(e) 16 except ValueError as e: 17 print(e) 18 19 #3 万能异常Exception 20 s1 = 'hello' 21 try: 22 int(s1) 23 except Exception as e: 24 print(e) 25 26 #4 多分支异常与万能异常 27 #4.1 如果你想要的效果是,无论出现什么异常,我们统一丢弃,或者使用同一段代码逻辑去处理他们,那么骚年,大胆的去做吧,只有一个Exception就足够了。 28 #4.2 如果你想要的效果是,对于不同的异常我们需要定制不同的处理逻辑,那就需要用到多分支了。 29 30 #5 也可以在多分支后来一个Exception 31 s1 = 'hello' 32 try: 33 int(s1) 34 except IndexError as e: 35 print(e) 36 except KeyError as e: 37 print(e) 38 except ValueError as e: 39 print(e) 40 except Exception as e: 41 print(e) 42 43 #6 异常的其他机构 44 s1 = 'hello' 45 try: 46 int(s1) 47 except IndexError as e: 48 print(e) 49 except KeyError as e: 50 print(e) 51 except ValueError as e: 52 print(e) 53 #except Exception as e: 54 # print(e) 55 else: 56 print('try内代码块没有异常则执行我') 57 finally: 58 print('无论异常与否,都会执行该模块,通常是进行清理工作') 59 60 #7 主动触发异常 61 try: 62 raise TypeError('类型错误') 63 except Exception as e: 64 print(e) 65 66 #8 自定义异常 67 class EgonException(BaseException): 68 def __init__(self,msg): 69 self.msg=msg 70 def __str__(self): 71 return self.msg 72 73 try: 74 raise EgonException('类型错误') 75 except EgonException as e: 76 print(e) 77 78 #9 断言:assert 条件 79 assert 1 == 1 80 assert 1 == 2 81 82 #10 总结try..except 83 84 1:把错误处理和真正的工作分开来 85 2:代码更易组织,更清晰,复杂的工作任务更容易实现; 86 3:毫无疑问,更安全了,不至于由于一些小的疏忽而使程序意外崩溃了;
4、什么时候用异常处理
有的同学会这么想,学完了异常处理后,好强大,我要为我的每一段程序都加上try...except,干毛线去思考它会不会有逻辑错误啊,这样就很好啊,多省脑细胞===》2B青年欢乐多 首先try...except是你附加给你的程序的一种异常处理的逻辑,与你的主要的工作是没有关系的,这种东西加的多了,会导致你的代码可读性变差 然后异常处理本就不是你2b逻辑的擦屁股纸,只有在错误发生的条件无法预知的情况下,才应该加上try...except
十八、网络编程
详细请见 第 9 章 网络编程 地址 :http://www.cnblogs.com/fanglingen/articles/7410918.html