面向对象编程(2)
一 面向对象特性之继承
1.1 基本继承语法
面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一是通过继承机制。
通过继承创建的新类称为子类或派生类,被继承的类称为基类、父类或超类。
继承语法
class 派生类名(基类名) ...
# 无继承方式 # class Dog: # def run(self): # print("running...") # # def sleep(self): # print("sleep...") # # def toshetou(self): # print("toshetou...") # # class Cat: # def run(self): # print("running...") # # def sleep(self): # print("sleep...") # # def climb_tree(self): # print("climb_tree...") # 继承方式 class Animal: def run(self): print("running...") def sleep(self): print("sleep...") class Dog(Animal): def toshetou(self): print("toshetou...") class Cat(Animal): def climb_tree(self): print("climb_tree...") alex=Dog() alex.run()
这里一定一定要注意查找变量的顺序!
面试题:
class Base: def __init__(self): self.func() def func(self): print('in base') class Son(Base): def func(self): print('in son') s = Son()
1.2 多重继承
如果在继承元组中列了一个以上的类,那么它就被称作"多重继承" 。
语法:
派生类的声明,与他们的父类类似,继承的基类列表跟在类名之后,如下所示:
class SubClassName (ParentClass1[, ParentClass2, ...]): ...
多继承有什么意义呢?还拿上面的例子来说,蝙蝠和鹰都可以飞,飞的功能就重复定义了。
class Animal: def run(self): print("running...") def sleep(self): print("sleep...") class Dog(Animal): def toshetou(self): print("toshetou...") class Cat(Animal): def climb_tree(self): print("climb_tree...") ##########################################
class Eagle(Animal): def fly(self): print("fly...") class Bat(Animal): def fly(self): print("fly...")
有同学肯定想那就放到父类Animal中,可是那样的话其他不会飞的动物还怎么继承Animal呢?
所以,这时候多重继承就发挥功能了:
class Fly: def fly(self): print("fly...") class Eagle(Animal,Fly): pass class Bat(Animal,Fly): pass
是不是很棒呢
补充
说到多重继承,就不得不提一下c3算法了。mro即 method resolution order (方法解释顺序),主要用于在多继承时判断属性的路径(来自于哪个类)。
在python2.2版本中,算法基本思想是根据每个祖先类的继承结构,编译出一张列表,包括搜索到的类,按策略删除重复的。但是,在维护单调性方面失败过(顺序保存),所以从2.3版本,采用了新算法C3。
为什么采用C3算法
C3算法最早被提出是用于Lisp的,应用在Python中是为了解决原来基于深度优先搜索算法不满足本地优先级,和单调性的问题。
- 本地优先级:指声明时父类的顺序,比如C(A,B),如果访问C类对象属性时,应该根据声明顺序,优先查找A类,然后再查找B类。
- 单调性:如果在C的解析顺序中,A排在B的前面,那么在C的所有子类里,也必须满足这个顺序。
二 面向对象特性之封装
封装:是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。
好处:将变化隔离、便于使用、提高重用性、提高安全性
封装原则:将不需要对外提供的内容都隐藏起来、把属性都隐藏,提供公共方法对其访问。
使用封装有三大好处:
1、良好的封装能够减少耦合。
2、类内部的结构可以自由修改。
3、可以对成员进行更精确的控制。
4、隐藏信息,实现细节。
2.1 私有化变量
在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。
但是,从前面Student类的定义来看,外部代码还是可以自由地修改一个实例的name
、score
属性:
class Student(object): def __init__(self, name, score): self.name = name self.age = score alex=Student("alex",12) yuan=Student("yuan",34) alex.age=1000 print(alex.age)
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__
,在Python中,实例的变量名如果以__
开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,所以,我们把Student类改一改:
class Student(object): def __init__(self, name, score): self.__name = name self.__age = score alex=Student("alex",12) yuan=Student("yuan",34) print(alex.__age)
改完后,对于外部代码来说,没什么变动,但是已经无法从外部访问实例变量.__name
和实例变量.__score
了。
这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。
但是如果外部代码要获取name和score怎么办?可以给Student类增加get_name
和get_score
这样的方法:
class Student(object): def __init__(self, name, score): self.__name = name self.__age = score def get_name(self): return self.__name def get_age(self): return self.__age alex=Student("alex",12) yuan=Student("yuan",34) print(alex.get_name()) print(alex.get_age())
如果又要允许外部代码修改age怎么办?可以再给Student类增加set_age
方法:
class Student(object): def __init__(self, name, score): self.__name = name self.__age = score def get_name(self): return self.__name def get_age(self): return self.__age def set_age(self,age): self.__age=age alex=Student("alex",12) print(alex.get_age()) alex.set_age(1000) print(alex.get_age())
你也许会问,原先那种直接通过bart.score = 99
也可以修改啊,为什么要定义一个方法大费周折?因为在方法中,可以对参数做检查,避免传入无效的参数:
class Student(object): ... def set_age(self,age): if isinstance(age,int) and 0 <= age <= 100: self.__age = age else: raise ValueError('bad age!')
需要注意的是,在Python中,变量名类似__xxx__
的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__
、__score__
这样的变量名。
注意:
1.这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N
2.变形的过程只在类的内部生效,在定义后的赋值操作,不会变形
3.单下划线、双下划线、头尾双下划线说明:
- __foo__: 定义的是特殊方法,一般是系统定义名字 ,类似 __init__() 之类的。
- _foo: 以单下划线开头的表示的是 protected 类型的变量,即保护类型只能允许其本身与子类进行访问。(约定成俗,不限语法)
- __foo: 双下划线的表示的是私有类型(private)的变量, 只能是允许这个类本身进行访问了。
2.2 私有化方法
在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
#正常情况 >>> class A: ... def fa(self): ... print('from A') ... def test(self): ... self.fa() ... >>> class B(A): ... def fa(self): ... print('from B') ... >>> b=B() >>> b.test() #把fa定义成私有的,即__fa >>> class A: ... def __fa(self): #在定义时就变形为_A__fa ... print('from A') ... def test(self): ... self.__fa() #只会与自己所在的类为准,即调用_A__fa ... >>> class B(A): ... def __fa(self): ... print('from B') ... >>> b=B() >>> b.test()
2.3 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 ##################################### class People: def __init__(self,name,weight,height): self.name=name self.weight=weight self.height=height @property def bmi(self): return self.weight / (self.height**2) p1=People('egon',75,1.85) print(p1.bmi)
import math class Circle: def __init__(self,radius): #圆的半径radius self.radius=radius @property def area(self): return math.pi * self.radius**2 #计算面积 @property def perimeter(self): return 2*math.pi*self.radius #计算周长 c=Circle(10) print(c.radius) print(c.area) #可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值 print(c.perimeter) #同上 ''' 输出结果: 314.1592653589793 62.83185307179586 ''' ################################################# #注意:此时的特性area和perimeter不能被赋值 c.area=3 #为特性area赋值 ''' 抛出异常: AttributeError: can't set attribute '''
为什么要用property
将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则在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'
一个静态属性property本质就是实现了get,set,delete三种方法
class Foo: @property def AAA(self): print('get的时候运行我啊') @AAA.setter def AAA(self,value): print('set的时候运行我啊') @AAA.deleter def AAA(self): print('delete的时候运行我啊') #只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter f1=Foo() f1.AAA f1.AAA='aaa' del f1.AAA
#################################################
class Foo: def get_AAA(self): print('get的时候运行我啊') def set_AAA(self,value): print('set的时候运行我啊') def delete_AAA(self): print('delete的时候运行我啊') AAA=property(get_AAA,set_AAA,delete_AAA) #内置property三个参数与get,set,delete一一对应 f1=Foo() f1.AAA f1.AAA='aaa' del f1.AAA
怎么用?
class Goods: def __init__(self): # 原价 self.__original_price = 100 # 折扣 self.__discount = 0.8 @property def price(self): # 实际价格 = 原价 * 折扣 new_price = self.__original_price * self.__discount return new_price @price.setter def price(self, value): self.__original_price = value @price.deleter def price(self): del self.__original_price obj = Goods() print(obj.price) # 获取商品价格 obj.price = 200 # 修改商品原价 print(obj.price) del obj.price # 删除商品原价
2.4 classmethod方法与staticmethod方法
classmethod
class Classmethod_Demo(): role = 'dog' @classmethod def func(cls): print(cls.role) Classmethod_Demo.func()
staticmethod
class Staticmethod_Demo(): role = 'dog' @staticmethod def func(): print("当普通方法用") Staticmethod_Demo.func()
练习:
class Foo: def func(self): print('in father') class Son(Foo): def func(self): print('in son') s = Son() s.func() # 请说出上面一段代码的输出并解释原因?
class A: __role = 'CHINA' @classmethod def show_role(cls): print(cls.__role) @staticmethod def get_role(): return A.__role @property def role(self): return self.__role a = A() print(a.role) print(a.get_role()) a.show_role() # __role在类中有哪些身份? # 以上代码分别输出哪些内容? # 这三个装饰器分别起了什么作用?有哪些区别?
三 面向对象特性之多态
3.1 归一化设计
预备知识
l=[123,456,789] info={"name":"alex","age":1000} s="hello" print(len(l)) print(len(info)) print(len(s))
支付接口归一化
##################### 归一化设计 ##################### # 支付宝 微信 银行卡 nfc支付 class AliPay(object): def __init__(self,name,money): self.money=money self.name=name def pay(self): # 支付宝提供了一个网络上的联系渠道 print('%s通过支付宝消费了%s元'%(self.name,self.money)) class WeChatPay(object): def __init__(self,name,money): self.money=money self.name=name def pay(self): # 微信提供了一个网络上的联系渠道 print('%s通过微信消费了%s元'%(self.name,self.money)) def pay_func(pay_obj): pay_obj.pay() alipay=AliPay("alex",100) wechatpay=WeChatPay("yuan",200) pay_func(alipay) pay_func(wechatpay)
3.2 规范化方法
''' 规范化方法 支付宝 微信 银行卡 nfc支付 同事协作之间的代码规范问题 规定:Payment 就是一个规范类,这个类存在的意义不在于实现实际的功能,而是为了约束所有的子类必须实现pay的方法 Payment : 抽象类 pay = Payment() # 抽象类: 不能实例化 抽象类主要就是作为基类/父类,来约束子类中必须实现的某些方法 抽象类的特点: 必须在类定义的时候指定metaclass = ABCMeta 必须在要约束的方法上方加上@abstractmethod方法 ''' from abc import ABCMeta,abstractmethod #(抽象方法) class Payment(metaclass=ABCMeta): # metaclass 元类 metaclass = ABCMeta表示Payment类是一个规范类 def __init__(self,name,money): self.money=money self.name=name @abstractmethod # @abstractmethod表示下面一行中的pay方法是一个必须在子类中实现的方法 def pay(self,*args,**kwargs): pass @abstractmethod def back(self): pass class AliPay(Payment): def pay(self): # 支付宝提供了一个网络上的联系渠道 print('%s通过支付宝消费了%s元'%(self.name,self.money)) class WeChatPay(Payment): def pay(self): # 微信提供了一个网络上的联系渠道 print('%s通过微信消费了%s元'%(self.name,self.money)) def pay_func(pay_obj): pay_obj.pay() alipay=AliPay("alex",100) wechatpay=WeChatPay("yuan",200) pay_func(alipay) pay_func(wechatpay)
当子类和父类都存在相同的pay()
方法时,我们说,子类的pay()
覆盖了父类的pay()
,在代码运行的时候,总是会调用子类的pay()
。这样,我们就获得了继承的另一个好处:多态。
3.3 多态的概念
要理解什么是多态,我们首先要对数据类型再作一点说明。当我们定义一个class的时候,我们实际上就定义了一种数据类型。我们定义的数据类型和Python自带的数据类型,比如str、list、dict没什么两样:
a = list() # a是list类型 b = Animal() # b是Animal类型 c = Dog() # c是Dog类型
判断一个变量是否是某个类型可以用isinstance()
判断:
>>> isinstance(a, list) True >>> isinstance(b, Animal) True >>> isinstance(c, Dog) True
看来a
、b
、c
确实对应着list
、Animal
、Dog
这3种类型。
但是等等,试试:
>>> isinstance(c, Animal) True
看来c
不仅仅是Dog
,c
还是Animal
!
不过仔细想想,这是有道理的,因为Dog
是从Animal
继承下来的,当我们创建了一个Dog
的实例c
时,我们认为c
的数据类型是Dog
没错,但c
同时也是Animal
也没错,Dog
本来就是Animal
的一种!
所以,在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是,反过来就不行:
>>> b = Animal() >>> isinstance(b, Dog) False
Dog
可以看成Animal
,但Animal
不可以看成Dog
。
所以,上面的支付的例子,如果我们再定义一个ApplePay类型,也从Payment类派生:
class ApplePay(Payment): def pay(self): print('%s通过苹果支付消费了%s元'%(self.name,self.money)) applepay=ApplePay("egon",800)
''' 你会发现,新增一个Payment的子类,不必对pay()做任何修改,实际上,任何依赖Payment作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。 多态的好处就是,当我们需要传入AliPay、WeChatPay、ApplePay……时,我们只需要接收Payment类型就可以了,因为AliPay、WeChatPay、ApplePay……都
是Payment类型,然后,按照Payment类型进行操作即可。由于Payment类型有pay()方法,因此,传入的任意类型,只要是Payment类或者子类,就会自动调用实
际类型的pay()方法,这就是多态的意思: 对于一个变量,我们只需要知道它是Payment类型,无需确切地知道它的子类型,就可以放心地调用pay()方法,而具体调用的pay()方法是作用在AliPay、WeChatPay、
ApplePay哪个类对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Payment的子类时,只要确保pay()方
法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则: 对扩展开放:允许新增Payment子类; 对修改封闭:不需要修改依赖Payment类型的pay()等函数。 '''
3.4 鸭子类型
class CardPay(object): def __init__(self,name,money): self.money=money self.name=name def pay(self): print('%s通过银联卡支付消费了%s元'%(self.name,self.money)) def pay_func(pay_obj): pay_obj.pay() cp=CardPay("alvin",1000) pay_func(cp)
对于静态语言(例如Java)来说,如果需要传入Payment
类型,则传入的对象必须是Payment
类型或者它的子类,否则,将无法调用pay()
方法。
对于Python这样的动态语言来说,则不一定需要传入Animal
类型。我们只需要保证传入的对象有一个pay()
方法就可以了:
这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
四 反射
反射函数
getattr(obj, name[, default]) : 访问对象的属性。 hasattr(obj,name) : 检查是否存在一个属性。 setattr(obj,name,value) : 设置一个属性。如果属性不存在,会创建一个新属性。 delattr(obj, name) : 删除属性。
当操作属性是一个字符串时,不能在通过句点符进行操作,这时候,反射就可以发挥功能了!
class Animal(object): role="person" def __init__(self,name,age): self.name=name self.age=age def run(self): print("%s is running"%self.name) # alex=Animal("alex",34) # print(alex.name) # print(alex.age) # alex.run() ######################################## alex=Animal("alex",34) while 1: attr=input("查询alex的什么属性>>>") if hasattr(alex,attr): print(getattr(alex,attr)) else: print("alex没有该属性") # 当然类对象也可以反射 print(getattr(Animal,"role")) # 模块也可以反射 from sys import modules print(modules[__name__]) print(getattr(modules[__name__],"Animal"))
反射应用
class FTP(object): def __init__(self): self.run() def run(self): print(''' 提示: 上传: put 路径/文件名称 下载: get 路径/文件名称 ''' ) while 1: input_str=input(">>>") action,params=input_str.split(" ") if hasattr(self,action): getattr(self,action)() else: print("不存在该方法") def put(self): print("上传...") def get(self): print("下载...") ftp=FTP()
五 类的魔法方法
# 1 初始化方法:__init__ class A(object): def __init__(self): print("初始化执行方法") A() # 2 构造方法:__new__ class B(object): def __new__(cls,*args,**kwargs): print("我是用来开辟一块空间的") obj=super().__new__(cls) return obj def __init__(self): print("self就是__new__开辟的空间地址") B() # 应用:比如单例模式 # 3 __str__ class C(object): def __init__(self,name,age): self.name=name self.age=age def __str__(self): # 必须返回字符串类型 return self.name c1=C("c1",20) c2=C("c2",23) print(c1) print(c2) # 4 __call__ class D(object): def __call__(self, *args, **kwargs): print("call 被调用...") print(callable(D)) d1=D() print(callable(d1)) d1() # 5 析构方法 __del__ class F(object): def __del__(self): print("删除对象时被调用!") f=F() # del f # 思考:注释掉为什么也会调用__del__ # import time # time.sleep(100) # 应用 class Filehandler(object): file="a.text" def __init__(self): self.f=open(self.file) def __del__(self): self.f.close() # 6 __getitem__ class G(object): def __init__(self): pass def __getitem__(self,item): print("__getitem__被调用") def __setitem__(self, key, value): print("__setitem__被调用") def __delitem__(self, key): print("__delitem__被调用") g=G() g["name"]="alex" print(g["name"]) del g["name"] # 7 __getattr__ class H(object): def __init__(self): pass def __getattr__(self, item): print("__getattr__被调用") def __setattr__(self, key, value): print("__setattr__被调用") def __delattr__(self, item): print("__delattr__被调用") h=H() h.name="alex" print(h.name) del h.name # 8 __eq__ class I(object): def __init__(self,name,age): self.name=name self.age=age def __eq__(self, other): if self.name==other.name and self.age==other.age: return True else: return False i1=I("alex",30) i2=I("alex",30) print(i1==i2) # __len__ class G(object): def __len__(self): return 100 g=G() print(len(g))
六 面向对象作业