14--Python 面向对象进阶
@Author : Roger TX (425144880@qq.com)
@Link : https://github.com/paotong999
一、封装
封装是从业务逻辑中抽象对象时,要赋予对象相关数据与操作,将一些数据和操作打包在一起的过程。
封装对类形成了一种“黑盒”状态,我们不需要知道类内部是什么样的,只要对对象进行操作就可以。
1.含义
封装是对全局作用域中其它区域隐藏多余信息的原则。
2.实例
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线
__
,在Python中,实例的变量名如果以__
开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。
二、继承
继承概念
- 继承实现了代码的重用,相同的代码不需要重复的编写。
- 在Python中子类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类
- 父类的私有属性和方法不会被子类继承
继承顺序
- Python3中如果不指定继承哪个类,默认就会继承Object类,继承了Object类的类就叫做新式类。
- Python2中如果不指定继承哪个类,不会默认去继承Object类,没有继承Object类的类就叫做经典类。
- 经典类和新式类的不同就在于对方法的搜索顺序不同,经典类是深度优先;而新式类是广度优先。
继承顺序
单继承与多继承
- 父类中没有的属性在子类中出现叫做派生属性,父类中没有的方法在子类中出现叫做派生方法
- 只要是子类的对象调用,子类中有的名字一定用子类的,子类中没有才找父类的,如果父类也没有报错
- 如果父类、子类都有则用子类的,如果还想用父类的,单独调用父类的
-- 父类名.方法名 需要自己传self参数
-- super().方法名 不需要自己传self - 可以用
Foo.__mro__
方法查看继承顺序
class A:
def __init__(self,a=None):
self.a=a
def test(self):
print("A...test")
class B:
def __init__(self,b=None):
self.b=b
def test(self):
print("B...test")
class C(B,A):
def __init__(self,a):
A.__init__(self,a)
def t(self):
A.test(self) # 调用A的test()
super().test() # 这个调用的也是B的test
print("C....t")
c=C("aa")
#默认调用的是父类B的test方法,因为在class C(B,A),B在A前面
c.test()
c.t()
二、super()
函数
Python 要求:如果子类重写了父类的构造方法,那么子类的构造方法必须调用父类的构造方法。
子类的构造方法调用父类的构造方法有两种方式:
- 使用未绑定方法,这种方式很容易理解。因为构造方法也是实例方法,当然可以通过这种方式来调用。
- 使用 super() 函数调用父类的构造方法。
调用 super() 的本质就是调用 super 类的构造方法来创建 super 对象
- 使用 super() 构造方法最常用的做法就是不传入任何参数,然后通过 super 对象的方法调用父类方法。
- 在调用父类的实例方法时,程序会完成第一个参数 self 的自动绑定。
- 在调用类方法时,程序会完成第一个参数 cls 的自动绑定。
class Employee :
def __init__ (self, salary):
self.salary = salary
def work (self):
print('普通员工正在写代码,工资是:', self.salary)
class Customer:
def __init__ (self, favorite, address):
self.favorite = favorite
self.address = address
def info (self):
print('我是一个顾客,我的爱好是: %s,地址是%s' % (self.favorite, self.address))
class Manager(Employee, Customer):
# 重写父类的构造方法
def __init__(self, salary, favorite, address):
print('--Manager的构造方法--')
# 通过super()函数调用父类的构造方法
super().__init__(salary)
# 与上一行代码的效果相同
#super(Manager, self).__init__(salary)
# 使用未绑定方法调用父类的构造方法
Customer.__init__(self, favorite, address)
# 创建Manager对象
m = Manager(25000, 'IT产品', '广州')
m.work()
m.info()
三、抽象类&接口类
@abstractmethod:抽象方法
含abstractmethod方法的类不能实例化,继承的子类必须实现 @abstractmethod
装饰的方法,未被装饰的可以不重写
from abc import abstractclassmethod,ABCMeta
class Payment(metaclass=ABCMeta):
@abstractclassmethod
def pay(self):
print('支付ing....')
class Wechatpay(Payment):
def pay(self):
print('微信支付ing...')
@ property:方法伪装属性
方法返回值及属性值,被装饰方法不能有参数,必须实例化后调用,类不能调用
将一个方法伪装成属性,被修饰的特性方法,内部可以实现处理逻辑,但对外提供统一的调用方式
这个装饰器还有和其配套的setter、deleter
class Data:
def __init__(self):
self.number = 123
@property
def operation(self):
return self.number
@operation.setter
def operation_set(self, number):
self.number = number
@operation.deleter
def operation_del(self):
del self.number
d = Data()
print(d.operation)
d.operation_set = 222
del d.operation_del
四、多态
不同的子类对象调用相同的父类方法,产生不同的执行效果,可以增加代码的外部调用灵活度。
父类变量能够引用子类对象,当子类有重写父类方法,调用的将是子类方法。
- 定义一个父类
- 定义多个子类,并重写父类的方法
- 传递子类对象给调用者,不同子类对象产生不同的执行效果
五、枚举
在某些情况下,一个类的对象是有限且固定的,比如季节类,它只有 4 个对象;再比如行星类,目前只有 8 个对象。这种实例有限且固定的类,在 Python 中被称为枚举类。
程序有两种方式来定义枚举类:
- 直接使用 Enum 列出多个枚举值来创建枚举类。
- 通过继承 Enum 基类来派生枚举类。
直接使用 Enum 列出多个枚举值来创建枚举类:
import enum
# 定义Season枚举类
Season = enum.Enum('Season', ('SPRING', 'SUMMER', 'FALL', 'WINTER'))
# 直接访问指定枚举
print(Season.SPRING) # Season.SPRING
# 访问枚举成员的变量名
print(Season.SPRING.name) # SPRING
# 访问枚举成员的值
print(Season.SPRING.value) # 1
# 根据枚举变量名访问枚举对象
print(Season['SUMMER']) # Season.SUMMER
# 根据枚举值访问枚举对象
print(Season(3)) # Season.FALL
上面程序使用 Enum() 函数(就是 Enum 的构造方法)来创建枚举类,该构造方法的第一个参数是枚举类的类名;第二个参数是一个元组,用于列出所有枚举值。
Python 还为枚举提供了一个 __members__
属性,该属性返回一个 dict 字典,字典包含了该枚举的所有枚举实例。程序可通过遍历 __members__
属性来访问枚举的所有实例
# 遍历Season枚举的所有成员
for name, member in Season.__members__.items():
print(name, '=>', member, ',', member.value)
通过继承 Enum 基类来派生枚举类:
from enum import Enum, unique, IntEnum
@unique
class Orientation(Enum):
# 为序列值指定value值
EAST = '东'
SOUTH = '南'
WEST = '西'
NORTH = '北'
def info(self):
print('这是一个代表方向【%s】的枚举' % self.value)
# 通过枚举变量名访问枚举
print(Orientation['WEST']) # Orientation.WEST
# 通过枚举值来访问枚举
print(Orientation('南')) # Orientation.SOUTH
# 调用枚举的info()方法
Orientation.EAST.info() # 这是一个代表方向【东】的枚举
上面程序通过继承 Enum 派生了 Orientation 枚举类,通过这种方式派生的枚举类既可额外定义方法,如上面的 info() 方法所示,也可为枚举指定 value(value 的值默认是 1、2、3、…)
- @unique 装饰器防止value值相同,如果没有这个标签,当有两个值相同时,第二个name相当于第一个name的别名
- Enum 允许 value 为非整型,IntEnum 只允许 value 为整型
- 枚举类是单例模式,不能实例化
六、metaclass元类
如果希望创建某一批类全部具有某种特征,则可通过 metaclass
来实现。使用 metaclass
可以在创建类时动态修改类定义
为了使用 metaclass
动态修改类定义,程序需要先定义 metaclass
, metaclass
应该继承 type 类,并重写 __new__()
方法。
metaclass,直译为元类,可以理解为类的元数据
先定义metaclass,就可以创建类,最后创建实例。你可以把类看成是metaclass创建出来的“实例”。
定义ListMetaclass,按照默认习惯,metaclass的类名总是以Metaclass结尾,以便清楚地表示这是一个metaclass:
# metaclass是类的模板,所以必须从`type`类型派生:
class ListMetaclass(type):
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)
__new__()
方法接收到的参数依次是:
- 当前准备创建的类的对象;
- 类的名字;
- 类继承的父类集合;
- 类的方法和属性集合。
有了ListMetaclass,我们在定义类的时候还要指示使用ListMetaclass来定制类,传入关键字参数metaclass:
class MyList(list, metaclass=ListMetaclass):
当我们传入关键字参数metaclass时,Python解释器在创建MyList时,要通过 ListMetaclass.__new__()
来创建,在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义。
现在 MyList 就可以调用add()方法
L = MyList()
L.add(1)
L
下面看一个元类使用的实例
# 定义ItemMetaClass,继承type
class ItemMetaClass(type):
# cls代表动态修改的类
# name代表动态修改的类名
# bases代表被动态修改的类的所有父类
# attr代表被动态修改的类的所有属性、方法组成的字典
def __new__(cls, name, bases, attrs):
# 动态为该类添加一个cal_price方法
attrs['cal_price'] = lambda self: self.price * self.discount
return type.__new__(cls, name, bases, attrs)
# 定义Book类
class Book(metaclass=ItemMetaClass):
__slots__ = ('name', 'price', '_discount')
def __init__(self, name, price):
self.name = name
self.price = price
@property
def discount(self):
return self._discount
@discount.setter
def discount(self, discount):
self._discount = discount
# 定义cellPhone类
class CellPhone(metaclass=ItemMetaClass):
__slots__ = ('price', '_discount' )
def __init__(self, price):
self.price = price
@property
def discount(self):
return self._discount
@discount.setter
def discount(self, discount):
self._discount = discount
b = Book("Python基础教程", 89)
b.discount = 0.76
# 创建Book对象的cal_price()方法
print(b.cal_price())
cp = CellPhone(2399)
cp.discount = 0.85
# 创建CellPhone对象的cal_price()方法
print(cp.cal_price())
通过使用 metaclass 可以动态修改程序中的一批类,对它们集中进行某种修改。这个功能在开发一些基础性框架时非常有用,程序可以通过使用 metaclass 为某一批需要具有通用功能的类添加方法。