从零开始

这份速查表的主要目标是收集一些常见的基本语义或代码片段。速查表包含一些语法,这些语法我们已经知道,但在我们脑海中仍然模糊不清,或者是一些代码片段,我们一遍又一遍地在 Google 上搜索。此外,由于**Python 2 的生命周期结束日期**即将到来。大多数代码片段主要基于**Python 3**的语法。

Hello world!

当我们开始学习一门新的语言时,通常从打印**Hello world!**开始。在 Python 中,我们可以通过导入__hello__模块来以另一种方式打印消息。源代码可以在frozen.c找到。

>>> print("Hello world!")
Hello world!
>>> import __hello__
Hello world!
>>> import __phello__
Hello world!
>>> import __phello__.spam
Hello world!

Python 版本

对于程序员来说,了解当前的 Python 版本非常重要,因为并非所有语法都可以在当前版本中使用。在这种情况下,我们可以通过python -V或使用模块sys来获取 Python 版本。

>>> import sys
>>> print(sys.version)
3.7.1 (default, Nov  6 2018, 18:46:03)
[Clang 10.0.0 (clang-1000.11.45.5)]

我们也可以使用platform.python_version来获取 Python 版本。

>>> import platform
>>> platform.python_version()
'3.7.1'

有时,检查当前的 Python 版本很重要,因为我们可能希望在某些特定版本中启用某些功能。sys.version_info提供了有关解释程序的更多详细信息。我们可以用它来与我们想要的版本进行比较。

>>> import sys
>>> sys.version_info >= (3, 6)
True
>>> sys.version_info >= (3, 7)
False

省略号

Ellipsis是一个内置常量。Python 3.0 之后,我们可以使用...作为Ellipsis。它可能是 Python 中最神秘的常量。根据官方文档,我们可以用它来扩展切片语法。然而,在类型提示、存根文件或函数表达式中也有一些其他约定。

>>> ...
Ellipsis
>>> ... == Ellipsis
True
>>> type(...)
<class 'ellipsis'>

以下代码片段显示了我们可以使用省略号来表示尚未实现的函数或类。

>>> class Foo: ...
...
>>> def foo(): ...
...

if … elif … else

if 语句用于控制代码流程。Python 使用if ... elif ... else序列来控制代码逻辑,而不是使用switchcase语句。尽管有人建议我们可以使用dict来实现switch语句,但此解决方案可能会引入不必要的开销,例如创建一次性字典并破坏可读性代码。因此,不推荐此解决方案。

>>> import random
>>> num = random.randint(0, 10)
>>> if num < 3:
...     print("less than 3")
... elif num < 5:
...     print("less than 5")
... else:
...     print(num)
...
less than 3

for 循环

在 Python 中,我们可以通过**for 语句**直接访问可迭代对象的项。如果我们需要同时获取可迭代对象(如列表或元组)的索引和项,使用enumeraterange(len(iterable))更好。更多信息可以在循环技巧中找到。

>>> for val in ["foo", "bar"]:
...     print(val)
...
foo
bar
>>> for idx, val in enumerate(["foo", "bar", "baz"]):
...     print(idx, val)
...
(0, 'foo')
(1, 'bar')
(2, 'baz')

for … else …

当我们第一次看到else属于for循环时,可能会有点奇怪。else子句可以帮助我们避免在循环中使用标志变量。循环的else子句在没有发生break时运行。

>>> for _ in range(5):
...     pass
... else:
...     print("no break")
...
no break

以下代码片段显示了使用标志变量和else子句来控制循环的区别。我们可以看到,当循环中发生break时,else不会运行。

>>> is_break = False
>>> for x in range(5):
...     if x % 2 == 0:
...         is_break = True
...         break
...
>>> if is_break:
...     print("break")
...
break

>>> for x in range(5):
...     if x % 2 == 0:
...         print("break")
...         break
... else:
...     print("no break")
...
break

使用 range

Python 2 中range的问题是,如果我们需要多次迭代循环,range可能会占用大量内存。因此,在 Python 2 中建议使用xrange

>>> import platform
>>> import sys
>>> platform.python_version()
'2.7.15'
>>> sys.getsizeof(range(100000000))
800000072
>>> sys.getsizeof(xrange(100000000))
40

