python进阶之多态与鸭子类型

发布时间 2024-01-06 12:30:14作者: Xiao0101

多态与多态性

什么是多态

多态指的是一类事物有多种形态,比如动物有多种形态:猫、狗、猪、牛、羊等

class Animal: #同一类事物:动物
    def talk(self):
        pass
class Tiger(Animal): #动物的形态之一:老虎
    def talk(self):
        print('奥利给')
class Dog(Animal): #动物的形态之二:狗
    def talk(self):
        print('汪汪汪')
class Pig(Animal): #动物的形态之三:猪
    def talk(self):
        print('哼哼哼')

#实例化得到三个对象
tiger=Tiger()
dog=Dog()
pig=Pig()


import abc
# 同一类事物:文件
class File(metaclass=abc.ABCMeta): # 抽象类
    @abc.abstractmethod
    def click(self):
        pass


# 文件的形态之一:文本文件
class Txt(File):
    def click(self):
        print('open file')


# 文件的形态之二:JSON文件
class Json(File):
    def click(self):
        print('open JSON file')

什么是多态性

多态性指的是可以在不用考虑对象具体类型的情况下而直接使用对象,这就需要在设计时,把对象的使用方法统一成一种:例如cat、dog、pig都是动物,但凡是动物肯定有talk方法,于是我们可以不用考虑它们三者的具体是什么类型的动物,而直接使用

多态性分为动态多态性和静态多态性

(1)静态多态性

如任何类型都可以用运算符 + 进行运算

(2)动态多态性

import abc

class Animal(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def talk(self):
        pass
# 人
class People(Animal):
    def talk(self):
        print(f'你真帅!')
# 猪
class Pig(Animal):
    def talk(self):
        print(f'哼哼哼')
# 鸭
class Duck(Animal):
    def talk(self):
        print(f'嘎嘎嘎')


# people/pig/duck 都属于同一类 ---> 动物类,都能叫,都能实现叫的方法
people = People()
pig = Pig()
duck = Duck()

people.talk()
pig.talk()
duck.talk()

更进一步,我们可以定义一个统一的接口来使用,我不需要知道里面的具体逻辑,我只知道调用 talk 就能实现具体的功能

def animal_talk(obj):
    obj.talk()

animal_talk(people)
animal_talk(pig)
animal_talk(duck)

Python中一切皆对象,本身就支持多态性

任何事物.__len__()
def my_len(val)
	return val.__len__()
print(my_len(任何事物))
# 我们可以在不考虑三者类型的情况下直接使用统计三个对象的长度
s.__len__()
l.__len__()
t.__len__()

# Python内置了一个统一的接口
len(s)
len(l)
len(t)

为什么要使用多态性

多态性的好处在于增强了程序的灵活性和可扩展性,比如通过继承Animal类创建了一个新的类,实例化得到的对象obj,可以使用相同的方式使用obj.talk()

class Wolf(Animal): #动物的另外一种形态:狼
    def talk(self):
        print('嗷...')

wolf=Wolf() # 实例出一头狼
wolf.talk() # 使用者根本无需关心wolf是什么类型而调用talk
嗷...

综上我们得知,多态性的本质在于不同的类中定义有相同的方法名,这样我们就可以不考虑类而统一用一种方式去使用对象,可以通过在父类引入抽象类的概念来硬性限制子类必须有某些方法名

import abc

# 指定metaclass属性将类设置为抽象类,抽象类本身只是用来约束子类的,不能被实例化
class Animal(metaclass=abc.ABCMeta):
    @abc.abstractmethod # 该装饰器限制子类必须定义有一个名为talk的方法
    def talk(self): # 抽象方法中无需实现具体的功能
        pass

class Cat(Animal): # 但凡继承Animal的子类都必须遵循Animal规定的标准
    def talk(self):
        pass

cat=Cat() # 若子类中没有一个名为talk的方法则会抛出异常TypeError,无法实例化

鸭子类型(不依赖于继承)

鸭子类型(Duck Typing)是Python中一种动态类型语言的编程风格,它关注对象的行为而非类型。这个概念源自于“如果走起来像鸭子,叫起来像鸭子,那么它就是一只鸭子”。比起继承的方式,鸭子类型在某种程度上实现了程序的松耦合度。

在Python中,不关心对象的具体类型,只要对象具有特定的方法或属性,就可以被视为拥有某种类型。这种方法使得Python更加灵活,因为它允许不同的对象实现相同的方法,从而实现了多态性而无需显式地继承某个特定的接口或类。

鸭子类型的核心思想是基于对象的行为来判断其类型。例如,如果一个对象具有walk()quack()方法,那么它就可以被视为鸭子:

class Duck:
    def walk(self):
        print("Duck is walking")

    def quack(self):
        print("Duck is quacking")

class Person:
    def walk(self):
        print("Person is walking")

# 鸭子类型
def action(entity):
    entity.walk()
    entity.quack() if hasattr(entity, 'quack') else print("No quacking ability")

duck = Duck()
person = Person()

action(duck)    # 输出 Duck is walking 和 Duck is quacking
action(person)  # 输出 Person is walking 和 No quacking ability

在上面的例子中,action()函数接受一个对象作为参数,然后调用walk()quack()方法。虽然Person类并没有继承自Duck类,但是由于Person类也有walk()方法,所以它可以在action()函数中被当作鸭子类型使用。

鸭子类型的优势在于它使得代码更加灵活和通用,但有时也需要小心使用,因为它可能会导致一些意外的行为,特别是当对象在运行时缺少了某些预期的行为时。

补充: issubclass 和 isinstance方法

# 【一】检查对象类型 isinstance
print(isinstance('dream',str))
# True
print(isinstance('dream',int))
# False

class People(object):
    ...
p = People()
print(isinstance(People,object))
# True
print(isinstance(p,object))
# True

# 【二】判断一个类是否是一个类的子类的
class Person(object):
    ...
class Student(Person):
    ...
class Teacther:
    ...
print(issubclass(Student,Person))
# True
print(issubclass(Teacther,Person))
# False
print(issubclass(Person,object))
# True
print(issubclass(Teacther,object))
# True