Python中有趣的Ellipsis对象

栏目: IT技术 · 发布时间: 4年前

内容简介:在 Python 中你可能有时候会看到一个奇怪的用法,就像是这样:在你输入了三个点之后,Python 解释器非但不会报错,反而还会返回给你「Ellipsis」这么一个信息。那么这个有趣的东西是什么呢?

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 参数也能进行请求:

Python中有趣的Ellipsis对象

非必要参数

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

Python中有趣的Ellipsis对象

必要参数

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

「伪」 pass 写法

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

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

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

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

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

【责任编辑:庞桂玉 TEL:(010)68476606】


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

小米生态链战地笔记

小米生态链战地笔记

小米生态链谷仓学院 / 中信出版集团 / 2017-5 / 56.00

2013年下半年,小米开始做一件事,就是打造一个生态链布局IoT(物联网);2016年年底,小米生态链上已经拥有了77家企业,生态链企业整体销售额突破100亿元。这3年,是小米生态链快速奔跑的3年,也是小米在商场中不断厮杀着成长的3年。 3年,77家生态链企业,16家年销售额破亿,4家独角兽公司,边实战,边积累经验。 小米生态链是一个基于企业生态的智能硬件孵化器。过去的3年中,在毫无先......一起来看看 《小米生态链战地笔记》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

MD5 加密
MD5 加密

MD5 加密工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换