当前位置: 首页 > article >正文

79、Python之鸭子类型:没有听过鸭子类型?关键在于认知的转变

引言

不同于Java等静态类型的语言,Python基于动态类型系统的设计理念,使得Python在很多应用场景中,显得更急灵活、高效。而在动态类型系统中,有一个很重要的概念,就是“鸭子类型”。鸭子类型的背后,代表的是一些编程认知方式的转变,是对“协议”、“行为”与类型之间关系的更加深入的理解。

本文的主要内容有:

1、什么是鸭子类型

2、简单对比Python与Java的类型系统

3、鸭子类型的应用

4、鸭子类型的注意事项

什么是鸭子类型

所谓“鸭子类型(Duck Typing)”,是一种动态类型系统的编程概念。其核心思想来自于詹姆斯·惠特科姆·莱利的鸭子测试:“如果它走起来像鸭子,叫起来像鸭子,那么它就是鸭子”。

换句话说,在鸭子类型中,对象的有效性不依赖于其显式的类型,而是依赖于对象是否具有所需要的属性和方法。

可以粗略的理解为,所谓的实体的强类型,更像是一种标签。而我们在实际业务场景中,需要的是某些能力、行为。所以,只要某个实体对象,具备了我们所需要的能力,能够在系统交互中实现所需要的行为,那么至于这个实体对象被标记为什么类型,其实并不是很重要。

以“捉老鼠”的场景为例,我们最根本的诉求是不被老鼠所影响,想到的方法,或者需要具备的能力是“能够把老鼠捉起来”。至于是白猫、黑猫、多管闲事的狗,又或者是有些屠龙技的人,只要能达到捉到老鼠的目的即可。

所以,本质上来说,在动态类型系统中,我们所关注的不再是静态类型系统中,一个实体对象所属的类型,而是这个实体对象所具备的属性和方法。而所应该具备的属性和方法,更加抽象来说,叫做“协议”。所以,我们的关注视角,从具体类型转换到了协议。能理解了这一个认知的转换,就已经理解了鸭子类型的本质。

简单对比Python与Java的类型系统

如果有同学比较熟悉Java,应该能够深刻地感受到,在Java的编程世界中,类型系统是静态的,类型检查是在编译时进行的,并且对象的类型是显式声明的。这样的好处在于,有些错误可以在编译期就能提早发现。但是,也会导致更多的强制类型转换、一大堆样板代码等。

在Java中,如果我们定义不同动物,定义一个函数接收具体的动物对象,发出不同的叫声,我们大概需要这样做:

package com.study;

interface Animal {
    void bark();
}
class Dog implements Animal {
    @Override
    public void bark() {
        System.out.println("小狗汪汪汪");
    }
}
class Duck implements Animal {
    @Override
    public void bark() {
        System.out.println("鸭子嘎嘎嘎");
    }
}
public class Zoo {
    public static void make_sound(Animal animal) {
        animal.bark();
    }

    public static void main(String[] args) {
        Dog dog = new Dog();
        make_sound(dog);
        Duck duck = new Duck();
        make_sound(duck);

    }
}

需要先有一个表示动物的类型,可以是一个接口或者抽象类,用于指向具体的动物子类型的对象。

定义对象,还要明知道类型,再手动显式声明类型。

运行结果:

27db4bd6bdd45bcbe34996614c493b9e.jpeg

这是一套标准的面向对象的继承的设计实现,也确实达到了我们想要的效果。

在Python中,我们也可以通过面向对象的继承的思路,来实现同样的效果。但是,由于Python是动态类型的,我们还可以有更简化的实现方式:

class Dog:
    def bark(self):
        print('小狗汪汪汪')


class Duck:
    def bark(self):
        print('鸭子嘎嘎嘎')


def make_sound(animal):
    animal.bark()


if __name__ == '__main__':
    dog = Dog()
    make_sound(dog)
    duck = Duck()
    make_sound(duck)

从代码行数的角度,依然能看到更加简洁。

从设计实现上,Dog和Duck两个类型没有任何继承关系(当然都是object类的子类,这点这里可以忽略)。它们定义了同样的方法bark(),所以在进行函数make_sound()调用时,只要传入的对象实例具有bark()方法,就能成功执行。

执行结果:

