Python 进阶指南(编程轻松进阶):九、深奥的 Python 怪现象

原文:http://inventwithpython.com/beyond/chapter9.html

定义编程语言的规则系统是复杂的,并且可能导致代码,尽管没有错,但是非常奇怪和不可预料。这一章深入探讨了更难理解的 Python 语言的奇特之处。您不太可能在现实世界的编码中遇到这些情况,但是它们是 Python 语法的有趣用法(或者是滥用,取决于您的观点)。

通过学习本章中的例子,您将对 Python 如何工作有一个更好的了解。让我们找点乐子,探索一些深奥的问题。

为什么 256 是 256 而 257 不是 257

==操作符比较两个对象是否相等,而is操作符比较它们是否相等。尽管整数值42和浮点值42.0具有相同的值,但它们是保存在计算机内存中不同位置的两个不同的对象。您可以通过使用id()函数检查他们不同的 id 来确认这一点:

>>> a = 42
>>> b = 42.0
>>> a == b
True
>>> a is b
False
>>> id(a), id(b)
(140718571382896, 2526629638888)

当 Python 创建一个新的整数对象并将其存储在内存中时,该对象的创建只需要很少的时间。作为一个微小的优化,CPython(Python 解释器可从python.org下载)在每个程序开始时为-5256创建整数对象。这些整数被称为预分配整数,CPython 自动为它们创建对象,因为它们相当常见:程序更可能使用整数02,而不是1729。当在内存中创建一个新的整数对象时,CPython 首先检查它是否在-5256之间。如果是这样,CPython 通过简单地返回现有的 integer 对象而不是创建一个新的来节省时间。这种行为也通过不存储重复的小整数来节省内存,如图 9-1 所示。

f09001

图 9-1:Python 通过对单个整数对象(左)使用多个引用来节省内存,而不是对每个引用使用单独的、重复的整数对象(右)。

由于这种优化,某些人为的情况会产生奇怪的结果。要查看示例,请在交互式 Shell 中输入以下内容:

>>> a = 256
>>> b = 256
>>> a is b # 1
True
>>> c = 257
>>> d = 257
>>> c is d # 2
False

所有 256 个对象实际上都是同一个对象,所以abis运算符返回True 1 。但是 Python 为cd分别创建了 257 个对象,这就是为什么is操作符返回False 2 。

表达式257 is 257的计算结果为True,但是 CPython 在同一个语句中重用为相同字面值创建的整数对象:

>>> 257 is 257
True

当然,现实世界的程序通常只使用一个整数的值,而不是它的单位。他们永远不会使用is操作符来比较整数、浮点数、字符串、布尔值或其他简单数据类型的值。一个例外是当你使用is None而不是== None时,正如第 96 页“使用is None而不是==进行比较”中所解释的。否则,你很少会碰到这个问题。

字符串内化

类似地,Python 重用对象在代码中表示相同的字符串字面值,而不是制作相同字符串的单独副本。要在实践中看到这一点,请在交互式 Shell 中输入以下内容:

>>> spam = 'cat'
>>> eggs = 'cat'
>>> spam is eggs
True
>>> id(spam), id(eggs)
(1285806577904, 1285806577904)

Python 注意到分配给eggs'cat'字符串和分配给spam'cat'字符串相同;因此,它没有创建第二个冗余的字符串对象,而是给eggs分配了一个引用,指向spam使用的同一个字符串对象。这解释了为什么它们的字符串的 id 是相同的。

这种优化被称为字符串预留,和预分配整数一样,它只不过是 CPython 实现的一个细节。你不应该写依赖它的代码。此外,这种优化不会捕获所有可能的相同字符串。试图识别可以使用优化的每个实例通常会花费比优化节省的时间更多的时间。例如,尝试在交互 Shell 中从'c''at'创建'cat'字符串;您会注意到 CPython 创建最终的'cat'字符串作为新的字符串对象,而不是重用为spam创建的字符串对象:

>>> bacon = 'c'
>>> bacon += 'at'
>>> spam is bacon
False
>>> id(spam), id(bacon)
(1285806577904, 1285808207384)

字符串内化是解释器和编译器用于许多不同语言的一种优化技术。你可以在en.wikipedia.org/wiki/String_interning找到更多的细节。

Python 的伪递增和递减操作符

在 Python 中,您可以使用增加的赋值操作符将变量的值增加1或减少1。代码spam += 1spam -= 1分别将spam中的数值增加和减少1

