2.1.Python基本语法
Windows 10
Python 3.11.3 @ MSC v.1916 64 bit (AMD64)
Latest build date 2023.05.01
Python简史
1982年,Guido 从阿姆斯特丹大学 (University of Amsterdam) 获得了数学和计算机硕士学位。那时,他就已经接触并使用过 Fortran、Pascal、C 等高级语言,但 Guido 还没找到一门易用性强、生产效率高的语言。个人计算机在那时才刚开始发展,相较于现在,当时个人计算机的配置还很低。于1981年问世的第一台个人计算机 IBM5150 就只具有 4.77MHz 主频的 CPU 和 16KB 的 RAM。而当时懂得编程的人往往都精通计算机,所以他们一般更关心程序的性能,而不是编程的效率和语言的易用性。为了提高程序的性能,语言不得不逼迫程序员像计算机一样思考,这让 Guido 感到苦恼,即使他已经准确知道如何用 C 语言实现一个功能,但他仍然不得不花大量时间去编写代码。
当时一个可用的选择是 shell,Bourne shell 作为 Unix 系统的命令解释器在 1978 年就已经发布。shell 可以像胶水一样,将 Unix 的许多功能连接在一起。许多需要 C 语言写上百行代码才能实现的功能,在 shell 中输入几行命令就可以完成。但 shell 本质上是调用其他程序执行命令,它并不是一个真正的编程语言。因此,shell 并不能全面调用操作系统的接口。
Guido 希望有一种语言,这种语言能够像 C 语言那样,能够全面调用计算机的资源,又可以像 shell 一样轻松地编程。当时的 ABC 语言让 Guido 看到希望。ABC 由荷兰的 CWI (Centrum Wiskunde & Informatica, 数学和计算机研究所) 开发,于 1987 年发布第一个版本。80年代中期,Guido 曾在 CWI 工作,并参与了 ABC 语言的开发。
与当时的大部分语言不同,ABC是交互式的,其重点是语言的友好性。它的一些主要设计让语言变得容易阅读、容易使用:
- 语句按缩进分组。
- 只有五种基本数据类型。
- 强动态类型系统。
- 拥有交互式解释器(使用
>>>
提示)。
语法示例:
>>> WRITE 2**1000
1071....
>>> PUT 1/(2*1000) in x
>>> WRITE 1 + 1/x
然而 ABC 主要目的是用于教学或语言研究,而不是作为系统编程语言。相对于其他系统编程语言,ABC 过于严格、纯净,存在一些严重的设计缺陷:
- 只能通过 STDIN/STDOUT 输出文本,没有与外界的接口,即没有文件 I/O,这是致命的。
- 不能与外部设备交互。
- 可拓展性差。ABC不是模块化的语言,如果想在ABC中添加新功能,则需要改动很多地方。
- 只具有一个单一的命名空间,而不是每个函数或模块可以具有单独的命名空间。
- 过度创新。ABC 使用自然语言来表达程序的含义,例如使用
HOW TO
定义函数,使用PUT
为变量赋值。但因为 Fortran 和 C 语言的流行,大家更习惯于使用 function 或 define 定义函数,使用等号赋值。尽管这让ABC变得特别,但实际上增加了程序员的学习难度。
虽然 ABC 并不是一门成功的语言,但这给了 Guido 设计 Python 的灵感。1989 年左右,Guido 参加了 CWI 一项开发新操作系统的工作。Guido 他们需要为用户写很多应用程序,这些程序一般只是偶尔运行一小段时间,但可能使用复杂的逻辑,用 C 语言完成这些工作效率极低。这让 Guido 想使用 ABC 之类的语言。ABC 的生产力要比 C 高得多,但代价是运行时的性能损失。虽然,ABC 语言面世时,个人计算机的性能还很低,但计算机行业以惊人的速度发展。90 年代初,个人计算机开始走进普通家庭,个人计算机的性能越来越高。因此,Guido 认为以性能换效率已经是可以接受的折衷方案。Guido 认为他可以设计一种类似 ABC,但与之又不一样的语言,这种语言可以保留 ABC 高效的生产力,并且克服 ABC 的缺陷。
于是,在 1989 年的圣诞节前后,Guido 开始使用 C 语言开发 Python 语言的编译器/解释器,并在 1991 年初发布了第一个开源版本。Python 的名字来源于 Guido 所挚爱的电视剧 Monty Python's Flying Circus,Python 的文献资料中也经常引用该电视剧的台词。而 Python 与巨蟒的关联则是出版商强加的,因为他们不想为书籍封面购买 Monty-Python 插画的许可。
Python 受到 ABC 的强烈影响,与 ABC 具有一些相似之处:
- 使用冒号
:
作为代码块的起点 - 使用缩进划分代码块
- 无需声明变量类型
- 支持交互式编程,也使用
>>>
作为提示符 - 提供可以组合的高级数据结构(list、dictionary)
- 自动垃圾收集(GC)
- 重视可读性,使用简洁的语法
- 由 C 语言实现字节码的编译,字节码由更高级的VM进行解释。
另一方面,Python抛弃了 ABC 的原教旨主义:
-
使用类似 C 语言的术语和符号。例如使用
=
表示赋值,使用def
定义函数。Guido 认为如果一些东西已经成为一种常识,则没有必要过度纠正1。 -
以模块为基础的拓展机制。Guido 认为 ABC 语言没有成功的一个重要原因是缺乏扩展性,因此 Python 从一开始就特别在意可拓展性。Python 可以在多个层次上拓展。在高层,可以导入
.py
文件(即 import 模块,这个灵感来自 Modula-3 语言)。在底层,可以导入C语言的库。因此,当需要注重性能时,Python 程序员可以编写 C 程序,并编译为.so
文件,将其导入 Python 中使用。 - 使用类似 Modula-3 语言的异常处理机制,只是添加了 else 子句。
- 支持 OO(Smalltalk)
- 支持文件 I/O
Python 成功地从其他语言中借鉴了很多东西,除了以已列出的 ABC、C等,还包括了许多未列出的语言。正如 Tim Peters 在Python 所说:
可以说 Python 的成功代表了它借用的所有语言的成功。同样,Ruby 借鉴了 Python,它的成功也代表了 Python 某些方面的成功。每种语言都是混合语言,它们具有一些优越的方面,但是也存在各种缺陷。对语言 “好与坏” 的判断通常受平台、硬件和时间等外部因素的影响。程序员遇到了许多关于语言的争议,但实际上很多争论都在浪费时间,使用开放的思想和客观的分析来区分每种语言的特定优缺点,以区分内部和外部因素,能更好地帮我们看清本质。
由于数据科学的兴起,Python变得更加流行,可能您刚听说Python这种语言,但实际上Python并不是一个年轻的语言,Python的第一个版本发布于1991年,由Guido von Rossum设计。
1990年代初,个人计算机开始进入普通家庭,计算机的性能大大提高。程序员开始关注计算机的易用性。诞生于该时代背景下的 Python 正是一门极具易用性的编程语言。
在语法方面,Python沿用了C语言的很多惯例,例如使用等号赋值,使用def
定义函数,同时又受到了ABC 语言的强烈影响。来自 ABC 语言的一些规定时至今日还富有争议,例如强制缩进。但这些语法规定确实让 Python 容易读。
Python的核心理念在 Zen of Python(PEP 20)文档中进行了总结,其中包括如下格言:
-
美丽胜于丑陋
-
显式胜于隐式
-
简单胜于繁多
-
繁多胜于晦涩
-
可读性很重要
交互模式
Python是一种解释型语言,Python解释器通过一次执行一条语句的方式运行程序。从 tty(终端) 读取命令时, 我们称解释器工作于交互模式 (interactive mode)。所谓的交互模式可以这样理解:在终端输入的Python语句会立即被执行,执行完之后,终端显示提示符。等待下一次命令的输入。可以在cmd窗口通过python
命令启动CPython解释器的交互模式,交互模式下CPython通过主提示符 (primary prompt) >>>
提示下一条命令,而从提示符···
提示一条命令的续行。
加强的交互式解释器(IPython)的提示符是一种带编号的风格,如In[2]
,而不是标准的>>>
。
保留字
保留字(又称为关键字),不能把它们用作任何标识符名称。Python的标准库提供了一个keyword模块,可以输出当前版本的所有关键字:
import keyword
print(keyword.kwlist)
['False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await',
'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except',
'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is',
'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try',
'while', 'with', 'yield']
变量命名规则
Python 的标识符是大小写敏感的,由字母、下划线和数字组成,且开头不能是数字。
哪些字母可用作标识符?
对于 Python 3 来说,这里的字母实际上不限于英文字母,也可以是非ASCII码的字母,例如希腊字母、西里尔字母、汉字等等,参见 PEP 3131。 但 Python 2 只支持英文字母,至少 Python 2 的 CPython 实现是这样。其他的 Python 2 实现有可能也支持非ASCII字符作为标识符,例如 IronPython。
为什么标识符不能以数字开头?
-
如果标识符以数字开头,那么像"0o10"、"1e5"这样的代码就会带来歧义,甚至对于"123"这样的代码,机器也无法确定这到底是数字还是标识符。 当然可以通过追加规则来消除歧义,例如规定变量赋值时,等号左边的是标志符。但如果真的这样做的话,代码必定会存在安全性问题,例如"1e5"在前面已经用作变量名了,在后面又想使用"1e5"这个数字。 另一方面,既然需要通过追加规则来消除歧义,那直接规定数字不能用于开头不就好了,毕竟数字作为标识符的开头好像没什么必要。
-
这会让编译器的词法分析更加复杂,从而降低编译器的性能。如果一个变量以数字开头,那么词法分析器就必须在遇到第一个或第二个英文字符的时候进行回溯,以确定是否为数字。
-
实际上,有些语言允许标识符以数字开头,但有一些限制,例如 Common Lisp。
Python 习惯于使用蛇形命名法(snake case)。具体来说,类名推荐使用驼峰命名法,而对于普通变量名、函数名、模块名、包名,则推荐使用蛇形命名法。
至于 Python 为什么偏爱蛇形命名法,最可能的原因是借鉴了 C 语言的规范。蛇形命名法起源于 1960 年代后期,它与 C 语言关系密切,The C Programming Language 的 2.1 节中就有提到使用下划线可以提高长变量名的可读性。一开始,蛇形命名法并没有如今特定的名称。Python 从 C 语言中借鉴过来后,在 PEP 8 中将其简称为 "lower_case_with_underscores"。直到 21 世纪初,在 Intel 和 Ruby 社区中,才有人开始以 "snake case" 来称呼它。
现今有不少编程语言在某些场景下会推荐使用蛇形命名法,包括R语言,而 Python 则是最早这么做的其中之一,并且是使用 snake case 场景最多的语言之一。
注释
编程语言的注释符一般有两种:行注释与块注释(inline/block)。由于历史原因,C语言家族选择\\
作为行注释符,而脚本语言(如shell)大多使用#
作为行注释符。Python 沿用了脚本语言的传统,单行注释以#
开头,并以物理行的末尾结束。
# 这是单行注释
Python 并没有块注释符,因此,有时多行字符串会被用作块注释。
"""
这是多行注释
这是多行注释
"""
虽然多行字符串用于多行注释时,Python 解释器并不会为此生成代码(多行注释作为文档字符串时除外),也没有负作用(negative effects),但这却是一种副作用(side effects)。基于此,PEP-8 建议多行注释均以#
号和空格开头:
Each line of a block comment starts with a # and a single space (unless it is indented text inside the comment).
缩进
位于逻辑行开始前的空白(空格和制表符)用于计算行的缩进层次,该层次用于语句的分组。不能使用\
连接两个物理行的缩进。
相同层次的代码必须保持相同的缩进量,缩进量则可以随意。当源码文件混用制表符和空格时,运行时会触发 TabError
。 由于非Unix平台的文本编辑器的特性,最好不要用制表符表示缩进。一般约定俗成使用4个空格的缩进。
# tab = "input"
if True:
print("True")
else:
print("False")
True
使用缩进划分代码块,而不是使用大括号{}
,是 Python 最具特色的地方,但这也是 Python 时常被诟病的地方。
一方面,当代码很长的时候,可能会不小心弄错部分行的缩进层级。但很长的代码块本身就有坏代码的味道,这降低了可读性,并暗示了代码可能存在过度的耦合,这是要尽量避免的。
另一方面,如果代码是从网上复制的,粘贴到源文件中可能会导致制表符和空格混用,或者丢失缩进格式。如果大家都不适用制表符表示缩进,第一个问题就可以解决,但丢失缩进格式确实无法自动还原代码。
语句分隔符
非正式地,简单语句必须在单个逻辑行上完成。多条简单语句可以存在同一逻辑行内,并以半角分号;
分隔。一般不推荐这样的做法,因为可能会降低代码的可读性。
续行符
虽然 Python 将;
作为语句分隔符,但使用 \newline
token 作为语句终止符。\newline
token 可以理解为各个系统合法的序列终止符(例如 \n
、\r\n
等),严格来说,还包括终止符前面的空格。另外,注释也表示逻辑行的结束。因此,换行是Python语法的一部分。
另外,\newline
也是逻辑行的结尾标记,简单语句不能越过逻辑行的边界,即不能将 \newline
包含其中。因此,当简单语句需要跨越多个物理行时,需要使用转义符\
对其中的 \newline
进行转义,这称为显式行连接(Explicit line joining):
a = 3 + \
4 + \
5
a
12
当\
用于续行时,可能会被称为续行符,但如上所述,\
本质上是转义符。因此,\
用于代码续行时,反斜杠后面不能带有注释,或其他字符(除非\
位于string literal之中)。
[]
,{}
, 或()
2之中的表达式可以跨越多个物理行,不需要使用转义符\
,这称为隐式行连接(Implicit line joining)。例如:
total = ['item_one', 'item_two', 'item_three', # comment
'item_four', 'item_five']
total
['item_one', 'item_two', 'item_three', 'item_four', 'item_five']
隐式的连续行可以带有带有注释,代码的缩进也不是语法上必须的,还允许其中包括空行。虽然隐式连续行存在换行,但其中并不包括 \newline
token。另外,隐式行连接也可以出现在多行字符串之中,此时不能带有注释。
空行
空行与缩进不同,空行并不是Python语法的一部分。仅包含空格、制表符、换页符和注释的逻辑行将被忽略,即不生成 \newline
token。代码文件中不存在空行,运行时也不会出错。但空行可用于分隔两段不同功能或含义的代码,让代码结构看起来更清晰,便于维护或重构。根据 PEP8 规范,类方法之间用 1 行空行分隔,顶级函数、类之间用 2 行空行分隔。
在交互式解释器中,由于需要实现 read-eval-print loop,空行的处理可能有所不同。在交互式解释器输入代码块时,一个完全空白的空行用于终止多行语句。
In[1]: for i in range(2):
...: print(i)
...:
0
1
Print 输出
print
函数将对象转换为字符串,默认将这些字符串输出到 STDOUT,并且默认在字符串末尾添加换行符。如果不想换行打印,需要更改print
函数的end
参数:
print("a")
print("b")
a
b
print("a", end=" ")
print("b", end=" ")
a b
程序可能会产生多种输出。虽然 print
函数是一个方便的方式,但通常我们会使用更高级的技术来代替它:
- 基于文本格式的文件。通常使用
print
函数也可以完成。 - PDF 或其他格式的输出文件。这将需要使用其他 Python 库来生成这些格式。
- 错误消息、调式消息和日志文件。通常使用
logging
模块,偶尔使用print
函数。
导入模块
Python 使用 import
或者 from...import
来导入相应的模块(module)。
import somemodule 导入整个模块
from somemodule import somefunction 导入某个模块的某个函数
from somemodule import func_1, func_2, class_1 导入某个模块的多个函数或类
from somemodule import * 导入某个模块的全部定义
字符转义
如果要在字符串中输入一些特殊的字符(这些字符特殊之处在于不能通过键盘直接输入),这就需要“转义符”。所谓转义,就是不采用符号原先的含义(字面含义),而采用另外一含义。在Pyhton中,\
代表转义符。
除非存在 r
前缀,否则将根据类似于标准C的规则来解释 string literals 和 bytes literals 中的转义序列。以下是Python可识别的转义序列:
转义序列 | 描述 | 转义序列 | 描述 |
---|---|---|---|
\newline |
反斜杠和换行符将被忽略 | \v |
纵向制表符 |
\\ |
反斜杠 | \t |
横向制表符 |
\' |
单引号 | \r |
回车符 |
\" |
双引号 | \f |
换页符 |
\a |
蜂鸣器响铃4 | \ooo |
八进制值为ooo 的字符,如 \012 表示换行,\000 为空字符 |
\b |
退格 | \xhh |
十六进制值为hh 字符,如 \x0a 表示换行 |
\n |
换行符 |
仅在 string literals 中才能被识别的转义序列为:
转义序列 | 描述 |
---|---|
\N{name} |
Character named name in the Unicode database |
\uxxxx |
Character with 16-bit hex value xxxx |
\Uxxxxxxxx |
Character with 32-bit hex value xxxxxxxx |
Python默认将\
作为转义符处理,但有时候我们想输入的是反斜杠符号\
,例如输入 windows 系统的文件路径。此时,可以在反斜杠前加上转义符\
,即\\
表示一个反斜杠符号。如果字符串包含了很多反斜杠\
,这样做就比较麻烦。Python提供了一个简化的操作,Pyhon允许用 r"字符串内容"
表示 "字符串内容"
内部的字符默认不转义:
print('\\\t\\')
\ \
print(r'\\\t\\')
\\\t\\
变量与对象
不正式地,编程的本质在于操纵计算机的 CPU 和 RAM。编程语言本身就是 CPU 指令集的抽象,它们都提供了各自操作内存中元素的方式。一般,在高级语言编程中,我们会通过操纵变量去操纵内存,而不是直接操纵内存地址,因为变量就代表了一块内存空间。如果是 OOP 范式的语言,变量关联了一个对象,该对象就是一块内存空间,即对象作为内存空间的抽象。对象基于类创建,类定义了该类型的对象需要占用多少内存空间,储存在对象中的值该如何解释,以及有哪些方法5可以用来操纵该对象。可以看出,类相当于一个模板,具有相同结构以及相似处理的数据就可以使用相同的模板。
Python 官方文档似乎更喜欢把变量称为 name(名字)。同时,又因为 Python 中的变量名对应的变量类型是可以随时改变的,因此有观点认为 Python 不存在变量这个概念。但这样的观点是荒谬的,就像在说因为熊猫叫做熊猫,所以它不是熊,因为鸵鸟不会飞,所以它不是鸟。Python 的 name 就是变量,只不过 Python 变量的行为和 C、C++、Java 这些编程语言有所区别而已。对于此问题的具体讨论可以参考《1.3.基本概念》的第3节。在本系列的文章中,变量和名字(name)具有等同的内涵;变量值(变量的值)和对象(object)具有等同的内涵。
Python的变量不需要声明、定义和初始化,变量会在赋值时被创建。编译器或解析器不会静态检查变量的类型,变量类型在运行时检查,例如使用isinstance
函数。
Objects
所有 Python 对象都有:
-
一个惟一的 identity(由
id(x)
返回的一个整数,注意:identity 不是 identifier) -
一个类型(由
type(x)
返回) -
一些值
对象的 identity 无法改变,也不能改变对象的类型。
Note
CPython2.2及更新的版本允许在相当有限的情况下更改对象的类型。
在 Python 中。一些对象允许你改变它们的值(在不改变 identity 和 type 的情况下),这种对象被称为 mutable object;而另一些对象不允许你改变它们的值,它们被称为 immutable object。
类型由类对象表示(类对象往往知道类的对象要占多少内存,有什么方法等等)。
一个对象可以有:
- 零个或多个方法(由类对象提供)
- 零个或多个名字
某些对象具有允许你更改对象内容(即对其进行修改)的方法。某些对象具有仅具有访问内容而不更改内容的方法。一些对象没有任何方法。即使对象具有方法,你也不能改变它们的类型或者内存地址。
Names
name 不是 object 的属性,object 并不知道它们的 name。
-
所有 name 在创建时必须关联(bind)到一个 object,否则在需要引用该 name 时会报错。
-
name 可以在创建以后指向任何一个 object(包括不同类型),所以 name 本身没有类别,但 name 关联的 object 是有类别的。
-
可以使用
type(name)
来查看 name 关联的 object 的类型。 -
一个对象可以关联(bind)多个名字,也可以没有名字。
-
name 存在于 namespace(命名空间),例如模块命名空间、实例命名空间、函数的局部命名空间等等。
Assignment
Python 也有 assignment 这个概念,但它的中文或许译为分配或者命名更为恰当,因为在 Python中,assignment 语句会改动 namespace,而不是改动了 object。
Attention
这个说法不太严谨,因为 assignment 可以改变 Python 中可变对象的值。
assignment: assignments do not copy data — they just bind names to objects.
换而言之,以下 assignment 语句表示将名称a
添加到namespace,并且namespace的a
键3所对应的value为10。
a = 10
如果名称已经存在,assignment语句将替换原始名称:
a = 10
a = 20
这意味着首先要将名称a
添加到namespace,并使其指向包含值10的整数对象。然后再使a
其指向包含值20的整数对象。原来的整数对象10不受此操作的影响。
如果这样做:
a = []
a.append(1)
第一条语句将a
关联到一个空列表对象,这会修改命名空间,第二条语句修改了该列表的内容,这并不会触及namespace。
综上所述,assignment statement 在 Python 中有两个作用:
- 用于(重新)将名称绑定到对象
- 修改可变对象的属性(内容)
单引号与双引号
Python 没有 char 类型,只有 str 类型,单引号和双引号都可以用来表示一个字符串:
str1 = 'Python'
str2 = "Python"
str1
和 str2
是没有任何区别的。Python 以简洁和易用性著称,这并不是一个多余的设计。
如果字符串中带有单引号,例如:
若单引号表示字符串,那么为了表示字符串中的单引号,则必须使用转义:
string = 'We all know that \'A\' and \'B\' are two capital letters.'
显然太多的转义符看起来并不好看,此时使用双引号表示字符串则美观得多:
string = "We all know that 'A' and 'B' are two capital letters."
同理,如果字符串中包含了双引号,那么可以使用单引号表示字符串,而避免使用转义符:
string = '"There was a storm last night," Paul said.'
这就是Python易用性和人性化的一个体现,使用单引号' '
定义字符串时,它会认为字符串中的双引号" "
是普通字符,从而不需要转义。反之亦然。
进制数表示
0b
开头表示二进制数:
0b10
2
0o
开头表示八进制数:
0o10
8
0x
开头表示十六进制数:
0x10
16
Lambdas
正常创建函数(使用def
)会自动将函数对象与def
后面的名字进行绑定,这与创建其他对象(例如字符串和整数)不同,后者可以动态创建,而无需将它们分配给名字。但只要使用lambda语法创建函数,函数也可以这样做,以这种方式创建的函数称为匿名(anonymous)函数。将简单函数作为参数传递给另一个函数时,最常使用此方法。语法如下:
# my_func是正常创建的函数(named function)
def my_func(f, arg):
return f(arg)
my_func(lambda x: 2 * x * x, 5)
50
在上面的代码中,我们动态创建了一个匿名函数,并使用参数调用它。Lambda函数没有命名函数(named function)那么强大。Lambda函数只能包含一个表达式,通常相当于一行代码。实际上,Lambda函数可以分配给一个名字,并像普通函数一样使用。但是,很少有充分的理由这样做。通常使用def
来定义函数更好。
double = lambda x: x * 2
print(double(7))
14
运算符
运算符 | 描述 |
---|---|
+ | 加 |
- | 减 |
* | 乘 |
/ | 除 |
% | 取模 |
** | 幂 |
// | 取整除 |
运算符 | 描述 |
---|---|
== | 等于 |
!= | 不等于 |
> | 大于 |
< | 小于 |
>= | 大于等于 |
<= | 小于等于 |
运算符 | 描述 |
---|---|
= | 简单的赋值运算符 |
+= | 加法赋值运算符 |
-= | 减法赋值运算符 |
*= | 乘法赋值运算符 |
/= | 除法赋值运算符 |
%= | 取模赋值运算符 |
**= | 幂赋值运算符 |
//= | 取整除赋值运算符 |
运算符 | 描述 |
---|---|
<< | 左移运算符:运算数的各二进位全部左移若干位,由<< 右边的数指定移动的位数,高位丢弃,低位补0 |
>> | 右移运算符:把> 左边的运算数的各二进位全部右移若干位,>> 右边的数指定移动的位数 |
& | 与运算符:参与运算的两个值,如果两个相应位都为1,则该位的结果为1,否则为0 |
| | 或运算符:只要对应的二个二进位有一个为1时,结果位就为1 |
^ | 异或运算符:当两对应的二进位相异时,结果为1 |
~ | 取反运算符:对数据的每个二进制位取反,即把1变为0,把0变为1。~x 类似于 -x-1 |
左移和右移的意思就是把位数整体向左或者向右移动若干位。比如1111向右移一位就变成了0111,原来没有的位自动填0,超出范围的位舍弃掉。看几个例子:
bin(0b1111 >> 1)
'0b111'
bin(0b1010 << 2)
'0b101000'
对于全部的按位运算符,如果使用十进制(或者其他进制数),也是对其二进制形式进行移位:
print(0b1111 is 15)
bin(15 >> 1)
True
<>:1: SyntaxWarning: "is" with a literal. Did you mean "=="?
<>:1: SyntaxWarning: "is" with a literal. Did you mean "=="?
C:\Users\Siwing\AppData\Local\Temp\ipykernel_11776\2644546950.py:1:
SyntaxWarning: "is" with a literal. Did you mean "=="?
print(0b1111 is 15)
'0b111'
其他按位运算符的例子:
print(bin(0b1111 & 0b1010))
print(bin(0b1111 | 0b1010))
print(bin(0b1111 ^ 0b1010))
print(bin(~0b1010), bin(-0b1010-1))
0b1010
0b1111
0b101
-0b1011 -0b1011
运算符 | 逻辑表达式 | 描述 |
---|---|---|
and | x and y | x、y同时为真,则结果为真,否则为假 |
or | x or y | x、y两者有一个为真,则结果为真,否则为假 |
not | not x | x为真,结果为假;x为假,结果为真 |
布尔运算符有个有趣的特征:只做必要的计算。
例如,仅当x
和y
都为真时,表达式x and y
オ为真。因此如果x
为假,这个表达式将立即返回假,而不关心y
。实际上,如果x
为假,这个表达式将返回x
,否则返回y
。
这种行为称为短路逻辑(或者延迟求值):布尔运算符常被称为逻辑运算符。
对于运算符or
,情况亦如此。在表达式x or y
中,如果x
为真,就返回x
,否则返回y
。
请注意,这意味着位于布尔运算符后面的代码(如函数调用)可能根本不会执行。像下面这样的代码就利用了这种行为
name = input('Please enter your name:') or '<unknown>'
如果没有输入名字,上述or
表达式的结果将为'< unknown>'
。
在很多情况下,宁愿使用条件表达式,而不耍这样的短路花样,因为过多的短路逻辑可能会让代码逻辑变得隐晦,更容易出错。 不过前面这样的语句确实有其用武之地。
运算符 | 描述 |
---|---|
in | 如果在指定的序列中找到值返回 True,否则返回 False |
not in | 如果在指定的序列中没有找到值返回 True,否则返回 False |
运算符 | 描述 | 实例 |
---|---|---|
is | is 是判断两个标识符是不是引用自一个对象 | x is y, 类似 id(x) == id(y) , 如果引用的是同一个对象则返回 True,否则返回 False |
is not | is not 是判断两个标识符是不是引用自不同对象 | x is not y , 类似 id(a) != id(b) 。如果引用的不是同一个对象则返回结果 True,否则返回 False |
运算符优先级
数值越大,优先级越高。具有相同优先级的运算符将从左至右的方式依次进行。用小括号()
可以改变运算顺序。
运算符 | 描述 | 优先级 |
---|---|---|
{key:value, ...} | 字典显示 | 16 |
[...] | 列表显示 | 16 |
(...) | 将表达式用括号括起或元组显示 | 16 |
x[index]、x[index1:index2]、f(args...)、x.attribute | 元素访问、切片、函数调用、属性引用 | 15 |
** | 幂运算 | 14 |
~ | 按位求补 | 13 |
*、/、//、%、@ | 乘、除、取整、取模 | 12 |
+、- | 加、减 | 11 |
<<、>> | 左移位、右移位 | 10 |
& | 按位与 | 9 |
^ | 按位异或 | 8 |
| | 按位或 | 7 |
<、<=、>、>=、==、!= | 比较运算符 | 6 |
is、is not | 身份运算符 | 6 |
in、not in | 成员运算符 | 6 |
not | 逻辑运算符 | 5 |
and | 逻辑运算符 | 4 |
or | 逻辑运算符 | 3 |
if - else | 条件表达式 | 2 |
lambda | Lambda 表达式 | 1 |