在 Python 3 中,内置函数range返回一个可迭代的**range 对象**而不是列表。range的行为与 Python 2 中的xrange相同。因此,如果我们想在循环中多次运行代码块,使用range不再占用大量内存。更多信息可以在 PEP 3100中找到。

>>> import platform
>>> import sys
>>> platform.python_version()
'3.7.1'
>>> sys.getsizeof(range(100000000))
48

while … else …

属于 while 循环的else子句与 for 循环中的else子句的作用相同。我们可以观察到,当 while 循环中发生break时,else不会运行。

>>> n = 0
>>> while n < 5:
...     if n == 3:
...         break
...     n += 1
... else:
...     print("no break")
...

do while 语句

许多编程语言(如 C/C++、Ruby 或 Javascript)提供了do while语句。在 Python 中,没有do while语句。但是,我们可以将条件和break放在while循环的末尾来实现相同的功能。

>>> n = 0
>>> while True:
...     n += 1
...     if n == 5:
...         break
...
>>> n
5

try … except … else …

大多数情况下,我们在except子句中处理错误,并在finally子句中清理资源。有趣的是,try语句还为我们提供了一个else子句,以避免捕获由不应受try ... except保护的代码引发的异常。else子句在tryexcept之间没有发生异常时运行。

>>> try:
...     print("No exception")
... except:
...     pass
... else:
...     print("Success")
...
No exception
Success

字符串

与其他编程语言不同,Python 不直接支持字符串的项赋值。因此,如果需要操作字符串的项,例如交换项,我们必须将字符串转换为列表,并在一系列项赋值完成后执行连接操作。

>>> a = "Hello Python"
>>> l = list(a)
>>> l[0], l[6] = 'h', 'p'
>>> ''.join(l)
'hello python'

列表

列表是用途广泛的容器。Python 提供了许多方法,例如**负索引**、**切片语句**或**列表推导式**来操作列表。以下代码片段显示了一些常见的列表操作。

>>> a = [1, 2, 3, 4, 5]
>>> a[-1]                     # negative index
5
>>> a[1:]                     # slicing
[2, 3, 4, 5]
>>> a[1:-1]
[2, 3, 4]
>>> a[1:-1:2]
[2, 4]
>>> a[::-1]                   # reverse
[5, 4, 3, 2, 1]
>>> a[0] = 0                  # set an item
>>> a
[0, 2, 3, 4, 5]
>>> a.append(6)               # append an item
>>> a
[0, 2, 3, 4, 5, 6]
>>> del a[-1]                 # del an item
>>> a
[0, 2, 3, 4, 5]
>>> b = [x for x in range(3)] # list comprehension
>>> b
[0, 1, 2]
>>> a + b                     # add two lists
[0, 2, 3, 4, 5, 0, 1, 2]

字典

字典是键值对容器。与列表一样,Python 支持许多方法,例如**字典推导式**来操作字典。Python 3.6 之后,字典保留键的插入顺序。以下代码片段显示了一些常见的字典操作。

>>> d = {'timmy': 'red', 'barry': 'green', 'guido': 'blue'}
>>> d
{'timmy': 'red', 'barry': 'green', 'guido': 'blue'}
>>> d['timmy'] = "yellow"        # set data
>>> d
{'timmy': 'yellow', 'barry': 'green', 'guido': 'blue'}
>>> del d['guido']               # del data
>>> d
>>> 'guido' in d                 # contain data
False
{'timmy': 'yellow', 'barry': 'green'}
>>> {k: v for k ,v in d.items()} # dict comprehension
{'timmy': 'yellow', 'barry': 'green'}
>>> d.keys()                     # list all keys
dict_keys(['timmy', 'barry'])
>>> d.values()                   # list all values
dict_values(['yellow', 'green'])

函数

在 Python 中定义函数非常灵活。我们可以使用**函数文档**、**默认值**、**任意参数**、**关键字参数**、**仅关键字参数**等来定义函数。以下代码片段显示了一些常见的函数定义表达式。

def foo_with_doc():
    """Documentation String."""

def foo_with_arg(arg): ...
def foo_with_args(*arg): ...
def foo_with_kwarg(a, b="foo"): ...
def foo_with_args_kwargs(*args, **kwargs): ...
def foo_with_kwonly(a, b, *, k): ...           # python3
def foo_with_annotations(a: int) -> int: ...   # python3

