跳转至

Google Style Guide

收录集合 Google Style Guides 🎉

这里是我在看完 Google C++ Style GuideGoogle Python Style Guide 的一些感想和学习记录。

Python Style

这里我列举一些重要的规范(它们是我在开发SkyPilot项目中遇见的),有些地方会加上自己的风格与理解,仅供参考🧐

Language Rules

import

这个关键字在python中有两种主要用法:

  1. 引入官方默认/第三方package或module
  2. 引入其他文件夹/文件内的class或function(多见于大型项目)

单独用 import 仅限 packages and modules,个人书写的文件引入/类引用/函数调用不允许直接这么写 ⚠️

from x import y as z 的使用场景是:

  1. 这个文件有有两个引入,名字都叫y,冲突了:
    • from cpp import class
    • from python import class
  2. 过于generic:
    • 1) y 本身是一种常见的parameter,跟某些public API冲突了(比如y = features)
    • 2) y 本身这个名字太泛型了: from storage.file_system import options as fs_options
  3. y 太长了,书写不方便,rename for convenience.

Exceptions

建议1:

  1. 在有意义的情况下使用内置异常类。例如,引发 ValueError 来指示编程错误
  2. 不要使用 assert 语句代替条件或验证前提条件🧨
Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Yes:

if minimum < 1024:
    # Note that this raising of ValueError is not mentioned in the doc
    # string's "Raises:" section because it is not appropriate to
    # guarantee this specific behavioral reaction to API misuse.
    raise ValueError(f'Min. port must be at least 1024, not {minimum}.')
port = self._find_next_open_port(minimum)
if port is None:
    raise ConnectionError(
        f'Could not connect to service on port {minimum} or higher.')
Python
1
2
3
4
5
6
7
No:

assert minimum >= 1024, 'Minimum port must be at least 1024.'
# The following code depends on the previous assert.
port = self._find_next_open_port(minimum)
assert port is not None
# The type checking of the return statement relies on the assert.

建议2:

  1. 鉴于我们使用 exception 是为了“防止特定的某个异常”,因此 catch 的部分一定是越精确越好
  2. try-except block 也应该是越小越好;对于要捕获多种异常的场景,我们可以多设置几个try-except block,切忌设计一个block捕获很多异常,因为这样捕获后很难知道到底是谁对谁

Generator Expressions

熟悉函数式编程的人一定对 lambda 表达式不陌生,python里也经常有这种写法,我觉得这种“函数式思想”非常非常好,很简便

但是我们也要遵守一定的规范,重点就一条:

不允许在一个“过滤式”中写多个for循环,每层过滤只允许考察一个“任务”,不允许多层嵌套 👀

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Yes:
  # layer 1: just for first iteration
  result = [mapping_expr for value in iterable if filter_expr]

  # layer 2: just for second-level filter
  result = [
      is_valid(metric={'key': value})
      for value in interesting_iterable
      if a_longer_filter_expression(value)
  ]

  # same idea here
  descriptive_name = [
      transform({'key': key, 'value': value}, color='black')
      for key, value in generate_iterable(some_input)
      if complicated_condition_is_met(key, value)
  ]

  result = []
  for x in range(10):
    for y in range(5):
      if x * y > 10:
        result.append((x, y))

  return {
      x: complicated_transform(x)
      for x in long_generator_function(parameter)
      if x is not None
  }

  return (x**2 for x in range(10))

  unique_names = {user.name for user in users if user is not None}

Default Iterators and Operators

能用封装好的迭代器(a in b) 就一定要用,别用远古的list/dictionary/...,不然多辜负开发者一片好心啊 🚀

Python
1
2
3
4
5
Yes:  

for key in adict: ...
if obj in alist: ...
for line in afile: ...
Python
1
2
3
4
No:

for key in adict.keys(): ...
for line in afile.readlines(): ...

yield and return

很长时间以来,我都对yieldreturn的用法不清楚,总是怕混淆,这里借机会整理一下:

事实上这两个关键字没有任何相似之处,甚至用法/场景基本上完全正交🤡

  • return 用于一次性返回一个值并结束函数执行。
  • yield 用于按需生成一系列值,可以在函数执行过程中暂停并继续,在需要 懒加载 或生成大量数据时非常有用

return

作用:return 用来从函数中返回一个值并结束函数的执行。