0f60d9a531c88f08dc9aa2eb9dcea8fb.jpeg

从上面的对比可以看出,Python中,类型的检查是运行时进行的,类型是隐式的。Python中的鸭子类型的存在,允许我们更加专注于对象的行为,而非其必须是某个特定类型,或者繁琐地进行类型继承关系的设计。

鸭子类型的应用

1、编程的灵活性和多态性

鸭子类型的引入,首先给我们提供了一种新的“多态”的思路,我们不需要进行类的继承体系的设计,不需要考虑抽象出具有特定功能的公共父类,只需要定义同样的行为方法即可。这种实现多态的方式,似乎是一种更加自然的方式。

2、代码的简洁性

鸭子类型减少了类型检查和类型转换的需求,从而使得代码更加简洁易读。在Java中,通常需要通过强制转换来实现多态行为。而在Python中,这些都变得不是必要的。

3、兼容性和扩展性

鸭子类型使得代码更加具有兼容性和可扩展性。之所以这样说,是因为,我们可以随时定义新的对象类型,只要定义特定的方法,就可以将新的类型的对象,传递给一个现有的函数。这样,使得对现有功能的扩展变得更加灵活、方便。

鸭子类型的注意事项

需要说明的是,虽然鸭子类型是我们接下来几篇文章的主角,但是还是有一些潜在的需要注意的事项:

1、运行时错误

由于类型检查被推迟到了运行时进行,所以,有些错误只有在实际运行代码时,才会被发现。所以,良好的测试变得更加重要。

2、代码的可读性

对于新手或者不熟悉代码库的开发者来说,鸭子类型在一定程度上会导致代码可读性的降低。所以,适当的注释和文档说明,也变得更加重要了。

总结

本文重点介绍了“鸭子类型”背后的核心理念,专注于行为而非类型本身。同时,对比了Java和Python这两种语言所代表的类型系统的差别,尤其在多态实现上的差异。尽管“鸭子类型”赋予了Python极大的灵活性和简洁性,但是,还是需要注意这些有点背后潜在的风险。

感谢您的拨冗阅读,希望对您有所帮助。

678e6f4ab958e369d1ba505a781d2f9c.jpeg


http://www.kler.cn/a/318676.html

相关文章:

  • Mysql数据库里的SSH连接
  • vue请求数据报错,设置支持跨域请求,以及2种请求方法axios或者async与await
  • 三维测量与建模笔记 - 特征提取与匹配 - 4.2 梯度算子、Canny边缘检测、霍夫变换直线检测
  • 【自用】0-1背包问题与完全背包问题的Java实现
  • 【HarmonyOS NEXT】一次开发多端部署(以轮播图、Tab栏、列表为例,配合栅格布局与媒体查询,进行 UI 的一多开发)
  • 【练习案例】30个 CSS Javascript 加载器动画效果
  • 网络安全-长亭雷池waf的sql绕过,安全狗绕过(5种绕过3+2)
  • 安科瑞Acrel-1000DP分布式光伏监控系统在鄂尔多斯市鄂托克旗巴音乌苏六保煤矿5MW分布式光伏项目中的应用
  • [linux][证书]证书导出公钥
  • MySQL记录存储过程执行的错误信息
  • 改进拖放PDF转换为图片在转换为TXT文件的程序
  • 浅谈C++之多线程实现
  • 口语训练材料
  • 力扣【283-移动零】【数组-C语言】
  • 微服务之服务保护
  • git checkout -b dev origin/dev
  • golang cmd.exec 执行命令后报错 No such file or directory
  • 最优化理论与自动驾驶(二-补充):求解算法(梯度下降法、牛顿法、高斯牛顿法以及LM法,C++代码)
  • Java-数据结构-排序(三) |ू・ω・` )
  • 【网络安全】密码学的新进展
  • Nginx 如何开启压缩
  • 伊犁云计算22-1 rhel8 dhcp 配置
  • YOLOv10改进,YOLOv10主干网络替换为VanillaNet( CVPR 2023 华为提出的全新轻量化架构),大幅度涨点
  • 操作系统知识3
  • 华为全联接大会HUAWEI Connect 2024印象(一):OpenEuler
  • uniapp沉浸式导航栏+自定义导航栏组件