Python中有趣的Ellipsis对象

开发 后端
在你输入了三个点之后,Python 解释器非但不会报错,反而还会返回给你「Ellipsis」这么一个信息。那么这个有趣的东西是什么呢?来看一下吧。

[[335494]]

 什么是Ellipsis

在 Python 中你可能有时候会看到一个奇怪的用法,就像是这样: 

  1. >>> ...  
  2. Ellipsis 

在你输入了三个点之后,Python 解释器非但不会报错,反而还会返回给你「Ellipsis」这么一个信息。那么这个有趣的东西是什么呢?

查阅 Python 官方文档后可以看到,它是一个**「内置常量」**(Built-in Constant)。经常用于对用户自定义的容器数据类型进行切片用法的扩展。

这也就意味着它可能是会作为一个「小众且另类」的语法糖来使用,但如果你用于 Python 中的容器数据类型(比如列表)进行切片索引时,可能会引发错误: 

  1. >>> nums = list(range(10))  
  2. >>> nums  
  3. [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]  
  4. >>> nums[...]  
  5. Traceback (most recent call last):  
  6.   File "<stdin>", line 1, in <module>  
  7. TypeError: list indices must be integers or slices, not ellipsis 

除此之外,如果你使用的是 Python 2 的解释器,那么压根就不支持 Ellipsis 的用法,从一开始输入时就报错: 

  1. $ python2  
  2. WARNING: Python 2.7 is not recommended.   
  3. This version is included in macOS for compatibility with legacy software.   
  4. Future versions of macOS will not include Python 2.7.   
  5. Instead, it is recommended that you transition to using 'python3' from within Terminal.  
  6. Python 2.7.16 (default, Nov  9 2019, 05:55:08)   
  7. [GCC 4.2.1 Compatible Apple LLVM 11.0.0 (clang-1100.0.32.4) (-macos10.15-objc-s on darwin  
  8. Type "help", "copyright", "credits" or "license" for more information.  
  9. >>> ...  
  10.   File "<stdin>", line 1  
  11.     ...  
  12.     ^  
  13. SyntaxError: invalid syntax 

虽然说在列表中使用 Ellipsis 会报错,但是碰到这种情况你会发现解释器返回给你的是这样的东西: 

  1. >>> nums = [1,2,3]  
  2. >>> nums  
  3. [1, 2, 3]  
  4. >>> nums[1] = nums  
  5. >>> nums  
  6. [1, [...], 3] 

可以看到,这里我们将 nums 中的第二个元素替换成自身,就会形成不断地递归嵌套赋值,而解释器最后直接给出了头尾两个元素之外,其他全部元素都会被 ... 所囊括在内。

根据 Python 官方的另一处文档,Ellipsis 本身也不支持任何操作,仅仅只是一个单例对象(Singleton)

谁能想到,Guido van Rossum 这么一位被人称为「仁慈的独裁者」的 Python 之父采纳 Ellipsis 的原因竟然是因为:有人认为三个省略号的写法可爱。(原文为:「Some folks thought it would be cute to be able to write incomplete code like this」)

应用

要说这个看起来「鸡肋」的 Ellipsis 类型对象没有用,这个说法似乎也不正确。因为它作为一种奇怪的语法糖也被应用到了某些地方。

Numpy 中的切片

虽然官方说 Ellipsis 主要用于用户自定义容器类型的切片操作,但是在我搜索了许久之后发现用 Ellipsis 来实现所谓的切片操作的貌似只有 Numpy。

使用 Python 做数据分析、挖掘或机器学习相关的朋友一定对 Numpy 高性能的科学计算库并不陌生。在 Numpy 中我们真正的使用 Ellipsis 来进行切片索引: 

  1. >>> import numpy as np  
  2. >>> arr = np.arange(9).reshape((3,3))  
  3. >>> arr 
  4.  array([[0, 1, 2],  
  5.        [3, 4, 5],  
  6.        [6, 7, 8]]) 

需要注意的是,Ellipsis 主要是对二维以上的数组才起作用: 

  1. >>> arr[...,1:2]  
  2. array([[1],  
  3.        [4],  
  4.        [7]])  
  5. >>> arr[2, ...]  
  6. array([6, 7, 8]) 

从结果中我们看到,Ellipsis 三个省略号的写法其实就等价于 arr[:, 1:2] 冒号的写法。但是在使用过程中 Ellipsis 只能出现一次: 

  1. >>> ndarr = np.arange(24).reshape((2,3,4))  
  2. >>> ndarr  
  3. array([[[ 0,  1,  2,  3],  
  4.         [ 4,  5,  6,  7],  
  5.         [ 8,  9, 10, 11]],  
  6.        [[12, 13, 14, 15],  
  7.         [16, 17, 18, 19],  
  8.         [20, 21, 22, 23]]])  
  9. >>> ndarr[:, :, :]  
  10. array([[[ 0,  1,  2,  3],  
  11.         [ 4,  5,  6,  7],  
  12.         [ 8,  9, 10, 11]],  
  13.        [[12, 13, 14, 15],  
  14.         [16, 17, 18, 19],  
  15.         [20, 21, 22, 23]]])  
  16. >>> ndarr[..., ..., ...]  
  17. Traceback (most recent call last):  
  18.   File "<stdin>", line 1, in <module>  
  19. IndexError: an index can only have a single ellipsis ('...') 

Ellipsis 在 Numpy 中出现的意义在于,当你的数组是高维的数组时,那么可以直接使用它来作为选取其他维度的等价写法,以下例子来源于 Numpy 官方文档: 

  1. >>> z = np.arange(81).reshape(3,3,3,3)  
  2. >>> z[1,...,2] # 等价于 z[1, :,:, 2]  
  3. array([[29, 32, 35],  
  4.        [38, 41, 44],  
  5.        [47, 50, 53]]) 

Type Hint 类型注解

自从 PEP 484 之后,Python 解释器开始支持类型注解。所谓的类型注解无非就是在 Python 实际代码中能像注释那样对当中的一些参数或返回值添加类型注释,就像是这样: 

  1. def add(x: int, y: int) -> int:  
  2.     return x + y 

如果你是有使用过 Java 或者 Go 这类对类型注解要求较为严格的编译型语言,那么相信对此并不陌生,无论是变量还是方法,都要写上对应的类型以防编译报错;但即便没有接触过这类编译型语言也不要紧,将其理解为注释即可,这样的注释是能被编辑器或 IDE 所支持,在你要查看函数定义或文档时会给予提示。

但是 Type Hint 仅仅只是一种「协定」,告诉别人你的方法里参数是如何、最后返回的是什么仅此而已,无论是加与不加都不会影响最终代码的效果,影响的仅仅只是代码的可读性罢了。

如果你的方法有多个返回值,我们不可能对每个返回值的类型都写上注解,因此这时 Ellipsis 对象就派上了用场。根据官方文档给出的说明,我们完全可以像这样来进行类型注解: 

  1. from typing import Tuple  
  2. def get_many_value(  
  3.     a:int, b:int, c:int,   
  4.     d:int, e:int, f:int  
  5. ) -> Tuple[int, ...]:  
  6.     return [a+b, c+d, e+f] 

这样的写法本质上就是 *args 的作用,表示同类型的可变长度元组。如果你将 Tuple 换成是 List,那么解释器会报错,因为 *args 在方法中的表现就是元组,那么作为注解的 Ellipsis 也应如此。这可能也就说明为什么在 Tuple 注解中不报错了。

FastAPI 中的必选参数

目前正流行开来的高性能 Web 框架 FastAPI 中,也应用了 Ellipsis。它用以表示参数是必填项,这在 Swagger 页面更能直观体现。 

  1. # pip install fastapi  
  2. # pip install uvicorn  
  3. from fastapi import FastAPI, Query  
  4. app = FastAPI()  
  5. @app.get('/greetWithOutEllipsis')  
  6. async def greet(name: str = None):  
  7.     if name:  
  8.         return {"info": f"Welcome! {name}"}  
  9.     return {"info": f"Welcome to FastAPI!"}  
  10. @app.get('/greetWithEllipsis')  
  11. async def greet(name: str = Query(..., min_length=2)):  
  12.     if name:  
  13.         return {"info": f"Welcome! {name}"}  
  14.     return {"info": f"Welcome to FastAPI!"}  
  15. if __name__ == "__main__":  
  16.     import uvicorn  
  17.     uvicorn.run(app, port = 5000

启动服务之后,在浏览器中输入 http://127.0.0.1:5000/docs 便能进入到服务的 Swagger 页面中,在上述例子中如果 name 参数并非是个必要的参数时,在 Swagger 页面中不会看到任何标识,即便我们不带上 name 参数也能进行请求: 

非必要参数

但当我们加上了一个 Query() 方法,并将其 Ellipsis 对象丢到当中时,不仅会给参数加上 required 的标识,同时还对传入的字符串长度进行了限制。

必要参数

除了参数之外,在 FastAPI 中你还可以在请求体、路径、字段等多个地方使用 Ellipsis 对象。

「伪」 pass 写法

Ellipsis 有时候还可以作为 pass 的一种「伪」写法,比如这样: 

  1. def greet():  
  2.     ... #等价于 pass 

这其实就和 # 注释符号与六个引号的长字符串注释类似。但实际上仅仅只是一种取巧的方法,实际上我们可以将 ... 替换成任何值或对象,如 None、1、True 等,因为在方法中并没有显示声明返回的对象,所以无论我们写什么最后的效果都是一样的。

但使用 Ellipsis 对象来作为 pass 关键字的替代品从「视觉」上来说或许还有点「意犹未尽」的意思。

当然如果在你和同事协作时,随手写下这样一个省略号,没准隐含着你对同事 Coding 的无奈,或者是对秃头的忧愁(逃) 

 

责任编辑:庞桂玉 来源: Python中文社区
相关推荐

2010-03-12 10:30:18

Python语言

2023-08-13 16:32:12

JavaScript

2021-03-15 08:15:42

ES2021语言开发

2009-10-23 14:22:59

Windows 7微软隐藏功能

2014-12-09 12:35:11

人工智能机器学习开源项目

2016-12-20 11:35:52

编程语言开源项目

2010-05-14 10:55:04

java对象序列化

2022-10-10 23:19:02

Python脚本语言工具库

2017-08-08 16:04:30

2009-04-30 16:21:37

VB.NET通用对象列表

2020-03-29 20:27:51

Python函数开发

2022-12-28 08:59:11

2022-01-23 14:08:52

PythonPygame

2018-05-07 15:32:54

编程语言Python程序员

2022-04-20 07:42:08

Python脚本代码

2022-08-23 12:32:37

Python可视化图表

2022-08-29 14:56:56

Python脚本代码

2014-09-19 11:17:48

面试题

2010-07-17 01:03:13

CMD Telnet

2023-07-14 22:36:42

Node.jsStorage
点赞
收藏

51CTO技术栈公众号