P

Python 面向协议编程 GenAI LIVE! | 2025 年 6 月 4 日

2025-06-10

Python 面向协议编程

GenAI LIVE! | 2025年6月4日

Python 对象🐍

在 Python 中一切都是object

一个对象由以下部分组成:

  • 由 内部标识的内存位置id
  • 确定对象协议的类型(类) 。
  • 对象持有的一个值(或一组值)。

例子:

# Given an object identified by the variable name `a`
>>> a = 1

# We can access its memory location `id`
>>> id(a)
1407854654624

# We can access its type
>>> type(a)
'<class 'int'>'

# We can access its value
>>> a
1

# We can use the behavior defined on its protocol
>>> a + 1
2
>>> a.hex()
'0x1'
Enter fullscreen mode Exit fullscreen mode

上述内容对于Python 中的每个对象都是类似的。

类型🔠

每个对象都基于一种类型,一种类型就是一种class定义。

  • print<- 是function类型
  • "Hello"<- 是str类型
  • 0<- 是int类型
  • [0:5]<- 是一种slice类型
  • str.upper<- 是一种method_descriptor类型
 Function String Integer       Slice
    __⬆️__  __⬆️_ _⬆️_         __⬆️__
    print("Hello"[0] + "World!"[0:5].upper())
                  ___⬆️___            __⬆️___
                   Symbol            Method Descriptor
Enter fullscreen mode Exit fullscreen mode

协议📝

Python 是一种POP语言(面向协议编程)

对象的类型决定了它的实现,它暴露了行为,行为是对象可以做的事情或者可以用对象做的事情。

有些语言称之为对象的特征。

每组能力就是我们所说的协议,协议对于在对象之间设置契约很有用。

识别Hello World程序上的协议:

   Callable  Subscriptable   Sliceable
    __⬆️__  ______⬆️    __________⬆️
    print("Hello"[0] + "World!"[0:5].upper())
             ________⬆️________      __⬆️__
                  Summable         Callable
Enter fullscreen mode Exit fullscreen mode
  • 可调用🗣️可以使用()调用
    当类型的协议包含方法时,该类型也是可调用的__call__

  • 可订阅✍🏻 其元素可以通过订阅访问。
    订阅可以是数字序数[0]或命名键['name']。当类型的协议包含该__getitem__方法时,该类型即为可订阅类型。

  • 可切片🔪 其元素集合可以被切片。
    当一个类型可下标且其__getitem__方法可以接受 而不是或 时,该类型即为可切片slice类型。切片是元素的组合。indexname[start:stop:step]

  • 可求和➕ 可以通过+运算与其他对象组合。
    组合后的乘积始终是新对象。对于数值类型,这指的是将sum两个或多个数字合并成一个集合的能力。对于序列,这指的是将concatenation其片段合并成一个。当类型的协议包含__add____radd__方法时,该类型即为可求和类型。

  • 可打印🖨️可以使用print
    所有 Python 对象进行打印,这些对象print都是可打印的,将查找__repr____str__用于打印对象的方法。

ℹ️ 还有更多,事实上,你可以定义自定义协议,协议非常通用,虽然在打字模块中有一些预定义的协议,但没有官方的协议列表。

from typing import Iterator, Iterable, Optional, Sequence, Awaitable, Generic
Enter fullscreen mode Exit fullscreen mode

完整的协议和子类型列表可在https://mypy.readthedocs.io/en/stable/protocols.html上找到

🦆 协议支持一种称为“鸭子类型”的方法,即在Python中,如果一个对象看起来像鸭子、行为像鸭子并且具有鸭子的行为,那么它就被称为鸭子,不管这是否是一只学会了模仿鸭子嘎嘎叫的的情况🐶。

打字和协议检查

一些协议可以使用内置函数检查

callable(print) is True
callable("Hello") is False
Enter fullscreen mode Exit fullscreen mode

某些协议必须根据其类型类进行检查

isinstance("Hello", str) is True
isinstance(0, slice) is False
Enter fullscreen mode Exit fullscreen mode

在某些情况下,验证协议的唯一方法
是检查其属性。

