4

[EuroPython 2021笔记] Python 3.10新功能开发者亲述:模式匹配案例实战

 2 years ago
source link: https://blog.csdn.net/juwikuang/article/details/120700866
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

[EuroPython 2021笔记] Python 3.10新功能开发者亲述:模式匹配案例实战

有数可据 2021-10-11 12:58:19 82
同时被 2 个专栏收录
7 篇文章 1 订阅

模式匹配是Python 3.10的新特性,EuroPython请来了这个功能的贡献者之一,Daniel Moisset,为我们介绍模式匹配。

When Python 3.10 comes out in October 2021, it will include a new feature called “Structural Pattern Matching”. Structural Pattern Matching has been a staple of functional programming languages, but it has recently appeared or been proposed as future additions to imperative and OOP programming languages (like JavaScript, C++, and Rust). It provides an elegant way of processing and deconstructing heterogeneous data structures, and it enables an alternative paradigm to method dispatch.

As one of this feature’s contributors, my goals in this presentation are to describe the motivation for this new functionality, present some of the problems that its use will effectively solve, and give a brief introduction of how to use it.

This talk is aimed at intermediate Python developers (although a beginner will be able to understand it). After attending you should be able to understand not just how to use pattern matching, but also when it’s a good idea to use it and what are the possible pitfalls to avoid.

There will be a Q&A session which could be a good chance to discuss your questions about why certain design decisions were made when introducing pattern matching into Python

Type: Talk (30 mins);

Python level: Intermediate;

Domain level: Beginner

当Python 3.10发布的时候(实际上,2021年10月4日已经发布了),它将会包含一个新的特性,叫Structural Pattern Matching(结构模式匹配)。Structural Pattern Matching 是函数编程语言的主要部分,但是最近它出现在了Javascript, C++, Rust等命令式或面向对象的语言中。它提供了一种优美地处理和解构各种各样地数据结构地方法。也为我们解决问题提供了另一种思路。

作为这个特性地贡献者,我这次演讲的目标是介绍开发这个新特性的动机,呈现一些这个新功能可以高效解决的问题,并简短介绍如何使用。

这次的介绍目标听众是中级Python程序员,初级程序员也应该能听懂。听完讲座以后,你应该能懂得如何使用模式匹配,已经什么时候应该使用它,并避免一些可能的陷阱。

之后会有提问环节,我们可以讨论如果用模式匹配设计Python代码。

类型:演讲(30分钟)

Python 水平:中级

领域水平:初级

I’m a Latin American expat in London. After 20ys of background as an entrepreneur, software engineer and project leader, I’m currently training engineers. Most of my professional career has been marked by a love for Python and good engineering. I’m deeply interested in compiler and language design, and places were CS theory and engineering practice touch each other closely.

Non professionally, I’m a bad piano player, a husband a Dungeon Master, and a cat herder.

Preferred pronouns are he/him.

我是在伦敦的拉丁美洲人。有20年的从业经验,办过企业,写过代码,带领过团队。我现在培训工程师。我的职业生涯的主要部分都伴随着我对Python的热爱。我深深的热爱编译器,语言设计,计算机理论与工程实践的结合。

业余爱好有弹钢琴,玩龙与地下城,撸猫。

请称呼我he/him。

(Daniel说了十分钟的动机,我总结如下。)

各种各样的数据结构,就像是一个有各种各样的小物件迷宫。他们可以是方形的,圆形的,十字形的,甜甜圈形的。他们可是白的,黑的,红的,蓝的。他们可以在迷宫的东边,西边,南边,北边。他们或大如泰山,或小如芝麻。我们要找出我们想要的形状,就要用模式匹配。

而我们做模式匹配的时候,往往在匹配的同时,也要提取出各种信息。比如,我们要提取长方形的长和宽,我们要提取原型的半径,我们要提取甜甜圈的内半径和外半径等。我们需要把这些信息,提取到新的变量中。

这样,我们就有了模式匹配。

在这里插入图片描述

模式匹配,Python里面用match/case语句实现。其语法如下:

match <subject>:
    case <pattern 1>:
    	<code block 1>
    case <pattern 2> if <guard>:
    	<code block 2>
    case <pattern 3>:
    	<code block 3>

首先,是match语句,match后面就是被匹配的主体。主体可以是一个值,一个变量,一个函数,一个list,或者任何的Python表达式。