其他语言,比如 C++和 JavaScript,有用于递增和递减的++--操作符。(“C++”这个名字本身就体现了这一点;这是一个半开玩笑的玩笑,表明它是 C 语言的增强形式。)C++和 JavaScript 中的代码可以有类似于++spamspam++的操作。Python 明智地没有包括这些操作符,因为它们容易受到细微错误的影响(正如在softwareengineering.stackexchange.com/q/59880帖子上所讨论的)。

但是拥有以下 Python 代码是完全合法的:

>>> spam = --spam
>>> spam
42

您应该注意的第一个细节是,Python 中的++--“操作符”实际上并不递增或递减spam中的值。相反,主要的-是 Python 的一元否定操作符。它允许您编写这样的代码:

>>> spam = 42
>>> -spam
-42

在一个值前面有多个一元负运算符是合法的。使用它们中的两个会得到值的负值,对于整数值,它只计算原始值:

>>> spam = 42
>>> -(-spam)
42

这是一个非常愚蠢的操作,您可能永远不会看到一元求反操作符在真实世界的代码中使用两次。(但如果你这样做了,那很可能是因为程序员学会了用另一种语言编程,并且刚刚编写了错误的 Python 代码!)

还有一个+一元运算符。它将整数值计算为与原始值相同的符号,也就是说,它完全不做任何事情:

>>> spam = 42
>>> +spam
42
>>> spam = -42
>>> +spam
-42

+42(或者++42)看起来和--42一样傻,那为什么 Python 还要有这个一元运算符呢?如果您需要为自己的类重载这些操作符,它的存在只是为了补充-操作符。(这是很多你可能不熟悉的术语!你会在第 17 章的里学到更多关于操作符重载的知识。)

+-一元运算符只在 Python 值的前面有效,在它后面无效。尽管spam++spam--可能是 C++或 JavaScript 中的合法代码,但它们会在 Python 中产生语法错误:

>>> spam++File "<stdin>", line 1spam++^
SyntaxError: invalid syntax

Python 没有递增和递减运算符。语言语法的一个怪癖只是让它看起来是这样。

全部或者没有

all()内置函数接受一个序列值,比如一个列表,如果该序列中的所有值都是“真”,则返回True如果一个或多个值为“假”,它将返回False你可以认为函数调用all([False, True, True])等同于表达式False and True and True

您可以将all()与列表推导、结合使用,首先基于另一个列表创建一个布尔值列表,然后求值它们的集合值。例如,在交互式 Shell 中输入以下内容:

>>> spam = [67, 39, 20, 55, 13, 45, 44]
>>> [i > 42 for i in spam]
[True, False, False, True, False, True, True]
>>> all([i > 42 for i in spam])
False
>>> eggs = [43, 44, 45, 46]
>>> all([i > 42 for i in eggs])
True

如果spameggs中的所有数字都大于 42,则all()实用工具返回True

但是如果你传递一个空序列给all(),它总是返回True。在交互式 Shell 中输入以下内容:

>>> all([])
True

最好将all([])理解为求值“列表中的所有项目都是真值”而不是“列表中的所有项目都是True”否则,您可能会得到一些奇怪的结果。例如,在交互式 Shell 中输入以下内容:

>>> spam = []
>>> all([i > 42 for i in spam])
True
>>> all([i < 42 for i in spam])
True
>>> all([i == 42 for i in spam])
True

这段代码似乎表明,不仅spam(一个空列表)中的所有值都大于42,而且它们也小于42,正好等于42!这在逻辑上似乎是不可能的。但是请记住,这三个列表推导式中的每一个都计算为空列表,这就是为什么它们中的项目都不为假,并且all()函数返回True

布尔值是整数值

就像 Python 认为浮点值42.0等于整数值42一样,它认为布尔值TrueFalse分别等价于10。在 Python 中,bool数据类型是int数据类型的子类。(我们将在第 16 章中讨论类和子类。)您可以使用int()将布尔值转换为整数:

>>> int(False) 
0
>>> int(True) 
1
>>> True == 1 
True
>>> False == 0
True

您也可以使用isinstance()来确认一个布尔值被认为是一种整数:

>>> isinstance(True, bool) 
True
>>> isinstance(True, int) 
True

True属于bool数据类型。但是因为boolint的子类,True也是int。这意味着你可以在任何可以使用整数的地方使用TrueFalse。这可能会导致一些奇怪的代码:

>>> True + False + True + True  # Same as 1 + 0 + 1 + 1
3
>>> -True            # Same as -1.
-1
>>> 42 * True        # Same as 42 * 1 mathematical multiplication.
42
>>> 'hello' * False  # Same as 'hello' * 0 string replication.
' '
>>> 'hello'[False]   # Same as 'hello'[0]
'h'
>>> 'hello'[True]    # Same as 'hello'[1]
'e'
>>> 'hello'[-True]   # Same as 'hello'[-1]
'o'

