Python之面向对象的三大特性(继承、封装、多态)
-
继承与派生
-
继承
- 继承是一种新建类的方式,在Python中支持一个子类继承多个父类。继承是类与类之间的从属关系,寻找这种关系需要先抽象,再继承。新建的类称为子类或派生类,父类又称为基类或超类。子类会继承父类的属性,可以使用父类的静态变量和方法。继承其实增加了类的耦合性。
-
使用继承的意义
- 减少代码冗余。
-
继承的使用方法
-
在Python 2中类分为两种,一种叫经典类(没有继承了object类,以及该类的子类,查找顺序遵从深度优先),一种叫新式类(继承了object类,以及该类的子类,查找顺序遵从广度优先,新式类中使用
类名.mro()
可以查看C3算法)。但在Python 3中全都为新式类。类的继承分为两种,一种是单继承,单继承可以继承多次;一种是多继承,Python中支持多继承,但并不是所有语言都支持,使用多继承可以同时继承多个父类,查找顺序为先继承的先找。# For Example: class ParentClass1: pass class ParentClass2: pass class SubClass1(ParentClass1): # 单继承 pass class SubClass2(ParentClass1,ParentClass2): #多继承,先从写在前面的父类中找 pass print(SubClass1.__bases__) # 查看SubClass的父类 print(SubClass2.__bases__) # 查看SubClass的父类 # For Example: class Father(object): # 继承object这种写法可以兼容Python 2 def __init__(self): self.func() def func(self): print('The func from Father') class Son(Father): # 子类Son会继承父类Father的所有属性和方法 def func(self): print('The func from Son') son = Son() # The func from Son # 注意:代码自上而下执行,定义Father类的时候,会开辟一个名称空间,将__init__和func放入Father类的名称空间;定义Son类的时候,也会开辟一个名称空间,程序读到class Son(Father)时会产生一个类指针,指向Father类的名称空间,同时将类指针和func存入Son的名称空间中;当程序读到son = Son()进行实例化产生对象的时候,同样也会为对象son开辟一个名称空间,产生一个类指针,指向Son类,并且实例化会自动触发__init__,首先会在对象son的名称空间找__init__,找不到,再去Son类中找,也没有,再通过类指针指向的Father中查找,找到了__init__,把son作为self传入到__init__中,此时,father中__init__的self实际上指向的时son的名称空间,当执行self.func()时,首先会在son的名称空间查找func(),没有,再去Son类中查找,发现有一个func()方法,执行,因此,son = Son()得到的结果是:The func from Son。 print(Son.mro()) # 查看广度优先的查找顺序,只在新式类中有
-
-
派生
-
如果子类的类体中只有一个pass,意味着全部继承父类。如果子类中定义了自己新的属性,称之为派生。如果子类中的属性或方法与父类重名,子类永远优先调用自己的属性或方法。如果需要在子类的方法中调用父类的方法,需要通过
父类名.方法名(self)
的方式。class Chinese(object): country = 'China' # 类的属性 def __init__(self, name, birthday, sex): self.name = name self.birthday = birthday self.sex = sex def attribute(self): # 类的方法 print('这是父类的方法') class Northerners(Chinese): def northerners_diet(self): print(F'{self.name} is northerners,Eat pasta') def attribute(self): # 子类中定义自己的attribute方法,称之为派生 Chinese.attribute(self) # 在子类的方法中调用父类的方法 print('这是子类的方法') class Southerners(Chinese): pass # Southerners类全部继承Chinese类 n1 = Northerners('Northerner', '2000-01-01', 'male') n1.attribute() # 这是子类的方法
-
在子类派生出的新方法中重用父类功能方法
方式一:指名道姓的调用(其实与继承没有什么关系)
# For Example: class User(object): def __init__(self, name, password, mobile): self.name = name self.password = password self.mobile = mobile class VIPUser(object): def __init__(self, name, password, mobile, effective_date, expiring_date): # self.name = name # self.password = password # self.mobile User.__init__(self, name, password, mobile) # 使用类名调用 self.effective_date = effective_date self.expiring_date = expiring_date user = VIPUser('老王', '123', 'xxx', '2020-01-01', '2021-01-01') print(user.__dict__)
方式二:
super()
调用(严格依赖于继承)super()
的返回值是一个特殊的对象,该对象专门用来调用父类中的属性。class User(object): def __init__(self, name, password, mobile): self.name = name self.password = password self.mobile = mobile class VIPUser(User): # 使用super()必须继承父类 def __init__(self, name, password, mobile, effective_date, expiring_date): # self.name = name # self.password = password # self.mobile super().__init__(name, password, mobile) # super()专门调取父类的方法,super(VIPUser, self) self.effective_date = effective_date self.expiring_date = expiring_date user = VIPUser('老王', '123', 'xxx', '2020-01-01', '2021-01-01') print(user.__dict__)
- 以上两种方式用哪种都可以,但不要混合使用
-
-
抽象类
-
抽象类是一个开发规范,或者说是用来规范开发代码的,通过抽象类可以约束它所有的子类实现相同的方法。一般用于多人协同开发时进行开发代码规范。
-
抽象类的实现方式一:
# 定义支付抽象类 class Payment(object): ''' Payment为抽象类,用于规范子类的pay方法。 ''' def __init__(self, name): self.name = name def pay(self, amount): # 严格限定子类方法的命名,如果不一样就主动抛出异常 raise NotImplementedError('请在子类中规范pay方法的命名') # 定义一个通过支付宝支付的类 class AliPay(Payment): # def pay(self, amount): # 由于父类抽象类的限制,此方法必须命名为:pay print(F'{self.name}通过支付宝成功支付:{amount}元') # 定义一个通过微信支付的类 class WeChatPay(Payment): def pay(self, amount): # 由于父类抽象类的限制,此方法必须命名为:pay print(F'{self.name}通过微信成功支付:{amount}元') # 定义一个通过苹果支付的类 class ApplePay(Payment): def pay(self, amount): # 由于父类抽象类的限制,此方法必须命名为:pay print(F'{self.name}通过苹果成功支付:{amount}元') # 归一化设计:让调用者不用实例化对象 def pay(name, payment_method, amount): if payment_method == 'AliPay': obj = AliPay(name) obj.pay(amount) elif payment_method == 'WeChatPay': obj = WeChatPay(name) obj.pay(amount) elif payment_method == 'ApplePay': obj = ApplePay(name) obj.pay(amount) # 其他程序员调用归一化设计的支付接口 pay('马云', 'AliPay', 10000) # 马云通过支付宝成功支付:10000元 pay('马化腾', 'WeChatPay', 10000) # 马化腾通过微信成功支付:10000元 pay('乔布斯', 'ApplePay', 10000) # 乔布斯通过苹果成功支付:10000元 # 原理:以ApplePay为例,当ApplePay实例化产生对象obj后,obj调用ApplePay类中的pay方法,如果ApplePay中的pay方法不是以‘pay’命名,则在ApplePay类中找不到pay方法,通过类指针去父类Payment中进行查找,找到了pay方法,但pay方法中的功能为主动抛出异常,借此来实现开发代码的规范。
-
抽象类的实现方式二:
# 导入相关模块 from abc import ABCMeta, abstractclassmethod # 定义支付抽象类 class Payment(metaclass=ABCMeta): ''' Payment为抽象类,用于规范子类的pay方法。 ''' def __init__(self, name): self.name = name @abstractclassmethod # 为pay加装饰器 def pay(self): pass # 定义一个通过支付宝支付的类 class AliPay(Payment): def pay(self, amount): # 由于父类抽象类的限制,此方法必须命名为:pay print(F'{self.name}通过支付宝成功支付:{amount}元') # 定义一个通过微信支付的类 class WeChatPay(Payment): def pay(self, amount): # 由于父类抽象类的限制,此方法必须命名为:pay print(F'{self.name}通过微信成功支付:{amount}元') # 定义一个通过苹果支付的类 class ApplePay(Payment): def pay(self, amount): # 由于父类抽象类的限制,此方法必须命名为:pay print(F'{self.name}通过苹果成功支付:{amount}元') # 归一化设计:让调用者不用实例化对象 def pay(name, payment_method, amount): if payment_method == 'AliPay': obj = AliPay(name) obj.pay(amount) elif payment_method == 'WeChatPay': obj = WeChatPay(name) obj.pay(amount) elif payment_method == 'ApplePay': obj = ApplePay(name) obj.pay(amount) # 其他程序员调用归一化设计的支付接口 pay('马云', 'AliPay', 10000) # 马云通过支付宝成功支付:10000元 pay('马化腾', 'WeChatPay', 10000) # 马化腾通过微信成功支付:10000元 pay('乔布斯', 'ApplePay', 10000) # 乔布斯通过苹果成功支付:10000元
-
-
-
封装
-
封装
- 装就是把属性存起来,封就是把这些存起来的属性隐藏,封装的终极奥义:明确的区分内外,对外是隐藏的,对内是开放的;隐藏对象的属性和实现细节,让类外部的使用者无法直接使用,仅对外提供公共访问方式。
- 广义的封装:把属性和方法装起来,在外部不能直接调用。装在类中的属性和方法都是广义上的封装。
- 狭义的封装:把类的属性和方法藏起来,在类的外部不能调用,只能在内部使用。
-
使用封装的意义
- 封装数据属性的目的:把数据属性封装起来,然后需要开辟接口给类外部的使用者使用,好处是我们可以在接口处添加逻辑控制,从而严格控制访问者对属性的操作。
- 使用封装的三个场景:
- 不想让别人看,也不想让别人改。
- 可以让别人看,但不想让别人改。
- 可以看也可以改,但必须按照指定的规则改。
-
封装的使用方法
import hashlib class User(object): def __init__(self, username, password): self.username = username self.__password = password # 在实例变量前面加上 __ ,就可以把实例变量隐藏起来,实例变量__password名变为:_User_password def __generate_hash(self): # 在方法前面加上 __ ,也可以把方法隐藏起来,在类外部无法调取 ''' 这是一个将用户密码加密生成MD5值的私有方法 :return: 用户密码加密后的MD5值 ''' md5 = hashlib.md5(self.username.encode('utf-8')) # 使用username加盐 md5.update(self.__password.encode('utf-8')) return md5.hexdigest() def get_password(self): return self.__generate_hash() user = User('Python', '123') # user.__password = '456' # 无法更改,会报错 # user. __generate_hash() # 无法调取,会报错 res = user.get_password() print(res) # 输出结果:ae35eacb1cb6f6d38c29a04ecb2d7471 # 隐藏属性:其实这种隐藏只是语法上的一种变形,这种语法上的变形,只在类定义阶段发生一次,类定义之后,新增的`__`开头的属性都没有变形效果。 # 如果父类不想让子类覆盖自己的方法,可以在方法名前加`__`开头。 # 在类的外部不能定义私有方法。 # 私有方法子类不能继承使用。
-
私有方法的调用
class Father(object): def __init__(self): self.__func() # self.__func 实际上是: self._Father__func() def __func(self): # __func 实际上是: _Father__func print('The func from Father') class Son(Father): def __func(self): # __func 实际上是: _Son__func print('The func from Son') son = Son() # The func from Father
-
数据类型的级别
- 在其他编程语言中数据类型有三种级别:
- Public(公有的):类内类外都能用,父类子类都能用。
- Protect(保护的):类内能用,父类子类都能用,类外不能用。
- Private(私有的):只有自己的类中能用,其他地方都不能用。
- 在Python中只支持:Public(公有的)和 Private(私有的)。
- 在其他编程语言中数据类型有三种级别:
-
封装之property
property
是一装饰器,访问它时会执行一段功能(函数)然后返回值,用来将类内的方法伪装成一个数据属性。# For Example: class People(object): 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('Python', 80, 1.68) p1.name # 调用类的属性 p1.bmi # bmi被@property伪装成了一个数据属性,因此调用时不用加() # p1.bmi对应的是一个函数,所以不能被赋值 # For Example: class User(object): def __init__(self, username, password): self.username = username self.__password = password # 私有实例变量 @property # 将password方法伪装成一个属性,达到只能看,不能改的效果 def password(self): ''' 定义一个password方法,只能看,不能改 :return: ''' return self.__password user = User('Python', '123') pwd = user.password # 调用者以为password是个属性,但其实只能看,不能改 print(pwd) # 123
-
封装之property的进阶用法
class Goods(object): ''' 这是一个商品类: 有商品名称,商品价格以及折扣 可以进行商品价格的调整,如打折和涨价 ''' discount = 0.8 # 折扣 def __init__(self, name, original_price): self.name = name self.__orig_price = float(original_price) @property # 将price伪装成属性 def price(self): ''' 这是一个实现商品价格折扣的方法 :return: 商品折扣的价格 ''' return self.__orig_price * self.discount @price.setter # @后面的名字必须与上面的方法名称price一样,被装饰的方法可以传入一个值 def price(self, new_value): # 定义的新方法名字必须与上面的方法名price一样 ''' 这是一个实现商品改变原价的方法 :param new_value: :return: 原价变动后的价格 ''' self.__orig_price = float(new_value) @price.deleter # @后面的名字必须与上面的方法名称price一样 def price(self): del self.__orig_price iphone12 = Goods('iPhone12', 12000) print(iphone12.price) # 调用的是被@property装饰的price 输出结果:9600.0 iphone12.price = 13000.0 # 调用的是被@price.setter装饰的price print(iphone12.price) # 输出结果:10400.0 del iphone12.price # 调用的是被@price.deleter装饰的price
-
封装之classmethod
classmethod
是一个装饰器,用于装饰类中的方法,被装饰的方法会成为一个类方法。应用场景:定义一个方法,默认传
self
,但这个self
没被使用,并且在这个方法里用到了类名调用属性的时候,需要使用classmethod。class Goods(object): ''' 这是一个商品类 ''' __discount = 0.8 def __init__(self, name, original_price): self.name = name self.__orig_price = float(original_price) self.price = self.__orig_price * Goods.__discount @classmethod # 把一个对象的绑定方法,修改成类方法 def change_discount(cls, new_discount): # cls会把类本身传进来,cls = Goods ''' 这是一个修改折扣的方法 此方法中self实际上没有被使用 :param new_discount: :return: ''' # Goods.__discount = new_discount cls.__discount = new_discount # 调用类中的静态变量 Goods.change_discount(0.6) # 不用实例化,直接用类名调用方法 iPhone = Goods('iPhone', 13000) print(iPhone.price)
import time class Date(object): def __init__(self, year, month, day): self.year = year self.month = month self.day = day @classmethod def today(cls): struct_time = time.localtime() date = cls(struct_time.tm_year, struct_time.tm_mon, struct_time.tm_mday) return date today_obj = Date.today() print(today_obj.year) # 调的是self.year = 2020 print(today_obj.month) # 调的是self.month = 3 print(today_obj.day) # 调的是self.day = 13
-
封装之staticmethod
staticmethod
是一个装饰器,用于装饰类中的方法,被装饰的方法会成为一个静态方法。应用场景:类外部的一个普通函数,需要放到类中,仍然保持普通函数的状态,需要使用
staticmethod
。一般纯面向对象编程中才会用到,不常用。
-
- 能定义再类中的内容
- 静态变量:所有对象共享的变量,由对象或类调用
- 绑定方法:自带self的函数,由对象调用。
- property属性:伪装成属性的方法,由对象调用,但不加()。
- 类方法:自带cls的函数,由对象或类调用。
- 静态方法:就是一个普通函数,由对象或类调用。
```python
class People(object):
country = '中国' # 静态变量
def func(self): # 绑定方法
print(self.__dict__)
@property
def property_func(self): # property属性
return 'property属性'
@classmethod
def class_func(cls): # 类方法
print(cls)
@ staticmethod
def static_func(): # 静态方法
print('不用穿self,就是一个普通函数')
```
网友评论