python 之面向对象(二)
面向对象的三大特征
继承:
多态:
封装:
什么是继承:
继承是创建新类的一种方式,新建的类可以继承一个或者多个父类,父类又可以称为基态,新建的类
称为子类或者派生类。
继承分为单继承和多继承
class A: pass class B: pass class C(A): def c(self): print("C 继承 A") class D(A,B): def d(self): print("D 继承 A和B") #查看继承的类 print(C.__bases__)#(<class '__main__.A'>,) print(D.__bases__)#(<class '__main__.A'>, <class '__main__.B'>) #查看继承顺序 print(C.mro())#[<class '__main__.C'>, <class '__main__.A'>, <class 'object'>] print(D.mro())#[<class '__main__.D'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
提示:如果没有基类,python的类会默认继承object类,object是所有python类的基类。
继承与抽象(先抽象再继承)
抽象就是抽取相似的部分,是一个从小范围到大范围的过程
抽象最主要的作用是划分类别
继承:是抽象的结果,用编程语言实现,必须先经历抽象这个过程,才能通过继承的方式表达抽象的结构。抽象是分析和设计的一个过程,通过抽象可以得到类。
继承与代码重用
class Animal: '''定义一个父类''' def __init__(self,name,aggr,life_value):#提取游戏中人物和狗的共性 self.name = name self.aggr = aggr self.life_value = life_value def eat(self): print("%s is eating"%self.name) class Dog(Animal):pass class Person(Animal):pass egg = Person("egon",100,20) dog = Dog("旺财",199,20) egg.eat()#egon is eating dog.eat()#旺财 is eating
派生
子类添加新的方法就叫做派生(派生不会影响到父类)
class Animal: #父类 基类 超类 def __init__(self,name,life_value,aggr): self.name = name self.life_value = life_value self.aggr = aggr #攻击力 def eat(self): print("%s is eating"%self.name) class Dog(Animal): def bite(self,enemy):#狗派生了咬的方法 enemy.life_value -= self.aggr class Person(Animal): def attack(self,dog):#人派生了攻击的方法 dog.life_value = self.aggr egg = Person("egon",1999,250) dog = Dog("旺财",99898,1000) #狗袭击了人,人的生命值减少了1000 dog.bite(egg) print(egg.life_value)#999
子类要用父类的属性,可以用supper()方法
class Animal: def __init__(self,name,life_value,aggr): self.name = name self.life_value = life_value self.aggr = aggr def eat(self): print("%s is eating"%self.name) class Dog(Animal): def __init__(self,bread,name,life_value,aggr): #Animal.__init__(self,name,life_value,aggr)子类中要使用父类的属性要用supper或者父类名.__init__(属性名) super().__init__(name,life_value,aggr)#注意父类有多少个属性就得传多少个属性 self.bread = bread def bite(self,enemy): enemy.life_value -= self.aggr class Person(Animal): def __init__(self,sex,name,life_value,aggr): #Animal.__init__(self,name,life_value,aggr) super().__init__(name,life_value,aggr) def attack(self,dog): dog.life_value = self.aggr egg = Person("male","egon",1999,1000) dog = Dog("哈士奇","旺财",99898,1000) egg.eat()
抽象类与接口类
接口类
继承有两种用途
继承基类的方法,做出自己的扩展或者改变
子类实现父类接口函数的功能
class Alipay: def pay(self,money): print("支付了%s元"%money) class Aapplepay: def pay(self,money): print("支付了%s"%money) def pay(payment,money): payment.pay(money) p = Aapplepay() pay(p,200)
开发中常犯的错误
class Alipay: def pay(self,money): print("支付了%s元"%money) class Aapplepay: def pay(self,money): print("支付了%s"%money) class Wechatpay: def fuqian(self,money): print("支付了%s"%money) def pay(payment,money): payment.pay(money) p = Wechatpay() pay(p,200)#AttributeError: 'Wechatpay' object has no attribute 'pay'
使用abc来实现接口类继承(限制和提示的功能,检测报错)
约束继承接口类的子类必须实现被abstractmethod装饰的方法
from abc import ABCMeta,abstractmethod class Payment(metaclass=ABCMeta): @abstractmethod def pay(self,money):pass class Alipay(Payment): def pay(self,money): print("支付了%s元"%money) class Aapplepay(Payment): def pay(self,money): print("支付了%s"%money) class Wechatpay(Payment): def fuqian(self,money): print("支付了%s"%money) p = Wechatpay() # TypeError: Can't instantiate abstract class Wechatpay with abstract methods pay
接口类继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节是什么,可一视同仁的处理实现了特定接口的所有对象”---》这个在程序设计上叫做归一化。
抽象类
同Java一样,python中也有抽象类的概念,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化
为什么要有抽象类
类是从一堆对象中抽取相同的内容,抽象类是从一堆类里面抽出相同的内容而来,包括数据属性和函数属性。抽象类中只有抽象方法(没有实现功能)该类不能被实例化,只能被继承,且子类必须实现抽象方法。
from abc import ABCMeta,abstractmethod class Animal(metaclass=ABCMeta): @abstractmethod def eat(self):#父类的方法子类必须实现 print("打开粮食的袋子") print("取出一个碗") print("往碗里倒粮食") def sleep(self):pass class Dog(Animal): def eat(self): print("dog is eating") super().eat() def sleep(self): print("dog is sleeping") d = Dog() d.eat() d.sleep()
多继承:
class A(object): def test(self): print('from A') class B(A):pass class C(A): def test(self): print('from C') class E(B):pass class F(C):pass class H: def test(self): print('from H') class I(H): def test(self): print('from I') class J(H): def test(self): print('from J') class D(E,F,I,J):pass d = D() d.test() #[<class '__main__.D'>, <class '__main__.E'>, <class '__main__.B'>, <class '__main__.F'>, <class '__main__.C'>, <class '__main__.A'>, <class '__main__.I'>, <class '__main__.J'>, <class '__main__.H'>, <class 'object'>] #继承顺序 print(D.mro())
钻石继承
继承顺序
python中的类可以继承多个类,寻找的方式有两种,分别是深度优先和广度优先
当类是经典类时,多继承情况下,会按照深度优先的方式查找
当是新式类时,多继承情况下,会按照广度优先的方式查找
python2中默认经典类,python3 中默认新式类(可以通过类名.mro()查看继承顺序)
继承小结
继承的作用:减少代码的重用
提高代码的可读性
规范编程方式
几个名词:抽象 抽象即抽取类似的部分
继承 子类继承父类的属性和方法
派生 子类在父类方法和属性的基础上产生新的方法和属性
接口类和抽象类
多继承问题:
抽象类中,尽量避免多继承
在继承接口时候,尽量使用多继承接口
方法的实现
在抽象中,我们可以对一些抽象方法做出基础实现
在接口类中,任何方法都只是一种规范,具体的功能需要子类实现。
钻石继承
新式类:广度优先
经典类:深度优先
多态
多态指的是一类事物的多种形态
from abc import ABCMeta,abstractmethod class Animal(metaclass=ABCMeta): @abstractmethod def talk(self): pass class People(Animal):#动物形态---》人 def talk(self): print("say hello") class Dog(Animal):#动物形态---》狗 def talk(self): print("say wangwang") class Pig(Animal):#动物的形态---》猪 def talk(self): print("say aoao")
多态性,调用同样的talk方法,每个对象会用自己的方式去响应同样的talk方法
from abc import ABCMeta,abstractmethod class Animal(metaclass=ABCMeta): @abstractmethod def talk(self): pass class People(Animal):#动物形态---》人 def talk(self): print("say hello") class Dog(Animal):#动物形态---》狗 def talk(self): print("say wangwang") class Pig(Animal):#动物的形态---》猪 def talk(self): print("say aoao") 人 = People() 狗 = Dog() 猪 = Pig() 人.talk() 猪.talk() 狗.talk()
【封装】
隐藏对象的属性和实现细节,仅对外提供公共访问方式。
【好处】
将变化隔离
便于使用
提高复用性
提高安全性
【封装原则】
将不需要对外提供的内容都隐藏起来
将属性都隐藏,提供公共方法对其访问
私有变量和私有方法
在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)
#这是一种变形操作 #类中所有双下划线开头的名称如__x都会自动变形:_类名__x的形式 class A: __N = 0#类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的 def __init__(self): self.__x = 10#变形为self._A__x def __foo(self):#变形为_A__foo print('from A') def bar(self): self.__foo()#只有在类内部才可以通过__foo的形式访问到。 a =A() print(a._A__foo()) #_A__foo()是可以访问到的,即这种操作不是严格意义上的限制外部访问,仅仅是一种语法上的变形
这种自动变形的特点:
1. 类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。
2.这种变形其实是针对外部的变形,在外部无法通过—x这个名字访问到的。
3.在子类定义的—x不会覆盖在父类定义的—x,因为子类中变成了:—子类名__x,而父类中变成了_父类名__x,即双下划线开头的属性在继承给子类的时候,子类是无法覆盖的。
需要注意的是这种机制并没有真正意义上限制我们从外部直接访问属性,知道了类名属性名就可以拼出名字:_类名__属性,然后直接从外部访问到了属性
私有方法:
在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
class A: def fa(self): print('from A') def test(self): self.fa() class B(A): def fa(self): print('from B') a = B() a.fa()#from B 正常情况下,子类会将父类的方法覆盖 class A: def __fa(self): print("from A") def test(self): self.__fa() class B(A): def __fa(self): print('from V') b = B() b.test()#from A 子类没有对父类的方法覆盖
封装与扩展性
封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码,而外部使用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变,这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。
#类的设计者 class Room: def __init__(self,name,owner,width,length,high): self.name = name self.owner = owner self.__width = width self.__length = length self.__high = high def tell_area(self):#对外提供的接口,隐藏了内部实现的过程 return self.__width*self.__length #调用者 r = Room("卧室",'egon',20,29,29) print(r.tell_area())#使用这个调用接口tell_area class Room: def __init__(self,name,owner,width,length,high): self.name = name self.owner = owner self.__width = width self.__length = length self.__high = high def tell_area(self):#对外提供的接口,隐藏内部实现,此时我们要求体积 #内部逻辑变了,在内部调整代码,外部感觉不到,还是使用该方法,但是功能已经改变 return self.__width*self.__length*self.__high #调用者 任然使用tell_area方法调用,无需改变自己的代码 r = Room("卧室",'egon',20,29,29) print(r.tell_area())
property 属性
#计算成人的BMI数值 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.8) p1.bmi()#没有使用property时是这样调用方法的 p1.bmi#使用了property时是这样调用的
#计算成人的BMI数值 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.8) p1.bmi()#没有使用property时是这样调用方法的 p1.bmi#使用了property时是这样调用的 import math class Circle: def __init__(self,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) print(c.perimeter)
为什么要用property
将一个函数定义成特性之后,对象再去使用的时候obj.name,根本无法觉察到自己的name执行了一个函数然后计算出来的,这种特性的使用方法遵循了统一访问的原则
python面向对象中的封装有三种方式:
【public】这种其实就是不封装,对外是公开的
【protected】这种封装对子类公开,对外不公开
【private】这种封装对谁都不公开
python中通过property对外提供set和get的接口
class Foo: def __init__(self,value): self.__NAME = value @property def name(self): return self.__NAME @name.setter def name(self,value): if not isinstance(value,str): raise TypeError('%s must be str'%value) self.__NAME = value#通过类型检查后将值存在真实的位置 @name.deleter def name(self): raise TypeError('can not delete') f = Foo('egon') print(f.name) f.name = 10 #TypeError: 10 must be str print(f.name) 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)#这一步相当于语法糖 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 print(obj.price) # AttributeError: 'Goods' object has no attribute 'original_price'
classmethod 和staticmethod
class Student: f = open('student',encoding = 'utf-8') def __init__(self): pass def func(self): pass @classmethod#类方法:默认参数cls,可以直接用类名调用,可以与类属性交互(可以使用f) def show_info_class(cls): for line in cls.f: name,sex = line.strip().split(",") print(name,sex) @staticmethod#静态方法,可以让类里的方法直接被类调用,就像正常函数一样 def show_info_student_static(): f = open('student',encoding='utf-8') for line in f: name,sex = line.strip().split(",") print(name,sex) # 海姣 = Student() # 海姣.show_info_class() Student.show_info_class() Student.show_info_student_static()
相同点和不同点
相同:
都可以直接被类调用
不同点:
类方法必须有一个cls参数表示这个类,可以使用类属性
静态方法不需要参数,静态方法不能直接给使用
绑定方法:
默认方法:默认有一个self对象传进来,并且只能被对象调用(绑定到对象)
类方法:默认有一个cls传进来表示本类可以被类和对象调用(不推荐)---》绑定到类
非绑定方法:
静态方法:没有默认参数,并且可以被类和对象(不推荐)调用----》非绑定