python面向对象编程三大特性
小结:前面我们说完了类和对象的关系,相信对类和对象的理解更加透彻..让我们一起去研究面向对象的三大特性吧....
继承
1.什么继承?
在程序中继承就是事物之间的所属关系.
2.继承的语法: 单继承 和多继承
#单继承
class A:pass
class B(A):pass #B类继承了A类 A为父类,B为子类
print(B.__bases__) # __bases__ 查看所有继承的父类
#多继承
class C:pass
class D:pass
class E(C,D):pass # E类继承了C,D两个类,其中C,D 都是父类,E类是子类
A类 : 父类,也称基类/超类.
B类:子类,也成派生类
3.父类和子类的关系
1. 子类可以继承父类的方法和属性
常规继承
class Animal: def __init__(self,name,kind,languge): self.name = name self.kind = kind self.languge = languge def eat(self): print('%s is eattint'%(self.name)) def drink(self): print('%s is drinking'%self.name) def yell(self): print('%s say %s'%(self.name,self.languge)) class Cat(Animal):pass class Dog(Animal):pass small_cat =Cat('small_cat','persian','mews') small_dog =Dog('small_dog','Huskil','yap') small_cat.yell() #Cat类没有__init__方法,继承父类中的属性和方法,在Animal类找到init方法进行执行. small_dog.drink() print(small_dog.name)
分析实例化执行流程: 以Cat类为例
1.首先先开辟一块内存空间,并且内存空间有一个类指针,指向Cat,
2.然后执行init方法,在Cat中并没有init方法,于是找Animal父类中init方法
3.将空间返回给small_cat
2.如果子类也中拥有自己的属性怎么继承呢?
class Animal:
def __init__(self,name,kind,language):
self.name = name
self.kind = kind
self.language = language
def yell(self):
print('%s say %s'%(self.name,self.language))
class Cat(Animal): #子类中拥有自己的方法
def climb(self): # 派生方法
print('%s can climb'%self.name)
class Dog(Animal): # 派生方法
def lookafter_door(self):
print('%s can look after door'%self.name)
small_cat =Cat('small_cat','persian','mews')
small_dog =Dog('small_dog','Huskil','yap')
small_cat.climb() #自己有方法用自己
small_dog.lookafter_door() #同理
animal_1 = Animal('大金鱼','鱼','吐泡泡')
animal_1.climb() # 父类调用子类的方法直接报错
从中分析子类调用方法:1.子类有自己的方法就用自己,用了自己的就不可以用父类的
2.子类中没有方法直接用父类的
3.子类有个性化方法,单独定义在子类中 -->派生方法
4.子类可以调用父类,父类不能调用子类.
3.子类和父类的方法相同又都想用怎么办?
class Animal:
def __init__(self,name,kind,language):
self.name = name
self.kind = kind
self.language = language
def sleep(self):
print('%s 在睡觉'%self.name,)
class Cat(Animal):
def climb(self): # 派生方法
print('%s can climb'%self.name)
def sleep(self):
Animal.sleep(self) # 父类名,主动传self 实现调用父类同名方法
#super().sleep() 当然这种 super(Cat,self).方法名() 也可以
print('团着睡')
small_cat =Cat('small_cat','persian','mews')
small_cat.sleep()
#如果需要既执行父类方法sleep,又要执行Cat类自己方法.达到特定的效果.就需要调用父类了
总结:当某一个方法,父类和子类都拥有的时候,那么在子类的方法中,调用父类的同名方法
1.父类名.方法名(self,...) 2.super().方法名(...)
4.为什么有继承?
几个类之间有重复的属性和方法,把相同的属性和方法都抽象出来,作为父类,子类取继承父类. 为了解决类与类之间属性和方法相同导致的代码重复的问题.
请看看示例代码:
#动物类
class Animal:
def __init__(self,name,hp,ad):
self.name = name
self.hp = hp
self.ad = ad
class People(Animal):
def __init__(self, name, sex, hp, ad):
super().__init__(name,hp,ad) # 使用super方法简化代码
self.sex = sex
def attack(self,giant):
print('%s发起了攻击了%s'%(self.name,giant.name))
giant.hp -= Sheldon.ad
print('%s的HP剩余%s' % (giant.name, giant.hp))
#怪兽类
class Monster(Animal):
def __init__(self,name,kind,hp,ad):
super().__init__(name,hp,ad)
self.kind = kind
def bite(self,Sheldon):
print('%s撕咬了%s'%(self.name,Sheldon.name))
Sheldon.hp -= giant.ad
print('%s的HP剩余%s'%(Sheldon.name,Sheldon.hp))
giant = Monster('giant','big',100,20)
Sheldon = People('sheldon','male',100,10)
Sheldon.attack(giant)
giant.bite(Sheldon)
继续看一个例子放松下:
class Base:
def __init__(self):
self.func()
def func(self):
print('in base')
class Son(Base):
def func(self):
print('in son')
s = Son()
猜猜会输出什么呢? 哈哈还是一起来揭晓吧!
3个步骤解释:
1. 在实例化之前创建了一个对象s的空间,类对象指针指向所在的Son类的空间,鉴于Son类继承Base类,因此Son类指针指向Base类空间,这三个空间通过类指针单向联系,ok这就是实例化前场景. 2. 实例化过程中首先找__init__方法,Son类中没有就找到了父类Base中init方法执行,同时将self返回给 s self.func() = s.func() 3. s.func()就会找距离自己最近的类进行调用方法,输出 in son !希望大家能明白!
首先了解接口和归一化
什么是接口(interface)?
接口(interface)是面向对象编程语言中接口操作的关键字,功能是把所需成员组合起来,用来装封一定功能的集合。它好比一个模板,在其中定义了对象必须实现的成员,通过类或结构来实现它。接口不能直接实例化接口不能包含成员的任何代码,只定义成员本身。接口成员的具体代码由实现接口的类提供。
什么是归一化?
只要是基于在同一个接口实现的类,那么所有继承接口类的子类产生的对象在使用时,方法都相同.
class Payment:pass class AliPay(Payment): def __init__(self,name): self.name = name def pay(self,money): print('%s通过支付宝消费了%s元'%(self.name,money)) class WeChatPay(Payment): def __init__(self,name): self.name = name def pay(self,money): print('%s通过微信消费了%s元'%(self.name,money)) class BankCardPay(Payment): def __init__(self,name): self.name = name def pay(self,money): print('%s通过银行卡消费了%s元'%(self.name,money)) def pay_func(person,payway,money): #使用函数归一化处理 针对 3个类中对象都具备支付的功能 if payway == 'alipay': # 'alipay','wechatpay','bankcardpay' 都属于相同的属性 payway,并要执行同样的方法pay per = AliPay(person) elif payway == 'wechatpay': per = WeChatPay(person) elif payway == 'bankcardpay': per = BankCardPay(person) per.pay(money) #经过函数传参判断,实例化不同的对象执行相同的方法就归一化 pay_func('小明','alipay',1000) pay_func('小红','wechatpay',800) pay_func('小东','bankcardpay',100) # 输出: # 小明通过支付宝消费了1000元 # 小红通过微信消费了800元 # 小东通过银行卡消费了100元
归一化好处: 1.不用在意对象的类是什么,只需要知道所有对象都具备某些功能就好,极大降低了使用难度.
2.归一化使得上层的外部使用者可以不加区分的处理所有接口兼容的对象集合.
抽象类
1.什么是抽象类?
抽象类就是规范类,这个类的存在的意义不在于实际功能,主要就是作为父类/基类来约束子类必须实现同名方法!
- 从开发角度来看,如果类是从现实中抽象而来的,那抽象类就是基于类抽象而来
- 从实现角度来看,抽象类与普通类的区别在于:抽象类只能有抽象方法(没有实现功能),不能被实例化,只能被继承,并且子类必须实现抽象类的方法.
2..抽象类怎么写?
from abc import ABCMeta,abstractclassmethod
class 类名(classmethod = ABCMeta):
@abstractmethod
def play(self):pass
以上代码语法就是这样,固定写法,没什么需要解释的...
3.抽象类有什么特点?
- 必须在定义类的时候指定metaclass = ABCMeta
- 必须在要约束方法上加上@abstractmethod
- 抽象类只能被继承,不能实例化对象了.
from abc import ABCMeta,abstractmethod (抽象方法) class Payment(metaclass=ABCMeta): # metaclass 是元类 metaclass=ABCMeta 表示Payment类是一个规范类 @abstractmethod # abstractmethod 表示它下面的pay方法是一个必须在子类实现的方法. def pay(self):pass #支付方法 class AliPay(Payment): # 子类继承父类 def __init__(self,name): self.name = name def pay(self,money): # AliPay类必须实现的方法 print('%s通过支付宝消费了%s元'%(self.name,money))
he = Payment('name') # 报错 TypeError: Can't instantiate abstract class Payment with abstract methods pay 不能被实例化 she = AliPay('she') she.pay(100) # she通过支付宝消费了100元
4.抽象类多继承可以实现复杂的类与类之间的规范
from abc import ABCMeta ,abstractmethod class Fly_Animal(metaclass=ABCMeta): #规范类1 @abstractmethod def fly(self):print('飞翔') class Swim_Animal(metaclass=ABCMeta): # 规范类2 @abstractmethod def swim(self):pass class Walk_Animal(metaclass=ABCMeta): # 规范类3 @abstractmethod def walk(self):pass class Swan(Fly_Animal,Swim_Animal,Walk_Animal): def fly(self): super().fly() #使用super()既使用父类中的飞的方法,又使用自己的方法! print('飞') def walk(self):print('走') def swim(self):print('游泳') class Tiger(Swim_Animal,Walk_Animal): def walk(self):print('走') def swim(self):print('游') class Parrot(Fly_Animal,Walk_Animal): def fly(self):print('飞') def walk(self):print('走') def talk(self):print('说话') #这样写看起来代码重复比较多,但是不同动物的相同行为却不能混为一谈 #同时不同动物相同行为的方法名是相同的.
抽象类中的方法可以写具体的方法!!!也可以不写
5.什么是多继承?
一个子类继承多个父类,父类中的方法和属性,子类都会继承
*** 但是子类继承多个父类是有顺序的...也就是多继承优先级的问题 ***,由此引入mro的概念
6. MRO
python2中存在两种类
经典类:python2.2之前一直使用的是经典类 ,没有super用法,也不继承object类.
新式类:在2.2之后出现了新式类,特点是基类根为object,也遵循c3算法,super使用必须传参数super(子类名,对象名).方法名
经典类-->深度优先遍历
python3中所有的类只有新式类
如果基类没有存在继承,则默认继承object类
新式类-->广度优先遍历
找了个别人的例子快递员送鸡蛋:
肯定是按照123456顺序来送 即每次都是最左边 找完撤回到分叉口继续往里找(从左往右,一条道跑到黑,然后撤回继续一条道跑到黑) 即深度优先遍历 如果 142356 就是广度优先遍历
现在所有的新式类继承顺序都遵循c3算法,也叫广度优先算法让我们看看吧..
钻石继承问题
钻石模型:
class A: pass def func(self):print('A') class B(A): pass def func(self):print('B') class C(A): pass def func(self):print('C') class D(B,C): pass def func(self):print('D') print(D.__mro__) c3算法提取: 每一个类继承顺序都是从基类向子类看, 形成指向关系的顺序,[当前类]+[父类的继承顺序]进行一个提取 如果一个类出现在从左往右的第一个顺序上,并且没有出现在后面的顺序中,或者出现在后面的顺序中了但是仍然是第一个,仍然提取第一个
如果从左往右的第一个序列的类出现在后面序列中, L(A) = [A] +[O] L(A) = AO L(B) = [B] + [AO] L(B) = BAO L(C) = [C] + [AO] L(C) = CAO L(D) = [D] + [BAO] +[CAO] D = [BAO] + [CAO] # 都存在A的情况.提取顺序先B类 看见A类两边序列都存在,跳过左边序列,取右边序列C类,然后再取A类O类 DB = [AO] + [CAO] DBC = [AO] + [AO] DBCA = [O] + [O] L(D) = DBCAO
当然不想算的话可以使用类名.__mro__()这个方法查看这个类的继承顺序.因为它也是通过c3算法算出来的
print(D.__mro__) 输出: (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
再看看 乌龟模型
#乌龟模型 class A: pass def func(self):print('A') class B(A): pass def func(self):print('B') class C(A): pass def func(self):print('C') class D(B): pass def func(self):print('D') class E(C): pass def func(self):print('E') class F(D,E): pass def func(self):print('F') d = D() d.func() c3算法: # 乌龟模型问题 # L(A) = [A] + [O] # L[A] = AO # 单纯的A类的继承顺序 # L(B) = [B] + [AO] # L(B) = BAO # L(C) = [C] + [AO] # L(C) = CAO # L(D) = [D] + [BAO] # L(D) = DBAO # L(E) = [E] + [CAO] # L(E) = ECAO # L(F) = [F] + [DBAO] + [ECAO] # L(F) = FDBECAO
print(F.__mro__) # (<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
相信你已经掌握了对象的规律....
7.super在多继承中的意义
super()可以执行 mro中的下一个父类的方法!!!
通常super有两个使用的地方:
1.可以访问父类的构造方法.
2.当子类方法想调用父类(mro)中的方法
第一种 class People: def __init__(self,name,sex,hobby): self.name = name self.sex = sex self.hobby = hobby class Star(People): def __init__(self,name,sex,hobby,age): super().__init__(name,sex,hobby) self.age = age star = Star('刘亦菲','girl','sing',18)
这里用super省去了子类中的重复代码
第二种
class A: def func(self):print('A') class B(A): def func(self): super().func() print('B') class C(A): def func(self): super().func() print('C') class D(B,C): def func(self): super().func() print('D') b = B() b.func() # AB 这个相对简单,对象在自己类里面调用func()时遇到super 直接找父类 进行调用,输出A,然后返回自己类中再输出B
d = D() d.func() ACBD # 这种钻石模型 构造根据c3算法取得顺序为DBCA, DBCA可以理解为从左往右 子类-->父类 从而做到打印时 父类-->子类的效果 ACBD
总结:super()不管写在哪里(除了写在最顶层父类中会找不到),在哪里执行,一定找到mro列表也就是得到C3算法顺序,再根据对应顺序往下找,否则都是错的!!!
多态
首先必须声明python中处处是多态,是一门自带多态的语言,但是所有面向对象语言中的多态表现形式并不尽相同
那就通过java来举例介绍下
java 多态概念:一个类的多种形态
def pay(float money): #跟python的不同java是强数据类型语言,在参数或数据前面必须添加具体的数据类型float print(money) pay() class Payment(object):pass class Alipay(Payment): def pay(self): pass class Wechatpay(Payment): def pay(self): pass def pay(Payment person_obj,float money): person_obj.pay(money) Sheldon = Alipay() pay(Sheldon,24.5) Penny = Wechatpay() pay(Penny,23.4)
Payment是一个类,他的多种状态:使用阿里支付Alipay,微信支付Wechatpay
支付表现出来的是多种状态:第一种状态:支付宝支付,第二种是微信支付
上述代码可以看出java就是通过继承来实现多态.,它的多态概念就是:一个类表现出来的多种状态.
再看看python的多态表现
class Alipay(object): def pay(self,money): pass class Wechatpay(object): def pay(self,money): pass def pay(person_obj,money): person_obj.pay(money) Sheldon = Alipay() pay(Sheldon,24.5) Penny = Wechatpay() pay(Penny,23.5)
并没有发生继承,只是几个类拥有同名的方法,可以写成一个函数,统一调用-归一化设计
总结来说:只管调用,不管细节,不管原来的代码是如何调用的!
java为什么要求传递数据的数据类型?(通过继承实现)
是为了代码不会报错,只要能够顺利调用这个函数,那么内部执行就大大降低出错的概率
python怎么就不要求呢?
更多来说不是硬性的规定,但是可能因为传递参数不符合规则而使代码报错,从而大大降低了代码繁琐程度!
什么是鸭子类型?
Duck typing 这个概念来源于美国印第安纳州的诗人詹姆斯·惠特科姆·莱利(James Whitcomb Riley,1849- 1916)的诗句: ”When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.”
是python语言特有的,不依赖于继承和规范进行的,如果两个类都需要执行同样的方法,应该起相同的名字,对于执行方法来说,这两个类就是鸭子类! 也就是归一化的表现
来看看示例代码吧:
class Duck(): def walk(self): print('I walk like a duck') def swim(self): print('i swim like a duck') class Person(): def walk(self): print('this one walk like a duck') def swim(self): print('this man swim like a duck')
def walk(animal):
animal.walk()
duck = Duck()
people = People()
walk(duck) # 输出 I walk like a duck
walk(people) #输出 this one walk like a duck
总结: 我们看上面这两个类,Duck类,Person类,他们都具有相同的方法, 当一个同名函数walk()调用Duck类,利用到walk()方法,我们传入Person类也可以运行,函数才不会检查对象的类型是不是duck,只要他拥有walk()方法就会被调用.
封装
老套路先提出一个质疑,什么是封装?
在程序设计中,封装(Encapsulation)是对具体对象的一种抽象,即将某些部分隐藏起来,在程序外部看不到,其含义是其他程序无法调用。要了解封装,离不开“私有化”,就是将类或者是函数中的某些属性限制在某个区域之内,外部无法调用。
那要封装在面向对象的概念什么呢?
--广义上(大家认为的):
把一类事物的相同行为和属性归到一个类中
class Person: def think(self):pass #人类都有思考的方法
--狭义上(学术上的定论):
把一些特殊的方法和属性藏在类中,外部无法调用,只有内部可以调用
为什么要封装呢?
1.封装类属性/静态属性 保护隐私,-->减少别人直接看到你不想让别人看到你比较隐私的东西,就像女生穿什么颜色内衣...你能看到吗??
2.封装方法和属性:隔离复杂程度,-->例如全自动洗衣机,你只需要把所有衣服扔进去,按一键启动后就等它自动洗完就好了,一键启动方法将洗衣机复杂漂洗刷干方式都隐藏起来
你不必知道它怎么做到的,这个一键启动可以比喻为一个接口,,你对它操作后就实现了洗衣机的功能.
友情提示:在编程语言中,对外提供的接口,(可理解为一个入口),就是函数,成为接口函数,这与接口的概念还有些区别,接口代表一组函数的集合体.
封装的层面
引子:封装其实分为两个层面,无论哪个层面的封装,都要对外界留下访问你所隐藏内容的接口,(也可以理解为一个入口,有了这个入口访问者无需且不能直接访问内部隐藏的细节,只能通过接口,我们在这个接口上可以做很多逻辑,严格控制使用者访问.
第一个层面的封装-->
创建类和对象会分别创建二者的名称空间,我们只能用类名.或者obj.的方式去访问里面的名字,这本身就是一种封装
class Person: Think_time = 0 def __init__(self): self.count() def count(self): Person.Think_time += 1 Shledon = Person() # 内部调用 在实例化后self.count()赋值给alex对象后执行调用,静态属性执行+1操作 print(Person.Think_time) # 通过类名调用类/静态属性 print(Shledon.Think_time) # 通过对象obj调用类属性
第二层面的封装:-->
把类中的某些属性和方法隐藏起来,也叫定义为私有化,只能在类的内部使用,外部无法调用访问,或者留下少量的接口函数供外部访问
- 隐藏静态属性
示例代码1:
,且外部无法调用访问 class Person: # Think_time = 0 # 不安全,因为这个属性可以在类的外部随意修改,所以我们不用 __Think_time = 0 # 安全,通过Think_time方法和__变量名让属性变成只能看不能改的值-->就是藏起来 哈哈 def __init__(self): self.count() def count(self): Person.__Think_time += 1 # 可以从一个类的内部使用__Think_time的属性 def dog_sum(self): return Person.__Think_time print(Person.__dict__) # 查看静态属性隐藏后的名字 Shledon = Person() print(Person.__Think_time) # 隐藏后通过类都查不到了哦!!哈哈 print(Person._Person__Think_time) # 自己偷偷看隐藏后的名字,由于静态属性可以更改,但是自己别改哦!! print(Shledon.Think_time()) Shledon.__Think_time # 对象直接查看静态属性报错了就,因为它的名字被隐藏啦,所以查不到!
示例代码2:
class Person: __Think_time = 0 # 私有的静态属性变成 _Person__Think_time def __init__(self): self.count() def count(self): return Person.__Think_time #只要是在类内部使用__名字,默认被修改为 _类名__名字 Sheldon = Person() print(Person.__dict__) print(Sheldon.count()) # 外部接口访问,(函数入口) print(Person._Person__Think_time) # 警告!!!绝对不能在后续我们的代码中出现这种情况
小结:
1.只要是在类的内部的名字前面加上双下划线,这个名字就变成私有的了,(只能在内部使用,外部就无法使用了)
2.在定义的时候,存储名字会发生变化,变成 ''_类名__名字'' ,所以我们在外部再调取原来的名字肯定钓不到呀
3.在类的内部使用__名字,默认被修改为 _类名__名字
- 隐藏类属性和对象属性/方法
示例代码1
class Person: __Think_time = 0 def __init__(self): self.__count() def __count(self): # __count已经被内部改变了只能看,不能改的值 Person.__Think_time += 1 def think_time(self): return Person.__Think_time Sheldon = Person() print(Sheldon.think_time()) # 1 外部访问通过接口函数 Penny = Person() print(Penny.think_time()) # 2 print(Person.__dict__)
示例代码2
class Person: def __init__(self,name,password): self.__name = name self.__password = password #针对对象属性进行隐藏 def login(self,usr,pwd): if usr == self.__name and pwd == self.__password: return True def name(self): return self.__name def set_name(self,new_name): if type(new_name) is str: self.__name = new_name Sheldon = Person('Sheldon','123') print(Sheldon.login('Sheldon','111')) # None print(Sheldon.login('Sheldon','123')) # True print(Sheldon.name()) # Sheldon Sheldon.set_name('Penny') # 调用set_name 方法后 默认参数传参后执行判断后赋值给self.__name,意味着在函数内部对对象属性进行更改 print(Sheldon.name()) # Penny 打印出来就是重新赋值的对象属性__name
总结:
1.只能在类的内部被调用,保证了内部数据的安全,不会被别人随意修改
2.私有属性类/静态属性:为了不被外部随意修改
3.私有方法:不希望被外部随意调用
4.私有的对象属性:不希望某个值被修改,且不希望某个值被从类的外部看到
封装中子类能否继承父类中私有化方法?
我觉得不能,父类总得有点隐私吧....哈哈
首先我们看两个示例代码:
示例1 class Foo(object): def __func(self): # 定义为私有方法,内部变形为 _Foo__func print('in foo') class Son(Foo): def func1(self): self.__func() # 定义为私有方法,内部变形为 _Son__func s = Son() #实例化 s.func1() # 对象s 调其方法func1后执行调用self.__func(),发现Son类并没有对象属性_Son__func # AttributeError: 'Son' object has no attribute '_Son__func'
示例2
class Foo(object): A = 123 __A = 'ABC' # _Foo__A print('---> ',__A) # 输出ABC 在类的内部使用 _Foo__A class Son(Foo): def func(self): print('--> 1 ',Son.A) # 子类没有找父类,在父类中找到A的静态属性 print('--> 2 ',Foo.__A) # 子类没有找父类,找不到"_Son__A 报错 print(Foo.__A) #由于 __A在内部已经变形,所以通过类查看静态属性查不到,报错 Son().func() 可以看出, __这个变量出现在哪个类中,就会在变形的时候 加上这个类的类名,因此在子类中使用私有的名字,会加上子类的名字
再看这个 示例3
class Foo(object): def __init__(self): self.__func() # 定义的时候内部已变形为 _Foo__func def __func(self): #定义的时候内部已变形为 _Foo__func因此能够调到 print('in Foo __func') #因此只打印 in Foo __func def func(self): print('in Foo func') class Son(Foo): def __func(self): # 定义的时候内部已变形为 _Soo__func,所以无法调到 print('in Son __func') def func(self): print('in Son func') Son()
总结:通过这两个例子发现在封装中父类私有化的情况下,子类无法继承父类私有化属性.
.....