hasattr("Hello", "__add__") is True  # Summable, we can use `+` operator.
Enter fullscreen mode Exit fullscreen mode

其他地方我们需要使用EAFP模式。

try: 
    "Hello" + 1 
except TypeError: # Strong type checking
    # we cannot `__add__` an `str` to an `int`
Enter fullscreen mode Exit fullscreen mode

打字协议

Python3 提供了一种定义自定义协议的方法

from typing import Protocol, runtime_checkable

@runtime_checkable
class CallableSummableSubscriptable(Protocol):
    def __call__(self) -> T: 
        ...
    def __add__(self, other: T) -> T: 
        ...
    def __getitem__(self, item: T) -> T: 
        ... 
Enter fullscreen mode Exit fullscreen mode

ℹ️ 协议方法只是具有空体的签名,由 表示...T通常是表示泛型类型的类型别名。

协议对于定义契约、函数签名的界限很有用,例如定义一个只有当类型具有指定协议时才接受参数的函数。

def awesome_function(thing: CallableSummableSubscriptable):
    # accepts only objects that implements that ⬆️ protocol.
    # Protocols can be checked at runtime @runtime_checkable
    # Or checked using static analysers e.g: mypy
Enter fullscreen mode Exit fullscreen mode

Python 中有传统的 OOP 吗?

OOP Python 实际上是POP(面向协议编程)

  • 有关协议行为的更多信息
  • 较少涉及传统的 OOP 概念。

所有传统的概念和模式也都可用,但有些是对象和协议所固有的,最终程序员不必以同样的方式去处理它们。

遗产

从另一个基类型继承并采用其所有行为的能力以及使用自定义实现覆盖的能力

class MyString(str)
    def __str__(self):
        return super().__str__().upper()

>>> x = MyString("Bruno")
>>> print(x)
"BRUNO"
Enter fullscreen mode Exit fullscreen mode

封装

能够隐藏对象属性和方法并仅公开其中选定的一组或以更可控的方式公开它们。

# Descriptor is a protocol for getter/setter like approach.
class Field:
    def __get__(...):
    def __set__(...):

class Thing:
    # Double underline means that the field is private
    # but actually it is only a naming mangling convention.
    __protected_attr = Field()

    # Properties can also be used to define getter/setter
    @property
    def foo(self): 
        return self.__protected_attr
    @foo.setter
    def set_foo(self, value): 
        self.__protected_attr = value
Enter fullscreen mode Exit fullscreen mode

多态性

无论对象的基本类型如何,对象都能够表现出不同的行为,并且过程可以将不同类型的对象作为参数。

len("Bruno")
len([1, 2, 3])

dict.get("key")
dict.get("key", default="other")

print("Hello")
print(123)
print(*["Hello", "World"])

def function(*args, **kwargs):
    ...
Enter fullscreen mode Exit fullscreen mode

ℹ️ 在传统的 OOP 文献中,多态性通常仅用于定义重用相同名称但不同实现的方法的能力,但实际上它比这更深入。

结论

🧑‍🍳 厨师在为菜谱选择食材时,会查看每种食材上定义的协议,厨师可以去超市看到一组不同类型的洋葱🧅,即使菜谱上只说洋葱,厨师也会根据所需的行为知道需要特定类型的洋葱,白洋葱更甜,更适合煮熟的食谱,紫洋葱更酸,因此更适合沙拉和生食食谱。(但这也取决于口味)

🧑‍💻 软件工程师在选择算法中使用的数据结构时🤖必须查看定义的协议以及对象的行为,即使要求说它是文本的集合,例如,工程师必须分析程序以明智地在元列表甚至集合之间进行选择。

协议通常比类型更重要。

:感谢@mathsppblog 启发了本文的第一段https://twitter.com/mathsppblog/status/1445148609977126914

鏂囩珷鏉ユ簮锛�https://dev.to/rochacbruno/python-protocol-orient-programming-1m0g
PREV
Github 正在替换“master”一词,以避免出现“奴隶”的现象。处理一下吧。
NEXT
🎬如何制作登录和注册表单 | HTML CSS 和 Vanilla JavaScript✨