match后面是诺干个case语句,case关键字后面就是模式。我们这里有pattern 1, pattern 2, pattern 3。case后面,就是具体的代码了。

模式后面是guard,也就是说,模式就算提取了,后面的代码也未必执行。还要通过guard检查。而guard检查,也是依赖于前面的模式提取出来的变量的。

我们来看一些具体的例子。

最简单的例子

resp = get_user_info(uuid)
match resp.status:
	case 200:
		process_response(resp.json)
	case 403:
		raise InvalidUser
	case 301:
		process_redirect(resp.location)
	case code if 500 <= code <= 599:
		raise ServerError
	case _:
		raise InvalidStatus

以上是一个最简单的例子,假如我们有个爬虫,我们获取爬虫的页面返回状态为resp.status。那么我们根据返回状态的不同,有不同的分支去处理。

我们看到模式匹配的对象在match后面,这里是resp.status,而模式则在case后面。

我们看到前三个模式分别是200, 403 和 301三个字面常量(literal)。它表示,如果resp.status等于他们,那么就匹配上了。

第四个case:

case code if 500 <= code <= 599:

这里,先做了提取,把resp.status提取到code中,然后,判断code是否介于500和599之间。

最后一个case,模式直接写了一个下划线。我们都知道下划线在Python里面通常表示这个变量不用保存,我不感兴趣。也就是说,把resp.status提取到下划线,然后丢掉。因为这里已经没有if条件了,所以这里一定能匹配上。它相当于“其他”的意思。也就是说,如果前面的都匹配不上,就匹配这个。它相当于有些类c语言的switch语句里面的default。

Slicer

假如,我们要写一个slicer

Usage: slicer(seq, [start], stop, [step])
slicer(seq, stop) → seq[:stop]
slicer(seq, start, stop) → seq[start:stop]
slicer(seq, start, stop, step) → seq[start:stop:step]

让我们实现它:

def slicer(seq, start=0, stop, step=1):
    ...

糟了,报错了:

SyntaxError: non-default argument follows default argument

这时,你可以用模式匹配。

def slicer(seq, *args):
    match args:
        case [stop]: return seq[:stop]
        case [start, stop]: return seq[start:stop]
        case [start, stop, step]: return seq[start:stop:step]
        case _: raise TypeError("invalid arguments")

这里模式匹配的好处就是,可以根据参数的多少,判断出每个参数的含义。

FizzBuzz

FizzBuzz是一个儿童益智游戏。从1到无穷数数。数到三的倍数,我们不说三,而是喊Fizz。数到五的倍数,我们不喊五,而是喊Buzz。如果是又是三的倍数,又是五的倍数,我们就要喊Fizzbuzz。

举例如下:

1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
Fizzbuzz
16
17
Fizz
19
Buzz
Fizz
22
23
Fizz
Buzz
26
Fizz
28
29
Fizzbuzz

如果没有模式匹配,我们将会写一推的if…else…,如果用模式匹配,就会简化很多。这里,一共有四种情况,Fizzbuzz, Fizz, Buzz, 以及其他。

for i in range(1, 101):
    match (i % 3 == 0), (i % 5 == 0):
        case True, True: print("Fizzbuzz")
        case True, _: print("Fizz")
        case _, True: print("Buzz")
        case _: print(i)

Web API

假如,我们的Web API返回了一段json,我们可以用模式匹配来出路。

resp = get_user_info(uuid).json
match resp:
    case {"error": msg}:
    	raise APIError(msg)
    case {"user": {"name": n, "dob": d}} if is_birthday(d):
    	render(f"Happy Birthday {n}! 🥳")
    case {"user": {"name": n}}:
    	render(f"Welcome {n}!")
    case _:
    	raise APIError("Unexpected get_user_info response")

如果API报错了,那么json里面会有一个error关键字,我们可以匹配到这个关键字,并把error的值提取到变量msg里面。我们抛出异常APIError。

如果是匹配到一个今天过生日的用户,则返回生日快乐。

否则,只返回欢迎光临。

如果是其他情况,则抛出API Error。

模式匹配 vs 设计模式

如果我们要计算各种形状的面积,我们可以写一个函数,根据模式匹配,知道是什么形状,然后计算面积。这是函数式编程的思路。我们也可以把计算面积的函数写到每一个类里面。这就是OOP的思路了。

