python中的鸭子类型详解

Written by

in

文章目录
  • 鸭子类型的名字来源于一句著名的谚语: “如果它走起路来像鸭子,叫起来也像鸭子,那么它就可以被当做鸭子。” 翻译成编程语言就是: 一个对象的类型,不是由它继承自哪个类决定的,而是由它拥有的方法和属性(它的“行为”)决定的。 换句话说,我们并不关心对象本身是什么类型(is-a关系),我们只关心这个对象能做什么(has-a关系)。
  • 为了更好地理解鸭子类型,我们先看一个静态类型语言(比如 Java)的例子。 // 首先,我们必须定义一个接口,规定“鸭子”必须有哪些行为。 interface Duck { void quack(); void walk(); } // 然后,一个类必须显式地声明实现这个接口。 class RealDuck implements Duck { public void quack() { System.out.println(“Quack!”); } public void walk() { System.out.println(“Waddle waddle.”); } } class Person { // 这个方法只接受实现了 Duck 接口的对象 public void makeDuckAct(Duck duck) { duck.quack(); duck.walk(); } } // 使用 Person person = new Person(); RealDuck duck = new RealDuck(); person.makeDuckAct(duck); // 正常工作 在 Java 中,如果你想将一个对象传给 makeDuckAct 方法,它必须显式地 implements Duck。编译器在编译时就会检查这一点。 Python 方式(鸭子类型): # 我们不需要让任何类实现一个特定的接口。 class RealDuck: def quack(self): print(“Quack!”) def walk(self): print(“Waddle waddle.”) class ToyDuck: # 这是一个玩具鸭,它和 RealDuck 没有任何继承关系 def quack(self): # 但它有 quack 方法 print(“Squeak!”) def walk(self): # 它也有 walk 方法 print(“Click clack.”) class Person: # 这个函数不关心传入的 obj 是什么类 # 它只关心这个 obj 有没有 quack 和 walk 方法 def make_duck_act(self, obj): obj.quack() obj.walk() # 使用 person = Person() real_duck = RealDuck() toy_duck = ToyDuck() person.make_duck_act(real_duck) # 输出: Quack! n Waddle waddle. person.make_duck_act(toy_duck) # 输出: Squeak! n Click clack. 在 Python 中,Person.make_duck_act 方法没有要求 obj 必须是 RealDuck 类型。它只是尝试去调用 obj.quack() 和 obj.walk()。 只要传入的对象有这两个方法,代码就能正常运行。 如果没有,Python 会在运行时抛出一个 AttributeError 异常。 这就是鸭子类型的精髓:关注接口(行为),而非实现(类型)。
  • 鸭子类型是 Python 如此灵活和强大的原因之一,它渗透在语言的各个角落。 经典例子 1:len() 函数 len() 函数不关心你传给它的对象是列表、字符串、字典还是你自己的类。它只关心这个对象是否实现了 __len__() 方法。 class MyCustomCollection: def __len__(self): return 10 my_list = [1, 2, 3] my_str = “Hello” my_dict = {‘a’: 1} my_obj = MyCustomCollection() print(len(my_list)) # 3 print(len(my_str)) # 5 print(len(my_dict)) # 1 print(len(my_obj)) # 10 # 它们都能工作!因为都“表现得”像一个有长度的对象。 经典例子 2:for 循环 for 循环不关心你遍历的是列表、元组、文件对象还是你自己的类。它只关心这个对象是否是可迭代的,即是否实现了 __iter__() 方法(或 __getitem__() 方法)。 class MyRange: def __init__(self, start, end): self.start = start self.end = end def __iter__(self): current = self.start while current < self.end: yield current current += 1 for i in MyRange(0, 3): print(i) # 输出: 0 n 1 n 2 # MyRange 并不是 list 或 range,但它实现了 __iter__,所以可以被 for 循环遍历。 经典例子 3:上下文管理器 (with 语句) with 语句不关心你的对象是什么,只关心它是否实现了 __enter__() 和 __exit__() 方法。 class MyFile: def __enter__(self): print(“Opening file…”) return self def __exit__(self, exc_type, exc_val, exc_tb): print(“Closing file…”) def read(self): print(“Reading data…”) with MyFile() as f: f.read() # 输出: # Opening file… # Reading data… # Closing file…
  • 优点: 极大的灵活性:代码高度解耦。只要对象的行为一致,它们就可以互换使用,无需复杂的继承体系。 促进多态:无需通过继承来获得多态性,任何对象只要实现了所需的方法,就能参与进来。 代码简洁:不需要定义大量的接口和抽象基类。 缺点: 运行时错误:由于类型检查是在运行时进行的,如果传入的对象缺少某个方法,程序会直接崩溃。而在静态语言中,这种错误在编译时就能被发现。 文档和可读性:对于一个函数,你很难直接从签名 def func(obj): 看出它期望 obj 具有哪些方法和属性。这非常依赖于文档、注释和命名约定。 IDE 支持弱:IDE 很难对基于鸭子类型的代码进行智能提示和自动补全,因为它无法确定传入的对象具体有哪些方法。
  • 为了缓解鸭子类型的缺点,Python 社区也发展出一些最佳实践和工具: 详细的文档和类型提示: from typing import Protocol # 定义一个“协议”(接口),但这不是强制性的 class DuckLike(Protocol): def quack(self) -> None: … def walk(self) -> None: … class Person: # 使用类型提示表明我们期望一个“像鸭子”的对象 def make_duck_act(self, obj: DuckLike) -> None: obj.quack() obj.walk() 现代 IDE 和类型检查工具(如 mypy)可以识别这种提示,并提供更好的支持和错误检查。 使用 hasattr() 或 try-except 进行防御性编程: def make_duck_act_safe(self, obj): if hasattr(obj, ‘quack’) and hasattr(obj, ‘walk’): obj.quack() obj.walk() else: print(“This object is not duck-like enough!”) # 或者更“Pythonic”的方式:EAFP (Easier to Ask for Forgiveness than Permission) def make_duck_act_safe_eafp(self, obj): try: obj.quack() obj.walk() except AttributeError as e: print(f”This object is missing a duck behavior: {e}”)
  • 鸭子类型是 Python 动态类型系统的灵魂。它通过强调 “行为” 而非 “类型”,赋予了代码极大的灵活性和表现力。理解并善用鸭子类型,是写出地道、强大 Python 代码的关键一步。它要求程序员更多地依赖清晰的约定和文档,而不是编译器的强制检查。 到此这篇关于python中的鸭子类型的文章就介绍到这了,更多相关python鸭子类型内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客! 您可能感兴趣的文章: Python浅析多态与鸭子类型使用实例 python 多态 协议 鸭子类型详解 通过案例解析python鸭子类型相关原理 Python动态语言与鸭子类型详解 详细介绍Python的鸭子类型 详解duck typing鸭子类型程序设计与Python的实现示例
  • 目录
    • 1. 核心思想:什么是鸭子类型?
    • 2. 与“传统”静态类型语言的对比
    • 3. Python 中无处不在的鸭子类型
    • 4. 鸭子类型的优缺点
    • 5. 弥补鸭子类型的不足
    • 总结

    鸭子类型的名字来源于一句著名的谚语:

    “如果它走起路来像鸭子,叫起来也像鸭子,那么它就可以被当做鸭子。”

    翻译成编程语言就是:

    一个对象的类型,不是由它继承自哪个类决定的,而是由它拥有的方法和属性(它的“行为”)决定的。

    换句话说,我们并不关心对象本身是什么类型(is-a关系),我们只关心这个对象能做什么has-a关系)。

    为了更好地理解鸭子类型,我们先看一个静态类型语言(比如 Java)的例子。

    // 首先,我们必须定义一个接口,规定“鸭子”必须有哪些行为。
    interface Duck {
        void quack();
        void walk();
    }
    // 然后,一个类必须显式地声明实现这个接口。
    class RealDuck implements Duck {
        public void quack() { System.out.println("Quack!"); }
        public void walk() { System.out.println("Waddle waddle."); }
    }
    class Person {
        // 这个方法只接受实现了 Duck 接口的对象
        public void makeDuckAct(Duck duck) {
            duck.quack();
            duck.walk();
        }
    }
    // 使用
    Person person = new Person();
    RealDuck duck = new RealDuck();
    person.makeDuckAct(duck); // 正常工作

    在 Java 中,如果你想将一个对象传给 makeDuckAct 方法,它必须显式地 implements Duck。编译器在编译时就会检查这一点。

    Python 方式(鸭子类型):

    # 我们不需要让任何类实现一个特定的接口。
    class RealDuck:
        def quack(self):
            print("Quack!")
        def walk(self):
            print("Waddle waddle.")
    class ToyDuck: # 这是一个玩具鸭,它和 RealDuck 没有任何继承关系
        def quack(self): # 但它有 quack 方法
            print("Squeak!")
        def walk(self): # 它也有 walk 方法
            print("Click clack.")
    class Person:
        # 这个函数不关心传入的 obj 是什么类
        # 它只关心这个 obj 有没有 quack 和 walk 方法
        def make_duck_act(self, obj):
            obj.quack()
            obj.walk()
    # 使用
    person = Person()
    real_duck = RealDuck()
    toy_duck = ToyDuck()
    person.make_duck_act(real_duck) # 输出: Quack! n Waddle waddle.
    person.make_duck_act(toy_duck)  # 输出: Squeak! n Click clack.

    在 Python 中,Person.make_duck_act 方法没有要求 obj 必须是 RealDuck 类型。它只是尝试去调用 obj.quack() 和 obj.walk()

    • 只要传入的对象有这两个方法,代码就能正常运行。
    • 如果没有,Python 会在运行时抛出一个 AttributeError 异常。

    这就是鸭子类型的精髓:关注接口(行为),而非实现(类型)。

    鸭子类型是 Python 如此灵活和强大的原因之一,它渗透在语言的各个角落。

    经典例子 1:len() 函数

    len() 函数不关心你传给它的对象是列表、字符串、字典还是你自己的类。它只关心这个对象是否实现了 __len__() 方法。

    class MyCustomCollection:
        def __len__(self):
            return 10
    my_list = [1, 2, 3]
    my_str = "Hello"
    my_dict = {'a': 1}
    my_obj = MyCustomCollection()
    print(len(my_list))  # 3
    print(len(my_str))   # 5
    print(len(my_dict))  # 1
    print(len(my_obj))   # 10
    # 它们都能工作!因为都“表现得”像一个有长度的对象。

    经典例子 2:for 循环

    for 循环不关心你遍历的是列表、元组、文件对象还是你自己的类。它只关心这个对象是否是可迭代的,即是否实现了 __iter__() 方法(或 __getitem__() 方法)。

    class MyRange:
        def __init__(self, start, end):
            self.start = start
            self.end = end
        def __iter__(self):
            current = self.start
            while current < self.end:
                yield current
                current += 1
    for i in MyRange(0, 3):
        print(i)
    # 输出: 0 n 1 n 2
    # MyRange 并不是 list 或 range,但它实现了 __iter__,所以可以被 for 循环遍历。

    经典例子 3:上下文管理器 (with 语句)

    with 语句不关心你的对象是什么,只关心它是否实现了 __enter__() 和 __exit__() 方法。

    class MyFile:
        def __enter__(self):
            print("Opening file...")
            return self
        def __exit__(self, exc_type, exc_val, exc_tb):
            print("Closing file...")
        def read(self):
            print("Reading data...")
    with MyFile() as f:
        f.read()
    # 输出:
    # Opening file...
    # Reading data...
    # Closing file...

    优点:

    • 极大的灵活性:代码高度解耦。只要对象的行为一致,它们就可以互换使用,无需复杂的继承体系。
    • 促进多态:无需通过继承来获得多态性,任何对象只要实现了所需的方法,就能参与进来。
    • 代码简洁:不需要定义大量的接口和抽象基类。

    缺点:

    • 运行时错误:由于类型检查是在运行时进行的,如果传入的对象缺少某个方法,程序会直接崩溃。而在静态语言中,这种错误在编译时就能被发现。
    • 文档和可读性:对于一个函数,你很难直接从签名 def func(obj): 看出它期望 obj 具有哪些方法和属性。这非常依赖于文档、注释和命名约定。
    • IDE 支持弱:IDE 很难对基于鸭子类型的代码进行智能提示和自动补全,因为它无法确定传入的对象具体有哪些方法。

    为了缓解鸭子类型的缺点,Python 社区也发展出一些最佳实践和工具:

    详细的文档和类型提示

    from typing import Protocol
    # 定义一个“协议”(接口),但这不是强制性的
    class DuckLike(Protocol):
        def quack(self) -> None: ...
        def walk(self) -> None: ...
    class Person:
        # 使用类型提示表明我们期望一个“像鸭子”的对象
        def make_duck_act(self, obj: DuckLike) -> None:
            obj.quack()
            obj.walk()

    现代 IDE 和类型检查工具(如 mypy)可以识别这种提示,并提供更好的支持和错误检查。

    使用 hasattr() 或 try-except 进行防御性编程:

    def make_duck_act_safe(self, obj):
        if hasattr(obj, 'quack') and hasattr(obj, 'walk'):
            obj.quack()
            obj.walk()
        else:
            print("This object is not duck-like enough!")
    # 或者更“Pythonic”的方式:EAFP (Easier to Ask for Forgiveness than Permission)
    def make_duck_act_safe_eafp(self, obj):
        try:
            obj.quack()
            obj.walk()
        except AttributeError as e:
            print(f"This object is missing a duck behavior: {e}")

    鸭子类型是 Python 动态类型系统的灵魂。它通过强调 “行为” 而非 “类型”,赋予了代码极大的灵活性和表现力。理解并善用鸭子类型,是写出地道、强大 Python 代码的关键一步。它要求程序员更多地依赖清晰的约定和文档,而不是编译器的强制检查。

    到此这篇关于python中的鸭子类型的文章就介绍到这了,更多相关python鸭子类型内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客!

    您可能感兴趣的文章:

    • Python浅析多态与鸭子类型使用实例
    • python 多态 协议 鸭子类型详解
    • 通过案例解析python鸭子类型相关原理
    • Python动态语言与鸭子类型详解
    • 详细介绍Python的鸭子类型
    • 详解duck typing鸭子类型程序设计与Python的实现示例

    站内搜索