Python学习笔记七(面向对象)
一、继承与派生
1、继承定义
继承是创建新类的一种方式,新创建的类可以继承一个或多个父类(基类),新建的类也叫派生类或子类。继承可以解决代码重用问题。
class C1: pass class C2: pass class C3(C1): #单继承 pass class C4(C1,C2): #多继承 pass print(C3.__bases__) #查看继承的父类 print(C4.__bases__)
python2中,未显示继承object的类及其子类均为经典类,显示继承object的类及其子类均为新式类。
python3中,所有类均为新式类。
2、继承与重用
通过继承的方式创建子类,子类会遗传父类的所有属性(数据属性和函数属性),实现代码重用
1 class Schoolmember: 2 school = "xxx" 3 def __init__(self,name,age,sex): 4 self.name = name 5 self.age = age 6 self.sex = sex 7 def tell_info(self): 8 print("<名字:%s 年龄:%s 性别:%s>"%(self.name,self.age,self.sex)) 9 10 class Student(Schoolmember): 11 def learn(self): 12 print("%s is learning"%self.name) 13 def tell_info(self): 14 print("我是学生:",end="") 15 print("<名字:%s 年龄:%s 性别:%s>" % (self.name, self.age, self.sex)) 16 17 class Teacher(Schoolmember): 18 def teach(self): 19 print("%s is teaching"%self.name) 20 def tell_info(self): 21 print("我是老师:",end="") 22 print("<名字:%s 年龄:%s 性别:%s>" % (self.name, self.age, self.sex)) 23 stu1 = Student("xxx",10,"male") 24 stu1.tell_info() 25 teacher1 = Teacher("aaa",20,"male") 26 teacher1.tell_info() 27 print(id(stu1.school),id(teacher1.school))
初始化对象是,需要调用__init__()方法,子类中没有就会使用父类中的方法。
class Foo: def f1(self): print("Foo.f1") def f2(self): print("Foo.f2") self.f1() class Bar(Foo): def f1(self): print("Bar.f1") b = Bar() b.f2()
对象和Bar类中都没有f2(),则会在父类中查找,使用父类中f2()函数时,需要调用f1(),此时又从b对象开始查找,最后使用Bar类中的f1()函数。
3.派生
子类派生了父类中的tell_info()函数,调用该函数名时,则会使用子类中的函数。
子类中,新建的重名函数属性,在编辑函数内部功能时,可能需要重用父类中重名函数的功能,可以使用调用普通函数的方式,即:类名.func(),此时与调用普通函数无异,此时需要传入self。
class Schoolmember: school = "xxx" def __init__(self,name,age,sex): self.name = name self.age = age self.sex = sex def tell_info(self): print("<名字:%s 年龄:%s 性别:%s>"%(self.name,self.age,self.sex)) class Student(Schoolmember): def __init__(self,name,age,sex,course,stu_id): Schoolmember.__init__(self,name,age,sex) self.course = course self.stu_id = stu_id def learn(self): print("%s is learning"%self.name) def tell_info(self): print("我是学生:",end="") Schoolmember.tell_info(self) class Teacher(Schoolmember): def __init__(self,name,age,sex,level,salary): Schoolmember.__init__(self,name,age,sex) self.level = level self.salary = salary def teach(self): print("%s is teaching"%self.name) def tell_info(self): print("我是老师:",end="") Schoolmember.tell_info(self) stu1 = Student("xxx",10,"male","python",222) teacher1 = Teacher("aaa",20,"male",10,22222) stu1.tell_info() teacher1.tell_info()
4.组合与重用
软件重用的重要方式除了继承外还有组合。组合是指,在一个类中以另外一个类的对象作为数据属性。
class Schoolmember: school = "xxx" def __init__(self,name,age,sex): self.name = name self.age = age self.sex = sex def tell_info(self): print("<名字:%s 年龄:%s 性别:%s>"%(self.name,self.age,self.sex)) class Student(Schoolmember): def __init__(self,name,age,sex,course,stu_id): Schoolmember.__init__(self,name,age,sex) self.course = course self.stu_id = stu_id def learn(self): print("%s is learning"%self.name) def tell_info(self): print("我是学生:",end="") Schoolmember.tell_info(self) class Date: def __init__(self,year,mon,day): self.year = year self.mon = mon self.day = day def tell_birth(self): print("我的生日是:<%s-%s-%s>"%(self.year,self.mon,self.day)) stu1 = Student("xxx",10,"male","python",222) date_boj = Date(1999,1,1) stu1.birth = date_boj #stu1的birth属性是Date类的对象。 stu1.birth.tell_birth()
组合与继承都是有效地利用类的资源的重要方式,但概念和使用场景仍有区别:
a)继承的方式
通过继承建立类派生类和基类的关系,是一种“是”的关系,如狗是动物。当类之间有很多相同的功能时,提取共同功能作为基类,用继承比较合适。
b)组合的方式
用组合的方式建立了类与组合的类之间的关系,是一种“有”的关系,比如学生有生日,老师有课程等。当类之间有显著不同时,且较小的类是较大的类所需要的组件时,用组合比较好。
5.接口和抽象类
接口提取了一群类共同的函数,作为一个函数的集合,然后让子类趋势线接口中的函数。
这样做的好处是实现归一化:
a)归一化让使用者无需关心对象的类是什么,只需要知道对象具备了哪些功能就可以了,极大降低了使用难度。
b)归一化是的高层的外部调用者可以不加区分的处理所有接口兼容的对象集合
python中没有类似java中interface这类定义接口的关键字,需要通过抽象类来实现接口。
抽象类是一个特殊的类,它只能被继承,不能被实例化。抽象类是基于类的抽象。抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法。
import abc class Animal(metaclass=abc.ABCMeta): @abc.abstractclassmethod def eat(self): pass @abc.abstractclassmethod def run(self): pass class People(Animal): def eat(self): #函数名必须和抽象类中相同 print("people eating") def run(self): print("people run") class Pig(Animal): def eat(self): print("pig eating") def run(self): print("pig run") p = People() p.run()
抽象类是一个介于类和接口之间的一个概念,同事具备类和接口的部分特性,可以用来实现归一化设计。
6.继承的实现原理
多继承情况下,如果为非菱形结构,则从左到右,依次找完每一个路径。如果为菱形,则分为深度优先(经典类中)和广度优先(新式类中),python3为广度优先查找。
经典类中,多继承情况下,在要查找的属性不存在是,会按照深度优先的方式查找
新式类中,多继承情况下,在要查找的属性不存在是,会按照广度优先的方式查找。
对于定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表。为了实现继承,python会在MRO列表上从左到右开始查找基类,知道找到第一个匹配这个属性的类为止。相关准则如下:
a)子类会先于父类被检查
b)多个父类会根据他们在列表中的顺序被检查
c)如果对下个类存在两个合肥的选择,选择第一个父类
7.子类中调用父类的方法
第一种为指名道姓方式,即父类名.父类方法(),参考第三节中的例子。
第二种方法,使用super()
class Schoolmember: school = "xxx" def __init__(self,name,age,sex): self.name = name self.age = age self.sex = sex def tell_info(self): print("<名字:%s 年龄:%s 性别:%s>"%(self.name,self.age,self.sex)) class Student(Schoolmember): def __init__(self,name,age,sex,course,stu_id): super(Student,self).__init__(name,age,sex) #python3中super()括号中内容可以不写 self.course = course self.stu_id = stu_id def learn(self): print("%s is learning"%self.name) def tell_info(self): print("我是学生:",end="") super(Student, self).tell_info() stu1 = Student("xxx",10,"male","python",222) stu1.tell_info()
super()会按照mro列表上继续搜索下一个类,而不是又从对象开始。只要每个重定义的方法统一使用super()并只调用它一次,那么控制流程最终会遍历完整个MRO列表,每个方法也只会被调用一次。
class Foo: def f1(self): print("Foo.f1") super().f2() #在Sub的mro列表中,Foo之后是object,找不到f2,会报错。不会从头查找f2. class Bar: def f2(self): print("Bar.f2") class Sub(Bar,Foo): #先查找Bar,Bar中没有在查找Foo pass s = Sub() print(Sub.mro()) s.f1()
二、多态
多态指一类实物有多种形态。如动物的形态有人、狗、猫。
import abc class Animal(metaclass=abc.ABCMeta): @abc.abstractclassmethod def talk(self): pass class People(Animal): def talk(self): print("say hello") class Dog(Animal): def talk(self): print("wangwang") class Cat(Animal): def talk(self): print("miaomiao")
多态是指在不考虑实例类型的情况下使用实例。比如某个动物调用了talk()方法,不管他是什么类型的动物,肯定可以执行这个功能。
people = People() dog = Dog() cat = Cat() #调用talk()时不必考虑对象类型 people.talk() dog.talk() cat.talk() #这样可以统一接口来使用,需要使用某个功能时,直接传入对象,不区分该对象是什么类型。类似len([1,23]) len("abcdefg") def func(obj): obj.talk()
使用多态的好处:
a)增加了程序的灵活性,使用者都是通过同一种形式去调用,func(animal)
b)增加了程序的可扩展性,创建新类是,只需要继承基类,仍然通过原来的调用方式调用func(animal)。
三、封装
1.隐藏属性
在python中使用双下划线的方式隐藏属性,即设置为私有的。这种设置方式,本质上是一种在类定义阶段发生的变形操作。在类定义阶段,将所有双下划线开头的名称如__x都会自动变形为_类名__x的形式,直接从外部调用__x当然无法调用。
需要注意的问题:
a)这种机制没有从真正意义上限制外部直接访问属性,知道类名和属性名可以拼出转换后的属性名,仅仅只是一种语法意义上的变形,外部直接访问_类名__x是可以访问的。
b)变形过程只在类定义时发生,在定义后的赋值操作,不会变形。
c)在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的。
class Foo: __N = 99 def __init__(self,x,y): self.x = x self.__y = y def __f1(self): print("Foo.f1") def f2(self): print(self.__N,self.__y) obj = Foo(1,2) obj.f2() print(obj.__N) #报错,找不到 print(obj._Foo__N) #可以找到变形后的_Foo__N
#父类不想让子类覆盖自己的方法时,将其定义为私有方法,调用私有方法即可 class Foo: def __f1(self): print("Foo.f1") def f2(self): print("Foo.f2") self.__f1() #类定义阶段变形为_Foo__f1(),虽然仍会从头查找该方法,最后还是调用的Foo中的f1 class Bar(Foo): def __f1(self): print("Bar.f1") b = Bar() b.f2()
2.封装不是单纯意义的隐藏
封装数据:将数据隐藏起来不是目的。隐藏起来后对外提供操作该数据的接口,然后可以在接口附加对该数据的操作限制,一次完成对数据属性的操作的严格控制。
class People: def __init__(self,name,age): self.set_info(name,age) def tell_info(self): print("姓名:%s 年龄:%s "%(self.__name,self.__age)) def set_info(self,name,age): if type(name) is not str: raise TypeError("name must be str") #对输入数据格式进行控制 if type(age) is not int: raise TypeError("age must be int") self.__name = name #私有数据属性,无法直接外部访问 self.__age =age p = People("xxx",11) p.tell_info() p.set_info("xxx",44) p.tell_info()
封装方法:目的是隔离复杂度。用户无需知道内部细节,只需要通过外部可调用的简单接口来操作该功能。
class ATM: def __card(self): print("插卡") def _auth(self): print("身份认证") def __input(self): print("输入金额") def __print_bill(self): print("打印账单") def __take_money(self): print("取款") def withdraw(self): self.__card() self._auth() self.__input() self.__print_bill() self.__take_money() atm = ATM() atm.withdraw()
3.特性(property)
property是一种特殊的属性,访问它时会执行一段功能(函数),然后返回功能执行结果作为属性的值
class People: def __init__(self,name,age,height,weight): self.name = name self.age = age self.height = height self.weight = weight @property #将bmi函数变成了类似变量的属性 def bmi(self): return self.weight/(self.height ** 2) a = People("xxx",11,1.8,73) print(a.bmi) #外部调用时类似属性
用property的原因:将一个类的函数定义成特性后,对象再去使用的时候obj.name,根本无法察觉到自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了同一访问的原则。修改和删除特性时需要使用setter和deleter
class People: def __init__(self,name): self.__name = name @property def name(self): return self.__name @name.setter def name(self,obj): if type(obj) is not str: raise TypeError("name must be str") self.__name = obj @name.deleter def name(self): raise PermissionError("无法删除") p = People("xxx") print(p.name) p.name = "aaa" #感觉就像直接操作变量一样,但是在方法中可以添加控制 print(p.name) del p.name
四、绑定方法与非绑定方法
绑定方法:绑定给谁,谁来调用就将自动把它本身当做一个参数传入。
a)绑定到类的方法:用classmethod装饰器的方法
使用类.bound_method(),自动将类当做第一个参数传入。
(对象也可以调用,但仍将类作为第一个参数传入)
b)绑定到对象的方法:没有被任何装饰器装饰的方法
为对象量身定制
使用对象.bound_method(),自动将对象作为第一个参数传入
(属于类的函数,类可以调用,但必须按照函数规则来,不能自动传值)
非绑定方法:用staticmethod装饰器装饰的方法
不与类或对象绑定,类和对象都可以调用,但是没有自动传值,就是普通的函数而已。
HOST='127.0.0.1' PORT=3306
import settings import hashlib import time class MySql: def __init__(self,host,port): self.host = host self.port = port def func(self): print("%s说:你好"%self.name) @classmethod #类绑定方法 def from_conf(cls): return cls(settings.HOST,settings.PORT) #传的类本身,相当于执行了__init__函数 ,返回一个对象 @staticmethod #非绑定方法 def create_id(n): m = hashlib.md5() m.update(str(time.clock()+n).encode("utf-8")) return m.hexdigest() conn = MySql.from_conf() print(conn.host,conn.port) print(MySql.create_id(2)) print(conn.create_id(3)) #对象和类都可以调用,不会自动传入参数
五、面向对象进阶
1.isinstance(obj,cls)和issubclass(sub,super)
isinstance(obj,cls)检查obj是否是类cls的对象
class Foo: pass obj = Foo() print(isinstance(obj,Foo))
issubclass(sub,super)检查sub类是否是super类的派生类
class Foo: pass class Bar(Foo): pass print(issubclass(Bar,Foo))
2.反射
指程序可以访问、检测和修改它本身状态或行为的一种能力。
python面向对象中的反射:通过字符串形式操作对象相关属性。python中的一切事物都是对象,都可以使用反射。通过hasattr、getattr、setattr、delattr四个函数实现。
hasattr(obj,name) 判断obj中有没有一个name字符串对象的方法或属性。
getattr(obj,name,default=None) 获取obj中和字符串name名称相同的方法或属性,没有的话得到None
setarrt(obj,name,value) 设置obj中和字符串name名称相同的方法或属性值为value
delattr(obj,name) 删除obj中和字符串name名称相同的方法或属性
class Schoolmember: school = "xxx" def __init__(self,name,age,sex): self.name = name self.age = age self.sex = sex class Student(Schoolmember): def __init__(self,name,age,sex,course,stu_id): super(Student,self).__init__(name,age,sex) #python3中super()括号中内容可以不写 self.course = course self.stu_id = stu_id stu1 = Student("xxx",10,"male","python",222) #判断是否有和字符串"age"名称相同的属性 print(hasattr(stu1,"age")) #获取age的值 age = getattr(stu1,"age") print(age) #设置属性 setattr(stu1,"age",33) print(stu1.age) #删除属性 delattr(stu1,"age") print(stu1.age) #已被删除会报错
使用反射的好处:
a)实现可插拔机制。实现定义好接口,接口只有在被完成后才会真正执行,实现了即插即用,是一种“后期绑定”,即先把主要逻辑写好(只定义接口),然后后期再去实现接口功能。
# 其他功能均为实现 class FtpClient def __init__(self,addr): print("正在连接服务器%s"%addr) self.addr = addr
#实现约定好需要实现的函数名称和工,不管其是否实现,不影响此处程序的执行 import FtpClient f1 = FtpClient("192.168.11.1") if hasattr(f1,"get"): func_get=getattr(f1,"get") func_get() else: print("不存在此方法")
b) 动态导入模块(基于反射当前模块成员)
import importlib importlib.import_module("package.classname")