Python 中有趣的 Ellipsis 对象

Python中文社区

共 5265字,需浏览 11分钟

 ·

2020-08-04 12:43


什么是Ellipsis

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

>>> ...
Ellipsis

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

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

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

>>> nums = list(range(10))
>>> nums
[0123456789]
>>> nums[...]
Traceback (most recent call last):
  File "", line 1in 
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 201905: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 "", line 1
    ...
    ^
SyntaxError: invalid syntax

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

>>> nums = [1,2,3]
>>> nums
[123]
>>> 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([[012],
       [345],
       [678]])

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

>>> arr[...,1:2]
array([[1],
       [4],
       [7]])
>>> arr[2, ...]
array([678])

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

>>> ndarr = np.arange(24).reshape((2,3,4))
>>> ndarr
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  91011]],

       [[12131415],
        [16171819],
        [20212223]]])
>>> ndarr[:, :, :]
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  91011]],

       [[12131415],
        [16171819],
        [20212223]]])
>>> ndarr[..., ..., ...]
Traceback (most recent call last):
  File "", line 1in 
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([[293235],
       [384144],
       [475053]])

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

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

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

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

作者:100gle,练习时长不到两年的非正经文科生一枚,喜欢敲代码、写写文章、捣鼓捣鼓各种新事物;现从事有关大数据分析与挖掘的相关工作。


赞 赏 作 者



Python中文社区作为一个去中心化的全球技术社区,以成为全球20万Python中文开发者的精神部落为愿景,目前覆盖各大主流媒体和协作平台,与阿里、腾讯、百度、微软、亚马逊、开源中国、CSDN等业界知名公司和技术社区建立了广泛的联系,拥有来自十多个国家和地区数万名登记会员,会员来自以工信部、清华大学、北京大学、北京邮电大学、中国人民银行、中科院、中金、华为、BAT、谷歌、微软等为代表的政府机关、科研单位、金融机构以及海内外知名公司,全平台近20万开发者关注。


推荐阅读:
用 Python 进行系统聚类分析
用 Python 对数据进行相关性分析
如何在 matplotlib 中加注释和内嵌图
如何用一行代码让 gevent 爬虫提速 100%


▼点击成为社区会员   喜欢就点个在看吧

浏览 69
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报