当然,你可以使用bool值作为数字并不意味着你应该这样做。前面的例子都是不可读的,不应该在现实世界的代码中使用。本来 Python 没有bool数据类型。直到 Python2.3 才添加了布尔值,此时它将bool变成了int的子类以简化实现。你可以在www.python.org/dev/peps/pep-0285读取 PEP 285 中bool数据类型的历史。

顺便说一下,TrueFalse在 Python3 中只是关键字。这意味着在 Python2 中,有可能使用TrueFalse作为变量名,导致看似矛盾的代码如下:

Python2.7.14 (v2.7.14:84471935ed, Sep 16 2017, 20:25:58) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> True is False
False
>>> True = False
>>> True is False 
True

幸运的是,这种令人困惑的代码在 Python3 中是不可能的,如果您试图使用关键字TrueFalse作为变量名,这将引发语法错误。

链接多种运算符

在同一个表达式中链接不同种类的运算符可能会产生意想不到的错误。例如,这个例子在一个表达式中使用了==in操作符:

>>> False == False in [False]
True

这个True结果令人惊讶,因为你可能会认为它是:

  • (False == False) in [False],也就是False
  • False == (False in [False]),也就是False

但是False == False in [False]并不等同于这两个表达式。确切地说,它相当于(False == False) and (False in [False]),只是作为42 < spam < 99相当于(42 < spam) and (spam < 99)。该表达式根据下图进行计算:

g09001False == False in [False]表达式是一个有趣的 Python 谜语,但它不太可能出现在任何真实世界的代码中。

Python 的反重力特性

要启用 Python 的反重力特性,请在交互式 Shell 中输入以下内容:

>>> import antigravity

这条线是一个有趣的复活节彩蛋,它打开了网页浏览器,进入了一个经典的 XKCD 漫画,讲述了在xkcd.com/353的 Python 故事。Python 可以打开您的 web 浏览器,这可能会让您感到惊讶,但这是webbrowser模块提供的内置特性。Python 的webbrowser模块有一个open()函数,可以找到你的操作系统的默认网络浏览器,并打开一个特定 URL 的浏览器窗口。在交互式 Shell 中输入以下内容:

>>> import webbrowser
>>> webbrowser.open('https://xkcd.com/353/')

webbrowser模块是有限的,但是它可以帮助用户在互联网上找到更多的信息。

总结

人们很容易忘记,计算机和编程语言是由人类设计的,它们有自己的局限性。如此多的软件建立在语言设计师和硬件工程师的创造之上,并依赖于他们的创造。他们非常努力地工作,以确保如果你的程序有问题,那是因为你的程序有问题,而不是运行它的解释软件或 CPU 硬件有问题。我们最终会认为这些工具是理所当然的。

但这就是为什么学习计算机和软件的奇怪角落和缝隙是有价值的。当您的代码出现错误或崩溃时(或者甚至只是行为怪异,让您觉得“这很奇怪”),您需要理解调试这些问题的常见陷阱。

你几乎肯定不会碰到本章提到的任何问题,但是意识到这些小细节会让你成为一个有经验的 Python 程序员。

查看全文

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.dgrt.cn/a/2242067.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章:

g09001

Python 进阶指南(编程轻松进阶):九、深奥的 Python 怪现象

原文:http://inventwithpython.com/beyond/chapter9.html 定义编程语言的规则系统是复杂的,并且可能导致代码,尽管没有错,但是非常奇怪和不可预料。这一章深入探讨了更难理解的 Python 语言的奇特之处。您不太可能在现实世界的编码……

javascript如何每秒钟切换一张图片

// 定义函数<script>function change(n){if(n>5){n1;}document.getElementById(test).setAribute("src",n".jpg");n;setTimeout("change("n"),1000);}//窗体加载时window.onload function(){setTimeout("change(1)",100……

javascript中如何获取select标签中option的值

<script>var zdocument.getElementById("test");z.option[z.selectedIndex].text;<script><body><select id"test"><option>值</option></select><body>…

获取表单元素的方式

document.getElemenById("表单标签的ID")
document.getElementByName("表单标签的name")[索引]
document.表单标签的name
document.forms["表单标签的name"] //常用属性
action 表单提交到的页面
id 表单的编号
name 表单名字
target 跳转……

javascript中如何自动给表table添加行和列

