本篇文章为大家展示了Python中Ellipsis对象有什么用,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。
什么是Ellipsis
在 Python 中你可能有时候会看到一个奇怪的用法,就像是这样:
>>> ... Ellipsis
在你输入了三个点之后,Python 解释器非但不会报错,反而还会返回给你「Ellipsis」这么一个信息。那么这个有趣的东西是什么呢?
查阅 Python 官方文档后可以看到,它是一个**「内置常量」**(Built-in Constant)。经常用于对用户自定义的容器数据类型进行切片用法的扩展。
这也就意味着它可能是会作为一个「小众且另类」的语法糖来使用,但如果你用于 Python 中的容器数据类型(比如列表)进行切片索引时,可能会引发错误:
>>> nums = list(range(10)) >>> nums [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> nums[...] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: list indices must be integers or slices, not ellipsis
除此之外,如果你使用的是 Python 2 的解释器,那么压根就不支持 Ellipsis 的用法,从一开始输入时就报错:
$ python2 WARNING: Python 2.7 is not recommended. This version is included in macOS for compatibility with legacy software. Future versions of macOS will not include Python 2.7. Instead, it is recommended that you transition to using 'python3' from within Terminal. Python 2.7.16 (default, Nov 9 2019, 05:55:08) [GCC 4.2.1 Compatible Apple LLVM 11.0.0 (clang-1100.0.32.4) (-macos10.15-objc-s on darwin Type "help", "copyright", "credits" or "license" for more information. >>> ... File "<stdin>", line 1 ... ^ SyntaxError: invalid syntax
虽然说在列表中使用 Ellipsis 会报错,但是碰到这种情况你会发现解释器返回给你的是这样的东西:
>>> nums = [1,2,3] >>> nums [1, 2, 3] >>> nums[1] = nums >>> nums [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 来进行切片索引:
>>> import numpy as np >>> arr = np.arange(9).reshape((3,3)) >>> arr array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])
需要注意的是,Ellipsis 主要是对二维以上的数组才起作用:
>>> arr[...,1:2] array([[1], [4], [7]]) >>> arr[2, ...] array([6, 7, 8])
从结果中我们看到,Ellipsis 三个省略号的写法其实就等价于 arr[:, 1:2] 冒号的写法。但是在使用过程中 Ellipsis 只能出现一次:
>>> ndarr = np.arange(24).reshape((2,3,4)) >>> ndarr array([[[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]], [[12, 13, 14, 15], [16, 17, 18, 19], [20, 21, 22, 23]]]) >>> ndarr[:, :, :] array([[[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]], [[12, 13, 14, 15], [16, 17, 18, 19], [20, 21, 22, 23]]]) >>> ndarr[..., ..., ...] Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: an index can only have a single ellipsis ('...')
Ellipsis 在 Numpy 中出现的意义在于,当你的数组是高维的数组时,那么可以直接使用它来作为选取其他维度的等价写法,以下例子来源于 Numpy 官方文档:
>>> z = np.arange(81).reshape(3,3,3,3) >>> z[1,...,2] # 等价于 z[1, :,:, 2] array([[29, 32, 35], [38, 41, 44], [47, 50, 53]])
Type Hint 类型注解
自从 PEP 484 之后,Python 解释器开始支持类型注解。所谓的类型注解无非就是在 Python 实际代码中能像注释那样对当中的一些参数或返回值添加类型注释,就像是这样:
def add(x: int, y: int) -> int: return x + y
如果你是有使用过 Java 或者 Go 这类对类型注解要求较为严格的编译型语言,那么相信对此并不陌生,无论是变量还是方法,都要写上对应的类型以防编译报错;但即便没有接触过这类编译型语言也不要紧,将其理解为注释即可,这样的注释是能被编辑器或 IDE 所支持,在你要查看函数定义或文档时会给予提示。
但是 Type Hint 仅仅只是一种「协定」,告诉别人你的方法里参数是如何、最后返回的是什么仅此而已,无论是加与不加都不会影响最终代码的效果,影响的仅仅只是代码的可读性罢了。
如果你的方法有多个返回值,我们不可能对每个返回值的类型都写上注解,因此这时 Ellipsis 对象就派上了用场。根据官方文档给出的说明,我们完全可以像这样来进行类型注解:
from typing import Tuple def get_many_value( a:int, b:int, c:int, d:int, e:int, f:int ) -> Tuple[int, ...]: return [a+b, c+d, e+f]
这样的写法本质上就是 *args 的作用,表示同类型的可变长度元组。如果你将 Tuple 换成是 List,那么解释器会报错,因为 *args 在方法中的表现就是元组,那么作为注解的 Ellipsis 也应如此。这可能也就说明为什么在 Tuple 注解中不报错了。
FastAPI 中的必选参数
目前正流行开来的高性能 Web 框架 FastAPI 中,也应用了 Ellipsis。它用以表示参数是必填项,这在 Swagger 页面更能直观体现。
# pip install fastapi # pip install uvicorn from fastapi import FastAPI, Query app = FastAPI() @app.get('/greetWithOutEllipsis') async def greet(name: str = None): if name: return {"info": f"Welcome! {name}"} return {"info": f"Welcome to FastAPI!"} @app.get('/greetWithEllipsis') async def greet(name: str = Query(..., min_length=2)): if name: return {"info": f"Welcome! {name}"} return {"info": f"Welcome to FastAPI!"} if __name__ == "__main__": import uvicorn uvicorn.run(app, port = 5000)
启动服务之后,在浏览器中输入 http://127.0.0.1:5000/docs 便能进入到服务的 Swagger 页面中,在上述例子中如果 name 参数并非是个必要的参数时,在 Swagger 页面中不会看到任何标识,即便我们不带上 name 参数也能进行请求:
非必要参数
但当我们加上了一个 Query() 方法,并将其 Ellipsis 对象丢到当中时,不仅会给参数加上 required 的标识,同时还对传入的字符串长度进行了限制。
必要参数
除了参数之外,在 FastAPI 中你还可以在请求体、路径、字段等多个地方使用 Ellipsis 对象。
「伪」 pass 写法
Ellipsis 有时候还可以作为 pass 的一种「伪」写法,比如这样:
def greet(): ... #等价于 pass