函数注解

我们可以通过**函数注解**来表示类型,而不是在函数中编写字符串文档来提示参数和返回值的类型。函数注解的详细信息可以在 PEP 3017 和 PEP 484 中找到,它们是在 Python 3.0 中引入的。它们是**Python 3**中的**可选**功能。使用函数注解将导致**Python 2**中的兼容性问题。我们可以通过存根文件来解决此问题。此外,我们可以通过mypy进行静态类型检查。

>>> def fib(n: int) -> int:
...     a, b = 0, 1
...     for _ in range(n):
...         b, a = a + b, b
...     return a
...
>>> fib(10)
55

生成器

Python 使用yield语句来定义**生成器函数**。换句话说,当我们调用生成器函数时,生成器函数将返回一个**生成器**而不是返回值,以创建一个**迭代器**。

>>> def fib(n):
...     a, b = 0, 1
...     for _ in range(n):
...         yield a
...         b, a = a + b, b
...
>>> g = fib(10)
>>> g
<generator object fib at 0x10b240c78>
>>> for f in fib(5):
...     print(f)
...
0
1
1
2
3

生成器委托

Python 3.3 引入了yield from表达式。它允许生成器将部分操作委托给另一个生成器。换句话说,我们可以在当前**生成器函数**中从其他**生成器**中**yield**一个序列。更多信息可以在 PEP 380中找到。

>>> def fib(n):
...     a, b = 0, 1
...     for _ in range(n):
...         yield a
...         b, a = a + b, b
...
>>> def fibonacci(n):
...     yield from fib(n)
...
>>> [f for f in fibonacci(5)]
[0, 1, 1, 2, 3]

Python 支持许多常见的功能,例如**类文档**、**多重继承**、**类变量**、**实例变量**、**静态方法**、**类方法**等等。此外,Python 为程序员提供了一些特殊方法来实现**迭代器**、**上下文管理器**等。以下代码片段显示了类的常见定义。

class A: ...
class B: ...
class Foo(A, B):
    """A class document."""

    foo = "class variable"

    def __init__(self, v):
        self.attr = v
        self.__private = "private var"

    @staticmethod
    def bar_static_method(): ...

    @classmethod
    def bar_class_method(cls): ...

    def bar(self):
        """A method document."""

    def bar_with_arg(self, arg): ...
    def bar_with_args(self, *args): ...
    def bar_with_kwarg(self, kwarg="bar"): ...
    def bar_with_args_kwargs(self, *args, **kwargs): ...
    def bar_with_kwonly(self, *, k): ...
    def bar_with_annotations(self, a: int): ...

async / await

asyncawait 语法是从 Python 3.5 开始引入的。它们被设计用于与事件循环一起使用。一些其他特性,例如 **异步生成器**,在之后的版本中实现。

**协程函数** (async def) 用于为事件循环创建 **协程**。Python 提供了一个内置模块 **asyncio**,用于通过 async/await 语法编写并发代码。以下代码片段展示了一个使用 **asyncio** 的简单示例。代码必须在 Python 3.7 或更高版本上运行。

import asyncio

async def http_ok(r, w):
    head = b"HTTP/1.1 200 OK\r\n"
    head += b"Content-Type: text/html\r\n"
    head += b"\r\n"

    body = b"<html>"
    body += b"<body><h1>Hello world!</h1></body>"
    body += b"</html>"

    _ = await r.read(1024)
    w.write(head + body)
    await w.drain()
    w.close()

async def main():
    server = await asyncio.start_server(
        http_ok, "127.0.0.1", 8888
    )

    async with server:
        await server.serve_forever()

asyncio.run(main())

避免使用 execeval

以下代码片段展示了如何使用内置函数 exec。但是,由于存在一些安全问题以及代码对人类的可读性差,不建议使用 execeval。更多阅读资料可以在 小心 Python 中的 exec 和 evalEval 确实很危险 中找到。

>>> py = '''
... def fib(n):
...     a, b = 0, 1
...     for _ in range(n):
...         b, a = b + a, b
...     return a
... print(fib(10))
... '''
>>> exec(py, globals(), locals())
55