<script> /********************方法一*********************/
function add()
{ var ita.rows.length;//添加一行 var newTrta.insertRow();//创建函数 var tdnewTr.insertcell();//添加列 td.innerHTML<input type"text" id"a"/>//向列里面……

java简介和术语

java之父:詹姆斯.高斯林
1995.5.23正式命名为java
1998 三大版本比较完善,J2SE J2ME J2EE java的体系结构 JavaSE:java标准版,桌面应用程序 JavaME:java微型版,移动应用程序 JavaEE:java企业版,企业级应用 Java的术语 SD……

JAVA的数据类型和变量

一,原始类型 数值型 整形:byte short int long 浮点型:float double 布尔:boolern(true/false) 字符型:char 定义变量:类型 变量名 值;
引用类型 (类类型&#xff0……

数组 ,字符串的比较

1.如何定义一个数组 int[] a或int b[]; int []dnew int[]{1,2,3};
2.字符串的比较 String a"a";String b"c"; a.equals(b); 3.二维数组 double[][] scorenew double[5][3];//实例化一个3行5列的数组…

java如何生成二维码

1,去百度QR-code下一个安装包,解压
2,把 F:\zxing-master\core\src\main\java\路径下的com包和F:\zxing-master\javase\src\main\java包导入一个新的项目下,导出zixing.jar
3,把zixing.jar导入项目中
4,……

java如何打印万年历

Scanner inputnew Scanner(System.in);System.out.println("<<<<<万年历>>>>>");int year0;int month0;System.out.print("请输入年份:");yearinput.nextInt();System.out.print("请输入月份:&q……

学会这些终端快捷键,让你在Linux上的操作快100倍

🪶 简述 Linux命令行的许多快捷键与GNU/Emacs编辑器非常像,因此我十分建议可以学习学习emacs编辑器,来了解或发现更多的命令行快捷键。 点此访问emacs官网 点此访问emacs中国(论坛) 简述一下Emacs:Emacs’一切皆快捷键……

手把手教你Temporal Fusion Transformer——Pytorch实战

建立了一个关于能源需求预测的端到端项目: 如何为 TFT 格式准备我们的数据。 如何构建、训练和评估 TFT 模型。 如何获取对验证数据和样本外预测的预测。 如何使用built-in model的可解释注意力机制计算特征重要性、季节性模式和极端事件鲁棒性。
什么是Temporal F……

【Java开发】设计模式 12:解释器模式

1 解释器模式介绍
解释器模式是一种行为型设计模式,它提供了一种方法来解释语言、表达式或符号。
在该模式中,定义了一个表达式接口,并实现了对应的表达式类,这些类可以解释不同的符号组成的表达式,从而实现对语言的……

反序列化渗透与攻防(五)之shiro反序列化漏洞

Shiro反序列化漏洞
Shiro介绍
Apache Shiro是一款开源安全框架,提供身份验证、授权、密码学和会话管理。Shiro框架直观、易用,同时也能提供健壮的安全性
Apache Shiro 1.2.4及以前版本中,加密的用户信息序列化后存储在名为remember-me的Cookie中。攻击者可以使用Shiro的默……

vue2+vue3

vue2vue3尚硅谷vue2vue2 课程简介【02:24】vue2 Vue简介【17:59】vue2 Vue官网使用指南【14:07】vue2 搭建Vue开发环境【13:54】vue2 Hello小案例【22:25】了解: 不常用常用:id 更常用 简单class差值总结vue 实例vue 模板 : 先 取 &#xff0……

【hello Linux】环境变量

目录 1. 环境变量的概念 2. 常见的环境变量 3. 查看环境变量 4. 和环境变量相关的命令 5. 环境变量的组织方式 6. 通过代码获取环境变量 7. 通过系统调用获取环境变量 Linux🌷 在开始今天的内容之前,先来看一幅图片吧! 不知道你们是否和我一……

【Linux基础】常用命令整理

ls命令
-a选项,可以展示隐藏的文件和文件夹-l选项,以列表形式展示内容-h,需要和-l搭配使用,可以展示文件的大小单位ls -lah等同于la -a -l -h
cd命令(change directory)
语法:cd [Linux路径]……

客快物流大数据项目(一百一十二):初识Spring Cloud

文章目录
初识Spring Cloud
一、Spring Cloud简介
二、SpringCloud 基础架构图…

C和C++中的struct有什么区别

区别一: C语言中: Struct是用户自定义数据类型(UDT)。 C语言中: Struct是抽象数据类型(ADT),支持成员函数的定义。
区别二:
C中的struct是没有权限设置的&#xff0c……

docker的数据卷详解

数据卷 数据卷是宿主机中的一个目录或文件,当容器目录和数据卷目录绑定后,对方修改会立即同步
一个数据卷可以同时被多个容器同时挂载,一个容器也可以被挂载多个数据卷
数据卷作用:容器数据持久化 /外部机器和容器间接通信 /容器……

Published by

风君子

独自遨游何稽首 揭天掀地慰生平

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注