行为:一旦执行到 return 语句,函数会立即停止执行,并将 return 后的值返回给调用者。函数的状态会被丢弃。

特性:return 只能返回一次值。如果你在一个函数中调用了return,它会立即终止该函数的执行。

yield

作用:yield 用来生成一个迭代器(通常用于生成器函数)。它会将函数的状态“冻结”,并返回一个值。函数会在下一次迭代时从停止的地方继续执行,而不是重新开始。

行为:当执行到 yield 时,函数的执行被暂停,yield 后的值会被返回给调用者。下一次调用时,函数会从上次停止的位置继续执行。

特性:yield 允许函数在多个地方返回多个值,每次 yield 执行时,函数暂停并返回一个值。下次继续执行时,从暂停的位置开始。

我直接举个例子,说明 yield 的用法:

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def count_up_to(max):
    count = 1
    while count <= max:
        yield count  # 暂停并返回当前值
        count += 1

counter = count_up_to(5)

for number in counter:
    print(number)

Output:

Bash
1
2
3
4
5
1
2
3
4
5

Conditional Expressions

我很喜欢用这个,它非常简便,而且意思表达的也很明确

但是有个问题,如果这个 condition 很长,那书写的时候会有 “多层”,写起来麻烦不说,关键还难看懂😅

因此我们在这里要各位说明规范:

一般来说,单一的condition有三种,true-expression, if-expression, else-expression.

多层condition无非就是上述三种的叠加与嵌套,本质一样。

我们要保证,每一行只存在一种condition,不同的连接用换行的方式进行分隔 👍

Python
1
2
3
4
5
6
7
8
Yes:
    one_line = 'yes' if predicate(value) else 'no' # if-else can be combined together if simple
    slightly_split = ('yes' if predicate(value) # if-con
                      else 'no, nein, nyet') # else-con
    the_longest_ternary_style_that_can_be_done = (
        'yes, true, affirmative, confirmed, correct' # true-con
        if predicate(value) # if-con
        else 'no, false, negative, nay') # else-con

Properties

属性可用于 控制获取设置需要简单的属性(逻辑/计算)

它们通常很便宜,只为当前函数做一些很基础的事情(print / calculate / ...)

这里的典型是“装饰器”(@decorator)

我见过很多装饰器的使用例子,但是平时写的少,这里正好整理一下:

装饰器(decorators)是一个非常强大的功能,它允许你在不修改函数源代码的情况下,动态地扩展或修改函数的行为。装饰器本质上是一个函数,它接受一个函数作为参数,并返回一个新的函数,这个新函数对原始函数的行为做了某些修改或增强。

使用方式:

装饰器通过 @ 符号应用于函数上。@decorator 就是装饰器的应用方式,它相当于将函数传递给装饰器。

简易版装饰器:

举个例子:

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
def simple_decorator(func): # arg(func) here refers to say_hello()
    # define the action of decorator function
    def wrapper():
        print("Before calling the function.")
        func() # func here refers to say_hello() instance
        print("After calling the function.")
    # let it go!
    return wrapper

@simple_decorator
def say_hello():
    print("Hello!")

say_hello()

Output:

Bash
1
2
3
Before calling the function.
Hello!
After calling the function.

带参数的装饰器:

特别注意:如果装饰器应用于带有参数的函数,那么装饰器内部的 wrapper 函数需要接受并传递这些参数。

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
def decorator_with_args(func):
    def wrapper(*args, **kwargs):  # all original args should be listed here!
        print("Before calling the function.")
        result = func(*args, **kwargs)  # original function calling
        print("After calling the function.")
        return result
    return wrapper

@decorator_with_args
def add(a, b):
    return a + b

result = add(2, 3)
print(f"Result: {result}")

Outputs:

Bash
1
2
3
Before calling the function.
After calling the function.
Result: 5

带返回值的装饰器:

装饰器不仅可以用于在函数调用前后做一些操作,还可以修改函数的返回值

但是我感觉这个功能没有实际意义,可能单纯是为了炫技😅

举个例子吧:

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def multiply_decorator(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)  # original function calling
        return result * 2  # modify return value
    return wrapper

@multiply_decorator
def add(a, b):
    return a + b

result = add(2, 3)
print(f"Result after decoration: {result}")

Outputs:

Bash
1
Result after decoration: 10

我的浅见:

这个观点不知道有没有人提出来过,但是我 个人感觉装饰器这个功能本质上是定义了一个“操作流水线”

特别是在需要扩展已有函数的行为时,能够避免修改原始代码的复杂性,提升代码的灵活性和可维护性👊

仔细看每个decorator() function的内部,它们都是做了 controller-level 的调控

因此我对它的理解更倾向于宏观的“操作流水线”控制器,当我需要对一些/一类泛型函数做通用的类似操作时,我会选择使用装饰器,这样有利于将“主-从”逻辑梳理清楚,也有利于代码重用 🚀

Type Annotated Code

“类型注释”是一个非常好的习惯,它方便开发者短时间内捕获这个函数的参数类型信息🧱

类型注释(或“类型提示”)用于 手动标记函数或方法参数的返回值

Python
1
2
3
# 1. return value type of func is list
# 2. arg type is int
def func(a: int) -> list[int]:

我自己的代码风格是:

Python
1
2
3
4
5
6
def get_value(
        d: dict[str, int], 
        key: str
) -> int:
    # ...
    return d.get(key, 0)

Style Rules

Line length

一般情况下,一行不超过80个字符

例外:比如包含URL,或者注释中包含文件路径等不可抗力因素,这样的话如拆成两行会显得很奇怪

我们整体上会使用pylintrc来自动化配置行长度检查,在上述例外时,我们需要使这行“不检查”:

直接在这一行加上注释# pylint: disable=line-too-long即可

Python
1
some_really_long_string = "http://example.com/some/very/long/url/that/should/not/cause/a/warning"  # pylint: disable=line-too-long

如想让一段代码中的所有行都不进行长度检查,可以使用 # pylint: disable# pylint: enable 注释来禁用该段代码的检查。开始时禁用检查,并在结束时恢复检查:

Python
1
2
3
4
# pylint: disable=line-too-long
some_really_long_string1 = "http://example.com/some/very/long/url/that/should/not/cause/a/warning"
some_really_long_string2 = "this/is/another/long/string/that/should/not/be/warned/for/line/length"
# pylint: enable=line-too-long

Indentation

缩进肯定是4空格,此外我们还需要注意列表等元素的书写对齐

对于列表内元素很多的情况,我的习惯是:

Python
1
2
3
4
5
6
foo = long_function_name(
        var_one, 
        var_two,
        var_three, 
        var_four
)

还是建议按照我的习惯来💰

事实上以下写法都是符合google python style的:

末尾元素写逗号,那么收尾符(这里是))必须换行重开

Python
1
2
3
4
5
6
foo = long_function_name(
        var_one, 
        var_two,
        var_three, 
        var_four,
)

末尾元素无逗号,那么收尾符(这里是))随意

Python
1
2
3
4
5
foo = long_function_name(
        var_one, 
        var_two,
        var_three, 
        var_four)
terminology

末尾元素写逗号,这个逗号的学名是 “尾随逗号”

Shebang Line

Shebang Line(也叫做 Hashbang 或 Pound-Bang)是脚本文件的第一行,用于指定该脚本文件应该使用哪个解释器来执行

例如,在 Python 脚本的 Shebang Line 中,通常会这样写:

Bash
1
#!/usr/bin/env python3

这行代码的含义是告诉操作系统,执行这个脚本时应该使用 python3 解释器。/usr/bin/env 是一个常见的工具,用来确保使用环境中正确的解释器,避免硬编码解释器路径的情况。

使用 Shebang 的好处

1) 便于执行:如果脚本包含 Shebang Line,用户可以直接运行脚本而不必显式调用解释器。

例如,假设文件名为 myscript.py,可以通过以下命令执行:

Bash
1
./myscript.py

而无需指定 Python 解释器:

Bash
1
python3 myscript.py

2) 跨平台性:/usr/bin/env 是一个常见的方式,它会在环境路径中查找指定的解释器,这样可以避免硬编码特定路径,增强脚本的可移植性。

Comments and Docstrings

行注释就无需多言了,我们着重要求一下段落注释的格式:

这里我以SkyPilot中的一段代码为例

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
def _retry_zones(
        self,
        to_provision: resources_lib.Resources,
        num_nodes: int,
        requested_resources: Set[resources_lib.Resources],
        dryrun: bool,
        stream_logs: bool,
        cluster_name: str,
        cloud_user_identity: Optional[List[str]],
        prev_cluster_status: Optional[status_lib.ClusterStatus],
        prev_handle: Optional['CloudVmRayResourceHandle'],
        prev_cluster_ever_up: bool,
        skip_if_config_hash_matches: Optional[str],
    ) -> Dict[str, Any]:
        """The provision retry loop.

        Returns a config_dict with the following fields:
        All fields from backend_utils.write_cluster_config(). See its
          docstring.
        - 'provisioning_skipped': True if provisioning was short-circuited
          by skip_if_config_hash_matches, False otherwise.
        - 'handle': The provisioned cluster handle.
        - 'provision_record': (Only if using the new skypilot provisioner) The
          record returned by provisioner.bulk_provision().
        - 'resources_vars': (Only if using the new skypilot provisioner) The
          resources variables given by make_deploy_resources_variables().
        """
        # Get log_path name
        log_path = os.path.join(self.log_dir, 'provision.log')
        log_abs_path = os.path.abspath(log_path)
        if not dryrun:
            os.makedirs(os.path.expanduser(self.log_dir), exist_ok=True)
            os.system(f'touch {log_path}')
        rich_utils.force_update_status(
            ux_utils.spinner_message('Launching', log_path))

        # Get previous cluster status
        cluster_exists = prev_cluster_status is not None

        assert to_provision.region is not None, (
            to_provision, 'region should have been set by the optimizer.')
        region = clouds.Region(to_provision.region)

        # Optimization - check if user has non-zero quota for
        # the instance type in the target region. If not, fail early
        # instead of trying to provision and failing later.
        try:
            need_provision = to_provision.cloud.check_quota_available(
                to_provision)

        except Exception as e:  # pylint: disable=broad-except
            need_provision = True
            logger.info(f'Error occurred when trying to check quota. '
                        f'Proceeding assuming quotas are available. Error: '
                        f'{common_utils.format_exception(e, use_bracket=True)}')

对于一个函数,我们需要在刚开始的时候写注释,目的是定下基调,此时必须遵循docstrings:

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def _retry_zones(
        self,
        to_provision: resources_lib.Resources,
        num_nodes: int,
        requested_resources: Set[resources_lib.Resources],
        dryrun: bool,
        stream_logs: bool,
        cluster_name: str,
        cloud_user_identity: Optional[List[str]],
        prev_cluster_status: Optional[status_lib.ClusterStatus],
        prev_handle: Optional['CloudVmRayResourceHandle'],
        prev_cluster_ever_up: bool,
        skip_if_config_hash_matches: Optional[str],
    ) -> Dict[str, Any]:
        """The provision retry loop.

        Returns a config_dict with the following fields:
        All fields from backend_utils.write_cluster_config(). See its
          docstring.
        - 'provisioning_skipped': True if provisioning was short-circuited
          by skip_if_config_hash_matches, False otherwise.
        - 'handle': The provisioned cluster handle.
        - 'provision_record': (Only if using the new skypilot provisioner) The
          record returned by provisioner.bulk_provision().
        - 'resources_vars': (Only if using the new skypilot provisioner) The
          resources variables given by make_deploy_resources_variables().
        """

这里的明确范式是:

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
"""A one-line summary of the module or program, terminated by a period.

Leave one blank line.  The rest of this docstring should contain an
overall description of the module or program.  Optionally, it may also
contain a brief description of exported classes and functions and/or usage
examples.

Typical usage - return value / type and their meanings:
    foo = ClassFoo()
    bar = foo.FunctionBar()
"""

这么规定的原因是,我们需要使用pydoc进行规范化文本解析生成,方便生成一个文档进行各类函数汇总 😍

PyDoc

Pydoc 是 Python 提供的一个工具,它用于自动生成和查看 Python 模块的文档。它可以通过命令行或浏览器查看 Python 模块、类、方法、函数等的文档字符串(docstrings)。

Pydoc 提供了一种方便的方式来查看代码中的文档内容,而不需要查阅源代码文件本身。

可以通过python -m pydoc` 来直接使用 Pydoc。例如:

Text Only
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
```bash
python -m pydoc os
```

或者查看模块的 Web 文档:

```bash
python -m pydoc -p 8081
```

这样会在本地启动一个 Web 服务器,访问 `http://localhost:8081` 查看文档。

对于函数中间的解释,更多的是行文上的逻辑解释,这里就不要按docstrings来了:

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Optimization - check if user has non-zero quota for
# the instance type in the target region. If not, fail early
# instead of trying to provision and failing later.
try:
    need_provision = to_provision.cloud.check_quota_available(
        to_provision)

except Exception as e:  # pylint: disable=broad-except
    need_provision = True
    logger.info(f'Error occurred when trying to check quota. '
                f'Proceeding assuming quotas are available. Error: '
                f'{common_utils.format_exception(e, use_bracket=True)}')

一言以蔽之,按照下面的例子来就行

对于函数而言,这就是模板:

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def fetch_smalltable_rows(
    table_handle: smalltable.Table,
    keys: Sequence[bytes | str],
    require_all_keys: bool = False,
) -> Mapping[bytes, tuple[str, ...]]:
    """Description for this function in one line.

    Detailed description for this function. as a graph.
    ...
    ...

    Args:
        table_handle: An open smalltable.Table instance.
        keys: A sequence of strings representing the key of each table
          row to fetch.  String keys will be UTF-8 encoded.
        require_all_keys: If True only rows with values set for all keys will be
          returned.

    Returns:
        A dict mapping keys to the corresponding table row data
        fetched. Each row is represented as a tuple of strings. For
        example:

        {b'Serak': ('Rigel VII', 'Preparer'),
         b'Zim': ('Irk', 'Invader'),
         b'Lrrr': ('Omicron Persei 8', 'Emperor')}

        Returned keys are always bytes.  If a key from the keys argument is
        missing from the dictionary, then that row was not found in the
        table (and require_all_keys must have been False).

    Raises:
        IOError: An error occurred accessing the smalltable.
    """

对于类而言,应该在类定义下方设一个描述该类的文档字符串。公共属性(不包括属性)应记录在Attributes部分中,并遵循与属性相同的格式:

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class SampleClass:
    """Summary of class here.

    Longer class information...
    Longer class information...

    Attributes:
        likes_spam: A boolean indicating if we like SPAM or not.
        eggs: An integer count of the eggs we have laid.
    """

    def __init__(self, likes_spam: bool = False):
        """Initializes the instance based on spam preference.

        Args:
          likes_spam: Defines if instance exhibits this preference.
        """
        self.likes_spam = likes_spam
        self.eggs = 0

    @property
    def butter_sticks(self) -> int:
        """The number of butter sticks we have."""

Strings

format方法 (f-string)

Python
1
x = f'name: {name}; score: {n}'

这种等价于普通的string-literal写法

Python
1
x = 'name: %s; score: %d' % (name, n)

在一般情况下,我更倾向于f-string写法,因为它更简洁,我是懒狗🐶

字符串引号字符保持一致

我的习惯是外层用双引号,内层如有实际引号,则使用单引号

Python
1
"'Hi Grace!' Why are you hiding your eyes?"

