代码精进:工作中学到的12个代码风格

在工作中,我们编写代码时尽可能地使其易于阅读。这意味着以下几点:

  • 变量名有意义且更长(而不是 a, b 和 c)
  • 函数名有意义且更长
  • 许多注释和文档解释代码
  • 到处都是类型提示
  • 字符串似乎更长、更啰嗦
  • 等等

以下是我在过去几年的工作中学到的一些生产级别的 Python 代码风格。

1) 使用括号的元组解包

这是一些正常的元组解包:

a, b = (1, 2)

在生产级别的代码中,我们通常不使用像 ab 这样的变量名 —— 相反,我们的变量名会变得更长且描述性更强。

因此,我们可能会使用括号来帮助元组解包,如下所示:

x_coordinate, y_coordinate = (1, 2)

注意,通过这种方式,我们的元组解包可以容纳更长(且更具描述性)的变量名。

一个更现实的例子:

first_name, last_name = ("John", "Doe")

2) 多行列表推导式

这是正常的列表推导式的样子:

squared_numbers = [i**2 for i in range(10)]

在生产代码中,我们通常不使用像 i 这样的变量名 —— 我们的变量通常是更长且描述性的,以至于我们不能将整个列表推导式放在一行代码中。

我会这样重写上述代码:

squared_numbers = [number**2 for number in range(10)]

一个更真实的例子:

full_names = [f"{first} {last}" for first, last in [("John", "Doe"), ("Jane", "Doe")]]

3) 使用括号组合字符串

生产级别的字符串通常由于过于啰嗦而无法在一行内完成。因此我们使用括号来组合它们。

message = ("Hello, " "my name is " "John Doe")

注意 —— 在括号内,字符串字面量(使用引号)会自动拼接在一起,我们不需要使用 + 操作符来实现这一点。

4) 多行方法链式调用,借助括号

正常的方法链式调用:

result = object.do_something().do_another()

在生产级别的代码中,方法名通常更长,我们通常会有更多的方法链式调用在一起。

再次,我们使用括号将所有这些内容放入多行中,而不是缩短任何方法名或变量名。

result = (object.do_something()
          .do_another()
          .do_a_third())

注意,如果我们在括号内进行方法链式调用,我们不需要使用反斜杠 \ 来明确换行。

5) 索引嵌套字典

正常索引嵌套字典的方式:

value = dictionary["key"]["subkey"]

这里有一些问题:

  • 生产级别的代码中的字典有更多的嵌套层级
  • 字典的键名更长
  • 我们通常无法将整个嵌套索引代码挤在一行内。

因此,我们将其拆分为多行,如下所示:

value = (dictionary
         ["key"]
         ["subkey"])

如果这样还不够,我们可以将索引代码拆分为更多的行:

value = (dictionary["key"]
         ["subkey"]["subsubkey"])

或者如果我们仍然觉得这样难以阅读,我们可以这样做:

key1 = "key"
key2 = "subkey"
key3 = "subsubkey"
value = dictionary[key1][key2][key3]

6) 编写可读且信息丰富的函数

我们以前作为学生时这样写函数:

def calculate(a, b):
    return a + b

^ 包含此类代码的 PRs 很可能会被拒绝

  • 函数名没有描述性
  • 参数变量名不好
  • 没有类型提示,所以我们不知道每个参数应该是什么数据类型
  • 没有类型提示,所以我们也不知道函数应该返回什么
  • 没有 docstring,所以我们不得不推断我们的函数是做什么的

以下是我们在生产级别的 Python 代码中编写函数的方式

def add_two_numbers(number1: int, number2: int) -> int:
    """
    Add two numbers together.

    Parameters:
    number1 (int): The first number to add.
    number2 (int): The second number to add.

    Returns:
    int: The sum of the two numbers.
    """
    return number1 + number2
  • 函数名应该有描述性
  • 参数名应该有描述性,而不是例如 a, b, c
  • 每个参数都应该有类型提示
  • 函数的返回类型也应该包含
  • 应该包含一个 docstring,详细说明函数的作用、它接受的参数以及它的输出,作为一个字符串,用三引号括起来。

7) 尽可能减少缩进级别

这是一个 for 循环。如果我们的条件满足,我们做一些事情。

for item in items:
    if condition:
        do_something()

^ 一些同事和高级工程师实际上可能会对这段代码吹毛求疵 —— 它可以通过减少 do_something() 的缩进级别来写得更好。

让我们重写这个代码,同时减少 do_something() 的缩进级别:

for item in items:
    if not condition:
        continue
    do_something()

注意,do_something() 的缩进级别已经减少了 1 级,只是通过使用 if not condition 而不是 if condition

在生产级别的代码中,可能会有更多的缩进级别,如果缩进太多,我们的代码就会变得烦人且难以阅读。因此,这个技巧使我们能够使我们的代码稍微更整洁和易于人类阅读。

8) 使用括号的布尔条件

这是一个带有 3 个条件的 if 语句,使用 and 关键字连接。

if condition1 and condition2 and condition3:
    do_something()

在生产级别的代码中,条件变得更长,可能有更多的条件。因此,我们解决这个问题的一种方式是将这个巨大的条件重构为一个函数。

或者如果我们认为没有必要仅仅为了这个条件就编写一个新函数,我们可以使用括号编写我们的条件语句。

if (condition1 and
    condition2 and
    condition3):
    do_something()

这样,我们就不用被迫为这个单一的条件语句编写一个新函数或变量,同时我们能够保持它的整洁和可读性。

有时我实际上可能更喜欢这样写,尽管这只是基于个人偏好:

if all([
    condition1,
    condition2,
    condition3
]):
    do_something()

9) 防御 None 值

正常访问对象某个嵌套属性的代码。

name = dog.owner.name

这段代码可能存在的问题,可能会导致我们的 PR 被拒绝:

  • 如果 dog 是 None,我们会得到一个错误
  • 如果 dog.owner 是 None,我们也会得到一个错误
  • 基本上,这个代码块没有保护 dogdog.owner 可能是 None 的可能性。

在生产级别的代码中,我们需要积极防御这种情况。以下是我会如何重写这段代码。

if dog and dog.owner:
    name = dog.owner.name

Python 中的 and & or 操作符是短路操作符,这意味着它们一旦有了明确的答案就停止评估整个表达式。

  • 如果 dog 是 None,我们的表达式在 if dog 处终止
  • 如果 dog 不是 None,但 dog.owner 是 None,我们的表达式在 if dog and dog.owner 处终止
  • 如果我们没有任何 None 值,dog.owner.name 被成功访问,并用于与字符串 “bob” 进行比较

通过这种方式,我们额外保护了 dogdog.owner 可能是 None 值的可能性。

10) 防御遍历 None 值

这是我们可能遍历某个可迭代对象(例如列表、字典、元组等)的方式。

for item in mylist:
    process(item)

这个问题在于它没有保护 mylist 可能是 None —— 如果 mylist 碰巧是 None,我们会因为不能遍历 None 而得到一个错误。

以下是我如何改进这段代码:

for item in (mylist or []):
    process(item)

表达式 “mylist or None”

  • 如果 mylist 是真值(例如非空的可迭代对象),则返回 mylist
  • 如果 mylist 是假值(例如 None 或空的可迭代对象),则返回 [](空列表)

因此,如果 mylist 是 None,表达式 “mylist or []” 返回 [] 代替,我们就不会遇到不想要的异常。

11) 内部函数以 _ 开头

这是一个示例类。这里,run 方法使用其他方法 cleantransform

class Processor:
    def run(self, data):
        clean_data = self.clean(data)
        transformed_data = self.transform(clean_data)
        return transformed_data

    def clean(self, data):
        # Clean data
        pass

    def transform(self, data):
        # Transform data
        pass

在生产级别的代码中,我们力求尽可能明确,因此尝试区分内部方法和外部方法。

  • 外部方法 —— 被其他类和对象使用的方法
  • 内部方法 —— 被类本身使用的方法

按照惯例,内部方法以下划线 _ 开头是一个好的实践。

如果我们重写上述代码,我们得到:

class Processor:
    def run(self, data):
        clean_data = self._clean(data)
        transformed_data = self._transform(clean_data)
        return transformed_data

    def _clean(self, data):
        # Clean data
        pass

    def _transform(self, data):
        # Transform data
        pass

注意 —— 在方法名前添加下划线并不会使其他类和对象隐藏它。实际上,功能上没有区别。

12) 装饰器用于常见功能

这是一个有 3 个函数的类,每个函数做不同的事情。然而,注意不同函数之间有相似的步骤 —— try-except 块,以及日志记录功能。

class Processor:
    def process_one(self):
        try:
            # Process something
            pass
        except Exception as e:
            print(f"Error: {e}")

    def process_two(self):
        try:
            # Process something else
            pass
        except Exception as e:
            print(f"Error: {e}")

    def process_three(self):
        try:
            # Another process
            pass
        except Exception as e:
            print(f"Error: {e}")

减少重复代码的一个好习惯是编写一个包含共同功能的装饰器函数。

def with_logging_and_exception(func):
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            print(f"Error in {func.__name__}: {e}")
    return wrapper

class Processor:
    @with_logging_and_exception
    def process_one(self):
        # Process something
        pass

    @with_logging_and_exception
    def process_two(self):
        # Process something else
        pass

    @with_logging_and_exception
    def process_three(self):
        # Another process
        pass

这样,如果我们想更新共同代码(try-except 和日志记录代码),我们就不再需要在 3 个地方更新 —— 我们只需要更新包含共同功能的装饰器代码。

本文由mdnice多平台发布

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容

  • Introduction This document gives coding conventions for t...
    wuutiing阅读 4,552评论 0 9
  • 更新时间:2016/5/13 介绍 本文档所提供的编码规范,适用于主要的Python发行版中组成标准库的Pytho...
    超net阅读 5,861评论 0 15
  • 不要为了遵守这份风格指南而破坏代码的向后兼容性。 这里有一些好的理由去忽略某个风格指南: 当应用风格指南的时候使代...
    小哲1998阅读 449评论 0 4
  • 高阶函数:将函数作为参数 sortted()它还可以接收一个key函数来实现自定义的排序,reversec参数可反...
    royal_47a2阅读 687评论 0 0
  • 通常代码不单单是写给自己看的。 当代码出现bug而你又想不通哪有问题时,就需要把代码贴到论坛或请身边的人审阅。 或...
    学习之术阅读 848评论 0 2