22. 面向对象之多态
1. 多态
1.1 概念
多态指的是一类事物有多种形态
比如动物有多种形态:人、猴、鸭
1.2 代码示例
from abc import ABC, abstractmethod
# 对于程序来说,定义一个基类可以有多个子类
class Animal(ABC):
@abstractmethod
def run(self):
pass
@abstractmethod
def speak(self, name):
print(f'{name}正在说话')
# 第一种形态:人
class Person(Animal):
def run(self):
print('可以润')
def speak(self, name):
print(f'{name}正在说话')
# 第二种形态:猴
class Monkey(Animal):
def run(self):
print('猴哥也可以润')
# 第三种形态:鸭子
class Duck(Animal):
def run(self):
print('鸭子也可以润')
1.3 多态性
动态绑定:在程序运行的过程中,根据对象的类型,动态的将方法进行绑定
动态多样性:动态绑定在继承背景下的特性就叫动态多态性
静态多样性:如任何类型都可以用运算符加号+ 进行运算
from abc import ABC, abstractmethod
# 同一类事物:动物
class Animal(ABC):
@abstractmethod
def run(self):
pass
# 第一种形态:人
class Person(Animal):
def run(self):
print('可以润')
# 第二种形态:猴
class Monkey(Animal):
def run(self):
print('猴哥也可以润')
# 第三种形态:鸭子
class Duck(Animal):
def run(self):
print('鸭子也可以润')
person1 = Person()
monkey1 = Monkey()
duck1 = Duck()
# person1、monkey1、duck1都是动物,只要是动物肯定有run方法
# 于是可以不用考虑三个对象的具体是声明类型,而直接使用
person1.run()
monkey1.run()
duck1.run()
# 更进一步,可以定义一个统一的接口来使用
def func(obj):
obj.run()
python中一切皆对象,本身就支持多态性
# 在不考虑对象类型的情况下统计对象的长度
a.__len__()
b.__len__()
c.__len__()
# python内置了一个统一的接口
len(a)
len(b)
len(c)
2. 鸭子类型
鸭子类型(duck typing)是一种编程风格,不是一个真实存在的约束关系,是一种普遍的规范
看起来像、声音像、走起路来像鸭子,那么它就是鸭子
# 二者看起来都像文件,因而就可以当文件一样去用,然而它们并没有直接的关系
class Txt: # Txt类有两个与文件类型同名的方法,即read和write
def read(self):
pass
def write(self):
pass
class Video: # Video类也有两个与文件类型同名的方法,即read和write
def read(self):
pass
def write(self):
pass
3. 绑定方法和非绑定方法
3.1 概念
绑定方法(动态方法):
绑定给谁,谁来调用就自动将它本身当作第一个参数传入
绑定给类的方法、绑定给对象的方法
非绑定方法(静态方法):
既不绑定给类也不绑定给对象
一句话概括:方法即函数,可以理解为绑定函数、非绑定函数
3.2 绑定给对象的方法
概述:
绑定给对象的方法就是在类内部直接定义的方法------即在类内部写的函数,写的时候自动带上self参数
特点就是会自动补全self参数
对象调用绑定给对象的方法的时候不需要传入self参数,类调用对象的绑定方法的时候需要传入self参数(对象名)
class Student:
def __init__(self, name):
self.name = name
def run(self): # 绑定给对象的方法就是在类内部定义的方法,自动补全self对象
print(f'这是run方法,{self.name}可以润')
stu1 = Student(name='lavigne') # 实例化得到的对象
# (1)对象调用对象的绑定方法
# 不需要传递self参数,在对象调用对象的绑定方法的时候会自动将对象作为self参数传进去
stu1.run() # 这是run方法,lavigne可以润
# (2)类调用对象的绑定方法
# 必须主动给self传入一个对象
stu2 = Student(name='avril')
# Student.run() # 报错 TypeError: Student.run() missing 1 required positional argument: 'self'
Student.run(stu1) # 这是run方法,lavigne可以润
Student.run(stu2) # 这是run方法,avril可以润
3.3 绑定给类的方法 classmethod
类在使用时会将类本身当作参数传给类方法的第一个参数(即便是对象来调用也会将类当作第一个参数传入)
classmethod把类中的函数定义成类方法
class Student:
def __init__(self, name):
self.name = name
@classmethod
def read(cls): # 绑定给类的方法,必须用classmethod装饰器,并且在定义函数的时候默认补全参数cls
print(f'当前是绑定给类的read方法:{cls}')
# (1)对象调用绑定给类的方法
# 不需要传入cls参数
# 会自动识别实例化当前对象的类,直接将类传入
stu1 = Student(name='lavigne')
stu1.read() # 当前是绑定给类的read方法:<class '__main__.Student'>
# (2)类调用绑定给类的方法
# 不需要传入cls参数
# 默认将当前类作为cls的默认参数传入
Student.read() # 当前是绑定给类的read方法:<class '__main__.Student'>
总结:
定义绑定给类的方法的时候,必须用装饰器@classmethod;类似于绑定给对象的方法函数,会自动补全参数cls
对象调用绑定给类的方法,不需要传入cls参数
类调用绑定给类的方法,不需要传入cls参数
3.4 非绑定方法 staticmethod
在类内部用staticmethod装饰的函数即非绑定方法,就是普通函数
staticmethod既不绑定给类,也不绑定给对象,在定义函数的时候和普通函数一样不会补全任何默认参数
class Student:
def __init__(self, grade):
self.grade = grade
@staticmethod
def run(name): # 非绑定方法,必须用装饰器staticmethod,在定义函数时和普通函数一样不会补全默认参数
print(f'{name}可以润')
# (1)对象调用非绑定方法
stu1 = Student(grade=2)
stu1.run(name='lavigne')
# (2)类调用非绑定方法
Student.run(name='avril')
总结:
在定义非绑定方法(静态方法、非绑定函数)必须要用装饰器@staticmethod,并且装饰后的函数和普通函数一样
类和对象调用非绑定方法时,函数定义哪些参数就传入哪些参数,和普通函数一样
4. 反射
4.1 概念
反射是一种特性,允许程序在运行时检查、访问和修改对象的属性和方法
4.2 反射方法介绍
hasattr(obj, key) # 判断对象是否具有指定属性
getattr(obj, key[, default]) # 获取对象的属性值,如果属性不存在,可设置默认值
setattr(obj, key, value) # 设置对象的属性值
delattr(obj, key) # 删除对象的属性
4.3 hasattr
class Student:
def __init__(self, name, grade):
self.name = name
self.grade = grade
def run(self):
print('这是绑定给对象的方法')
@classmethod
def sing(cls):
print('这是绑定给类的方法')
@staticmethod
def rap():
print('这是非绑定/静态方法')
stu1 = Student(name='lavigne', grade=2)
# hasattr(obj, key)
# 从对象中判断属性是否存在,有则返回True,无则返回False
print(hasattr(stu1, 'name')) # True
print(hasattr(stu1, 'hobby')) # False
# 除了可以判断数据属性,也可以判断方法属性(函数)
print(hasattr(stu1, 'rap')) # True
4.4 getattr
class Student:
def __init__(self, name, grade):
self.name = name
self.grade = grade
def run(self):
print('这是绑定给对象的方法')
@classmethod
def sing(cls):
print('这是绑定给类的方法')
@staticmethod
def rap():
print('这是非绑定/静态方法')
stu1 = Student(name='lavigne', grade=2)
# getattr(obj, key[, default])
# 从对象中获取键对应的值,键值对存在则将值返回;不存在则抛出异常
# 可以指定键不存在的时候的默认值
print(getattr(stu1, 'name'))
# print(getattr(stu1, 'hobby')) # AttributeError: 'Student' object has no attribute 'hobby'
print(getattr(stu1, 'hobby', None)) # None
# 除了可以从对象获取数据属性,也可以获取方法属性(函数),返回函数的内存地址
print(getattr(stu1, 'rap')) # <function Student.rap at 0x000001E00E4A17E0>
# 根据获取到的内存地址调用函数
getattr(stu1, 'rap')() # 这是非绑定/静态方法
4.5 setattr
class Student:
def __init__(self, name, grade):
self.name = name
self.grade = grade
def run(self):
print('这是绑定给对象的方法')
@classmethod
def sing(cls):
print('这是绑定给类的方法')
@staticmethod
def rap():
print('这是非绑定/静态方法')
stu1 = Student(name='lavigne', grade=2)
print(stu1.__dict__) # {'name': 'lavigne', 'grade': 2}
# setattr(obj, key, value)
# 向对象中设置数据属性
setattr(stu1, 'hobby', 'music') # 键不存在则新增
print(stu1.__dict__) # {'name': 'lavigne', 'grade': 2, 'hobby': 'music'}
setattr(stu1, 'hobby', 'surf') # 键存在则修改
print(stu1.__dict__) # {'name': 'lavigne', 'grade': 2, 'hobby': 'surf'}
# 向类中添加方法属性(函数)
def holiday():
print('这是在类外部定义的函数')
setattr(Student, 'vocation', holiday) # 往类的属性字典中增加了一个键值对,键为vocation,值为holiday
# print(Student.__dict__) # 'vocation': <function holiday at 0x000001BC11303370>
print(Student.vocation()) # 这是在类外部定义的函数
# 向对象中添加方法属性
print(stu1.__dict__) # {'name': 'lavigne', 'grade': 2, 'hobby': 'surf'}
setattr(stu1, 'rest', holiday) # 往对象的属性字典中新增了一个键值对,值是函数名
print(stu1.__dict__) # {'name': 'lavigne', 'grade': 2, 'hobby': 'surf', 'rest': <function holiday at 0x000001E629AE3E20>}
4.6 delattr
class Student:
def __init__(self, name, grade):
self.name = name
self.grade = grade
def run(self):
print('这是绑定给对象的方法')
@classmethod
def sing(cls):
print('这是绑定给类的方法')
@staticmethod
def rap():
print('这是非绑定/静态方法')
stu1 = Student(name='lavigne', grade=2)
# delattr(obj ,key) 删除对象中的键值对
print(stu1.__dict__) # {'name': 'lavigne', 'grade': 2}
delattr(stu1, 'grade')
print(stu1.__dict__) # {'name': 'lavigne'}
# 对象不允许删除类中的任何方法属性(函数)
delattr(stu1, 'run') # AttributeError: run
delattr(stu1, 'sing') # AttributeError: sing
delattr(stu1, 'rap') # AttributeError: rap
# 但是类允许删除类中的任何方法属性(函数)
print(Student.__dict__)
delattr(Student, 'run')
delattr(Student, 'sing')
delattr(Student, 'rap')
print(Student.__dict__)
5. Class机制内置方法
# __init__ :初始化类时触发 **
# __del__ :删除类时触发
# __iter__ :迭代器
# __str__ :str函数或者print函数触发 **
# __repr__ :repr或者交互式解释器触发
# __doc__ :打印类内的注释内容 **
# __enter__ :打开文档触发
# __exit__ :关闭文档触发
# __getattr__ : 访问不存在的属性时调用
# __setattr__ :设置实例对象的一个新的属性时调用
# __delattr__ :删除一个实例对象的属性时调用
# __setitem__ :列表添加值
# __getitem__ :将对象当作list使用
# __delitem__ :列表删除值
# __call__ :对象后面加括号,触发执行
# __new__ :构造类时触发
# __init__ 实例化类得到对象,对象的数据属性初始化时触发
class Student:
def __init__(self, name):
self.name = name
print('触发__init__')
stu1 = Student(name='lavigne') # 触发__init__
# __del__ 用于在对象销毁之前执行一些清理操作。它通常用于关闭文件、释放资源等。这个方法不是析构方法。
class Student:
def __init__(self, name):
self.name = name
def __del__(self): # 对象被销毁之之前触发
print("object is being released")
stu1 = Student(name='lavigne') # 有实例化对象这个语句则会触发上面__del__,没有这个语句则不会
# __str__
# 该方法在对象被打印时自动触发
# print功能打印的就是它的返回值
# 该方法必须返回字符串类型
class Student:
def __init__(self, name):
self.name = name
def __str__(self):
# 返回类型必须是字符
return f'{self.name}正在度假'
stu1 = Student(name='lavigne')
# 打印对象,触发__str__,拿到返回值然后展示
print(stu1) # lavigne正在度假
# __doc__
# 用来打印类的注释内容
class Student:
"""
这是类的注释信息
"""
def __init__(self, name):
self.name = name
print(Student.__doc__) # 这是类的注释信息
# 反射 对象.键 触发
# (1) __getattr__(self, key) 键不存在才触发
class Student:
def __init__(self, name):
self.name = name
def __getattr__(self, key):
return f'属性{key}不存在'
stu1 = Student(name='lavigne')
print(stu1.name) # lavigne
print(stu1.hobby) # 属性hobby不存在
# (2)__setattr__ 对象.属性名 = 属性值 设置或修改键值对时触发
class Student:
def __init__(self, name):
self.name = name
def __setattr__(self, key, value):
print(f'键值对{key}:{value}新增成功')
stu2 = Student(name='lavigne') # 键值对name:lavigne新增成功
# (3)__delattr__ del 对象.属性名 删除键触发
class Student:
def __init__(self, name):
self.name = name
def __delattr__(self, key):
print(f'数据属性{key}已被删除')
stu3 = Student(name= 'lavigne')
del stu3.name # 数据属性name已被删除
# 反射 对象[键] 触发
# (1) __getitem__(self, key) 对象[键]获取对象后面中括号输入的值
class Student:
def __init__(self, name):
self.name = name
def __getitem__(self, key):
return f'{self.name} is {key}'
stu1 = Student(name='lavigne')
print(stu1['name']) # lavigne is name
print(stu1.name) # 不会触发getitem
# # (2)__setitem__ 对象[键] = 值 设置或修改键值对时触发
class Student:
def __init__(self, name):
self.name = name
def __setitem__(self, key, value):
print(f'键值对{key}:{value}新增成功')
stu2 = Student(name='lavigne')
stu2['age'] = 20 # 键值对age:20新增成功
stu2.__dict__.update({'num': '001'}) # 不会触发__setitem__
print(stu2.__dict__) # {'name': 'lavigne', 'num': '001'}
# # (3)__delitem__ del 对象[键] 删除键触发
class Student:
def __init__(self, name, id):
self.name = name
self.id = id
def __delitem__(self, key):
print(f'数据属性{key}已被删除')
stu3 = Student(name='lavigne', id='001')
del stu3['id'] # 数据属性id已被删除
del stu3.name # 不会触发__delitem__