对于多行字符串,首选 """ 而不是 '''

Python
1
2
3
4
5
6
7
8
long_string = """This is fine if your use case can accept
    extraneous leading spaces."""

long_string = ("And this too is fine if you cannot accept\n"
                 "extraneous leading spaces.")

long_string = ("And this is fine if you cannot accept\n" +
                 "extraneous leading spaces.")

正如上面所示,连接两个引号子句可以直接换行,也可以在第一个句末给一个+ 😊

logging msg 不可以用 f-string

Python
1
2
3
import tensorflow as tf
logger = tf.get_logger()
logger.info('TensorFlow Version is: %s', tf.__version__)

原因是:

log的语法格式是log(log_msg,reason),只有 a string literal 才能与之匹配

Error Messages

写错误处理一定要注意,不仅要报“这是什么类型的错误”,还要报“导致这个错误的原因”

Python
1
2
3
4
5
6
7
8
if not 0 <= p <= 1:
    raise ValueError(f'Not a probability: {p=}')

try:
    os.rmdir(workdir)
except OSError as error:
    logging.warning('Could not remove directory (reason: %r): %r',
                    error, workdir)

当异常被引发时,程序会立即停止执行当前函数或代码块,并开始查找最近的异常处理机制(即 try/except 语句):

  • 如果程序 没有捕获该异常,则会导致程序的终止,并且错误信息会显示在终端 或控制台中
  • 如果 try-except block捕获,程序不会因为异常而终止,而是会捕获异常并输出 错误信息

对于上面的例子:

p不在0到1之间时,会抛出ValueError异常,输出错误并终止程序

rmdir执行失败,程序会log出错误,但是还是正常执行下去

Files and Stateful Resources

有开就有关,事实上手动关闭有状态资源这件事对程序员要求很高,谁能记得住那么多东西呢?🤡

而且这种手动关闭有状态资源的操作很多时候会带来一些意想不到的 副作用

因此我们需要“自动化析构函数” -> with 应运而生

管理文件和类似资源的首选方法是使用 with 声明

Python
1
2
3
with open("hello.txt") as hello_file:
    for line in hello_file:
        print(line)

对于不支持with语句的类文件对象,请使用 contextlib.closing()

Python
1
2
3
4
5
import contextlib

with contextlib.closing(urllib.urlopen("http://www.python.org/")) as front_page:
    for line in front_page:
        print(line)

TODO Comments

TODO 是一个好习惯,一般写法有两种:

第一种,老版,我更喜欢这种风格

Python
1
# TODO(bxhu): merge cli into skyserve.

第二种,新版

Python
1
# TODO: crbug.com/192795 - Investigate cpufreq optimizations.

一般来说,出于礼仪,我们很少在TODO中指派他人去做某任务,各司其职比较好😄

Imports formatting

“导入格式”体现在文件开头,我们常会在这里引入“官方库” “第三方库” “文件系统中的另一文件(的函数/类对象)”

import 这个段落一般拆解成三个“子段落” (子段落中的具体内容按照字典序来排布)

  • 第一部分:标准库导入
  • 第二部分:第三方模块或包导入
  • 第三部分:文件系统中的另一文件 (的函数/类对象)
Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# standard library
import collections
import queue
import sys

# 3rd party
from absl import app
from absl import flags
import bs4
import cryptography
import tensorflow as tf

# file system itself
from book.genres import scifi
from myproject.backend import huxley
from myproject.backend.hgwells import time_machine
from myproject.backend.state_machine import main_loop
from otherproject.ai import body
from otherproject.ai import mind
from otherproject.ai import soul

Naming Convention

命名规范很有意思,我们来说一些有新意的🔥

  • e 常用作 try-exceptionexception 句柄
  • f 常用作 file的文件句柄
Python
1
2
3
4
try:
    x = 1 / 0  # 会抛出除零错误
except ZeroDivisionError as e:
    print(f"Caught an exception: {e}")
Python
1
2
with open('example.txt', 'w') as f:
    f.write('Hello, world!')

Type Annotations

第一件事,我们要说明如何换行 ,这点在函数定义阶段尤为常见:

这一块google doc提供了很高的自由度,不过我有我独特的审美,因此这里介绍的是我自己的要求 🦸

  1. 一行一个参数
  2. 如果函数名、最后一个参数和返回类型的组合太长,请换行缩进 4 格,并将右括号与def 对齐:

为了确保返回类型也有自己的行,可以在最后一个参数后面放置一个逗号

Python
1
2
3
4
5
6
7
def my_method(
    self,
    first_var: int,
    second_var: Foo,
    third_var: Bar | None,
) -> int:
  ...

第二件事,我们要说一说 “选择性默认值”

最基础写法 (一个参数只有一种可能的类型)

Python
1
2
# default a equals to 0
def func(a: int = 0) -> int:

此时在调用func()时,允许直接调用而不给a赋值(因为默认值已存在)

高阶写法 (一个参数有多种可能的类型)

Python
1
2
3
4
def modern_or_union(
    a: str | int | None, 
    b: str | None = None
) -> str:

a: str | int | None:这里使用了 Python 3.10 引入的“管道符号” (|) 来表示类型联合;这意味着参数 a 可以是字符串 (str)、整数 (int) 或者 None

b: str | None = None:参数 b 也使用了相同的语法,表示它可以是字符串或None,并且有一个默认值为 None

一个参数有多种可能的类型

除了上述的管道符号外,还有别的写法,比如使用 OptionalUnion 表达式:

Python
1
2
3
4
def union_optional(
    a: Union[str, int, None], 
    b: Optional[str] = None
) -> str:

Union是“在这几个之间选一个”,Optional限定为只能“二选一”

C++ Style