def area(s: Shape):
    match s:
        case Rectangle(w, h): return w * h
        case Square(side): return side ** 2
        case Circle(r): return math.pi * (r ** 2)
        case _: raise InvalidShape
            
# versus
class Circle(Shape):
    def area(self):
    	return math.pi * self.radius ** 2

函数式编程是“Dumb values and smart functions”。即数据结构就是单纯的数据结构,数据结构或者类没有方法method,逻辑都在函数里。函数式编程和OOP各有利弊。如果,你向增加一个新的方法。在OOP中,你需要修改每个类。而在函数式编程时,你只需要修改一个地方。模式匹配并不一定就比OOP好,它只是为我们提供了另一种解决问题的方案。

模式匹配 vs switch语句

switch通常建立在字面常量之上。而模式匹配则要强大的多。它可以提取参数到变量中。

各种模式匹配

表1:简单匹配

NoPatternMatches IfExtracts1123obj == 123-2Trueobj is True-3os.SEEK_ENDobj == os.SEEK_END-4x(always!)x = obj5-(always!)-6Duck()ininstance(obj, Duck)-

上面的三个例子,都是常量匹配。注意,常量为True时,匹配的规则时obj is True,如果obj是1,则会匹配失败。

例4中的x,可以匹配任何值。并将其赋值给x。
例5也可以匹配任何值,但是不会赋值,因为下划线赋值会被抛弃掉。
例6匹配类型为Duck的值。

表2:复合模式匹配

NoPatternMatches IfExtracts7(123, a)obj is a sequence
len(obj) == 2
obj[0] == 123a = obj[1]8[int(), *mid, int()]obj is a sequence
len(obj) >= 2
isinstance(obj[0], int)
isinstance(obj[-1], int)mid = obj[1:-1]9{“name”: n, “age”: a}obj is a mapping
"name" in obj
"age" in objn = obj[‘name’]
a = obj[“age”]

例7,obj是一个序列。长度为2,第一个值为123,第二个值赋值给了a。
例8,obj是一个序列。长度大于2。第一个和第三个值为int。中间的值都赋值给mid。
例9,obj是一个mapping。它有name和age两个关键字,其对应的值分别赋值给n和a。

表3:或者和as

NoPatternMatches IfExtracts10200 | 404 | 500obj in [200, 400, 500]-11[bool()|“yes”|“no” as flag, *_]obj is a sequence
len(obj) >= 1
isinstance(obj[0], bool) or
obj[0] in [“yes”, “no”]flag = obj[0]12[x] | {“value”: x}|x(always!)one of
x = obj[0]
x = obj[“value”]
x = obj

例10,obj是200,400和500中的一个。
例11,obj是一个序列。第一个值是bool型,或者是文字yes, no中的一个。我们这里使用as,将其赋值给flag。序列中的其他值,赋值给了下划线。
例12,obj是三种模式中的一个。第一个模式,obj是一个只有一个元素的列表,则此元素赋值给x。第二个模式,x是一个mapping,那么我们找到value,并赋值给x。第三个模式,直接把obj赋值给x。

表4: 类匹配

NoPatternMatches IfExtracts13Duck(color = “green”)isinstance(obj, Duck)
hasattr(obj, “color”)
obj.color == “green”-14Duck(color = “green”, age=a)isinstance(obj, Duck)
hasattr(obj, “color”)-15Duck(mom=Duck(age=a))isinstance(obj, Duck)
hasattr(obj, “mom”)
isinstance(obj.mom, Duck)
hasattr(obj.mom, “age”)a = obj.mom.age

例13,obj是一个Duck类的实例。并且,其color属性的值为“green”。
例14,obj是一个Duck类的实例。其color属性的值为“green”。并将其age赋值给a。
例15,obj是一个Duck类的实例。其属性mom,也是一个Duck的实例。我们这里获取mom的age,并赋值给a。

你可以看看官方文档。

PEP-636是模式匹配学习指南。PEP-635是模式匹配的动机。PEP-634则是详细说明。

[EuroPython 2021] Pattern Matching in Python:
https://ep2021.europython.eu/talks/5tcgQpi-pattern-matching-in-python/
视频(优酷):
https://v.youku.com/v_show/id_XNTgxMjMxOTA1Mg==.html


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK