技术栈:Google 50 条编码规范曝光,避坑指南全在这里!(一)

B站影视 日本电影 2025-09-01 12:00 2

摘要:这些最佳实践已经经受住了时间的考验,它们的价值几乎不会因为新的趋势或技术的出现而有所改变。这些法则将专业编程人员与业余爱好者区分开来,其中许多原则也可以适用于其它编程语言。

Python 最佳实践的条数众多,可能达到数百或数千条。每个人都有自己的见解和建议,在本文中,我们将集中讨论 50 条被广泛认可的 Python 最佳实践。

这些最佳实践已经经受住了时间的考验,它们的价值几乎不会因为新的趋势或技术的出现而有所改变。这些法则将专业编程人员与业余爱好者区分开来,其中许多原则也可以适用于其它编程语言。

在编程中,注释是向其他开发者解释代码意图的一种方式。

然而,注释有时也会带来一些问题:

过时信息:随着代码的修改,注释有时没有及时更新,从而误导其他人。分散注意力:过多的注释可能会分散注意力,使代码的可读性降低。限制思考:注释可能让读者依赖文字说明,而不是直接去理解代码本身的逻辑。

因此,在大多数情况下,我们应尽量通过 清晰的代码结构、合理的命名和恰当的抽象 来表达意图,而不是依赖注释。

当然,这并不是说完全不需要注释。在第 14 和 15 条法则中,我们将具体讨论在什么情况下应该使用注释,什么情况下应该避免使用注释。

一些开发人员可能会使用类似 name_of_variable_str 或 name_of_variable_int 的变量名来明确指出变量的类型。

然而,这种做法通常是不必要的,特别是当变量的类型从变量名中已经可以直观推断时。例如,name 自然让人联想到字符串,而 age 一般会是整数。再额外添加 _str 或 _int,只会让变量名变得冗长。

# ❌不推荐的做法user_name_str = "John Doe"user_age_int = 30# ✅推荐的做法user_name = "John Doe"user_age = 30

对于那些不能从变量名推断出变量类型的情况,使用注释是更好的选择。这样可以在保持变量名简洁的同时,清晰地表达变量的类型。

类通常代表一类对象或概念,它们拥有特征和行为。因此,类名应该始终使用名词,可以提高代码的可读性,并减少冗余。

例如,一个 Goat 类可以表示山羊这一概念,并包含山羊的特征(如角)和行为(如点头)。因此,Goat.get_horn_length 的命名方式比 GetGoat.get_horn_length 更自然、更清晰,也更符合面向对象编程的习惯。

函数通常表示某种操作或行为,因此命名时应当使用 动词或动词短语。这种命名方式不仅能让人一眼看出函数的作用,还能减少对注释的依赖,从而提高代码的可读性和可维护性。 例如:

def process_and_save_user_data(input_data): ......

除了名字清晰,函数的 签名(即参数和返回值的定义)同样重要。在定义函数时,应该始终指定参数的类型和函数的返回类型。这样做的好处有:

提高代码可读性:通过查看函数签名,开发者就可以立即了解函数需要什么样的参数以及它将返回什么类型的值。减少错误:明确指定类型可以帮助IDE(如PyCharm、VSCode等)进行类型检查,从而在编写代码时就发现潜在的错误。便于团队协作:当团队成员需要理解你的代码时,明确的类型可以减少沟通成本,提高协作效率。

例如:

# ❌不推荐的做法def convert_to_string(num): return "My new string is " + str(num) # ✅推荐的做法def convert_to_string(num: int) -> str: return "My new string is " + str(num)

在函数设计中,遵循“单一职责原则”(Single Responsibility Principle)是非常重要的,这意味着每个函数应该只做一件事情,并且做好。这样做有几个好处:

易于理解:当每个函数只做一件事时,它通常更简单,更容易理解。易于测试:每个函数可以独立测试,确保它正确执行其单一任务。易于维护:如果需要修改函数的行为,只影响一个函数,而不是多个。提高代码重用性:当函数专注于单一任务时,它可以在更多的上下文中重用。

❌不推荐的做法:

def check_if_Address_is_valid(address): if address.is_valid: latitude = address.getlatitude longitude = address.getlongitude return (latitude, longitude)

在这个例子中,check_and_get_location 函数同时执行了地址验证和经纬度位置获取两个功能,这违反了单一职责原则。更好的做法是将这些功能分开。

✅推荐的做法:

def check_if_address_is_valid(address): return address.is_validdef get_latitude(address): latitude = address.getLatitude return latitude def get_longitude(address): longitude = address.getLongitude return longitude

这种方法将每个函数限定为单一功能,虽然代码行数可能看起来更长了,但每个函数的职责都非常明确,阅读代码时可以快速理解每个函数的作用。同时可维护性更强了,例如如果需要修改地址验证逻辑,只需要修改check_if_address_is_valid函数,不影响其他函数。最后就是这些函数可以在其他地方重用,例如在不同的程序或模块中获取地址信息。

理解“单一功能”可能对初学者来说有些困难。关键是要明确函数的目的,确保它只处理一个特定的任务。如果你发现一个函数同时做了多件事,或者其中的部分逻辑可以被提取到独立函数中,那就说明它可能违反了单一职责原则。

判断函数是否只承担了一项职责,还有一个有用的思路:检查它是否同时处理了 不同抽象层次 的任务。如果是这样,就应该考虑将其拆分成更小、更专注的函数。怎么理解这一点了?

首先需要了解“抽象层次”的概念。

抽象层次(abstraction Levels)是软件开发和编程中用来描述不同复杂性或细节级别的概念。在编程中,抽象层次可以帮助我们更好的组织代码,使其更易于理解和维护。抽象层次通常分为:

高抽象层次(High-Level Abstraction):涉及的是程序的主要功能或目标,比如“处理订单”或“更新用户数据”。这些是程序要完成的核心任务,但并不涉及具体的实现细节。低抽象层次(Low-Level Abstraction):涉及的是实现这些功能的具体操作,比如“从数据库读取数据”或“计算折扣”。这些是实现高抽象层次功能所需的具体步骤。

如果一个函数同时包含了高层次和低层次的逻辑,那么它就可能在做多件事。一个好的函数应该专注于一个单一的抽象层次,避免混合高层次和低层次的操作。这一点在法则 7可以看到。

例如,一个函数要么负责高层次的业务逻辑,如“处理订单”,要么负责底层的具体操作,如“将订单信息保存到数据库”。如果一个函数同时处理了这两种操作,它就违背了同一抽象层次的原则。

如果一个函数既包含业务逻辑,又包含具体实现细节,就会让职责变得模糊,降低可读性和可维护性。

✅推荐的做法:

def calculate_average(numbers): total = sum(numbers) count = len(numbers) average = total / count return average

此函数包含抽象程度较低的语句。例如sum。len等。

❌不推荐的做法:

def calculate_average: numbers = get_numbers # 获取数据 numbers_plus_30 = [num + 30 for num in numbers] # 数据转换 total = sum(numbers_plus_30) # 计算总和 count = len(numbers) # 计算长度 average = total / count # 计算平均值 return average

在这个例子中,get_numbers 是一个较高级的抽象操作,表示“获取数据”。列表推导是一个中级抽象层次,表示“对数据进行变换”,而 sum 则是一个低级的数学操作。这种混合多个抽象层次的写法会让代码变得不清晰、不易理解。

函数的名称应该与它的参数紧密相关,确保名称和参数之间有逻辑上的关联。这样可以让代码更加直观和易于理解。

不推荐的做法:write(True),在这个例子中,我们无法明确知道 write 函数到底在做什么,因为参数 True 没有提供足够的信息来解释这个函数的目的。推荐的做法:write(name),这种方式更明确,因为它清晰地说明了我们正在“写入一个名字”。任何读到这段代码的人都可以轻松理解 write 函数的作用,而不需要去猜测或查看整个函数的实现。

函数的核心目的是为了提高代码的重用性和可维护性。一个函数越大,它的重用性就越低,也更难以理解和维护。这也是为什么一个函数只能完成一个特定任务的原因。

有些时候,开发人员在命名变量名或函数名时,会使用一些无法增加额外信息的词语,这些词语被称为噪音或冗余词语。这样的命名方式会让人感到困惑,例如:

getProfilegetProfileInfogetProfileInfosgetProfileAccount

如果不事先了解这些函数的实现,开发人员可能根本不知道该调用哪个函数,因为它们之间的差异并不清晰。

✅推荐的做法是:

get_user_profile #明确表示获取用户的个人资料。get_user_account #明确表示获取用户的账户信息。

这种命名方式直观、明确,避免了模糊或重复的词语,让代码更容易理解和使用。

“干净的代码”这个概念听起来有点抽象,但其实它的意思很简单。想象一下,如果你的房间里堆满了杂物,你可能找不到你需要的东西,而且感觉不舒服。同样,如果你的代码里充满了不必要的复杂和混乱,它也会很难理解和维护。

那么,什么才是“干净的代码”呢?

结构良好:代码组织得当,逻辑清晰,就像一个整洁的房间,每样东西都有它的位置。清晰:代码容易理解,就像一本写得好的书,读起来不费力。有条理:代码中的每个部分都有明确的职责,不会混在一起。

保持代码干净的关键在于简化逻辑、使用有意义的命名、避免不必要的复杂性,并遵循良好的编码规范和设计模式。

开放封闭原则 (Open-Closed Principle, OCP) 是面向对象编程中的一个重要原则。它规定类、方法或函数应该对扩展开放,但对修改封闭。也就是说,我们应当设计出可以轻松扩展的代码,而不是每次遇到新需求都去修改已有的逻辑。

举个违反 OCP 的例子,假设我们有一个 Address 类,用于获取国家的首都:

class Address: def __init__(self, country): self.country = country def get_capital(self): if self.country == 'canada': return"Ottawa" if self.country == 'nigeria': return"Abuja" if self.country == 'america': return"Washington D.C" if self.country == 'united Kingdom': return"London"

这明显不符合 OCP原则,因为每增加一个国家时,我们都需要编写一个新的 if 语句来处理它。虽然处理几个国家看起来还算简单,但如果需要考虑 100 个或更多的国家,这样的做法就会显得非常笨重和低效。

更好的方法是将国家与首都的映射关系存储在字典中:

capitals = { 'canada': "Ottawa", 'nigeria': "Abuja", 'america': "Washington D.C", 'united Kingdom': "London"}class Address: def __init__(self, country): self.country = country def get_capital(self): return capitals.get(self.country, "Capital not found")

❌下面的代码是不推荐的:

class PaymentProcessor: def __init__(self, amount): self.amount = amount def charge_debit_card(self): print('Your debit card is being processed for payment') print(f'You are to be charged {amount}') def charge_credit_card(self): print('Your credit card is being processed for payment') print(f'You are to be charged {amount}')

这种写法的问题是:每增加一种新的支付方式,就必须修改 PaymentProcessor 类,违背了开闭原则。

✅推荐的做法是将支付抽象出来,通过继承来扩展:

from abc import abstractmethodclass PaymentProcessor: @abstractmethod def pay_tax(self): passclass DebitCardPaymentProcessor(PaymentProcessor): def pay_tax(self, amount): print(f'Your debit card is being processed for payment') print(f'You are to be charged {amount}')class CreditCardPaymentProcessor(PaymentProcessor): def pay_tax(self, amount): print(f'Your credit card is being processed for tax payment') print(f'You are to be charged {amount}')

这样设计的好处在于,如果我们需要添加新的支付选项(例如加密货币或 PayPal),我们不需要修改 PaymentProcessor 类,只需要简单地定义一个新的支付类:

from abc import abstractmethodclass PaymentProcessor: @abstractmethod def pay_tax(self): passclass DebitCardPaymentProcessor(PaymentProcessor): def pay_tax(self, amount): print(f'Your debit card is being processed for payment') print(f'You are to be charged {amount}')class CreditCardPayment(PaymentProcessor): def pay_tax(self, amount): print(f'Your credit card is being processed for tax payment') print(f'You are to be charged {amount}')class CryptoPaymentProcessor(PaymentProcessor): def pay_tax(self, amount): print(f'Your crypto wallet is being processed for tax payment') print(f'You are to be charged {amount}')

里氏替换原则(Liskov Substitution Principle, LSP)是面向对象设计中的一个重要原则,它指出:子类对象必须能够替换掉它们的父类对象,并且不破坏系统的正确性。

换句话说,如果一个程序依赖于某个父类,那么在不改变逻辑的情况下,它应该能够无缝地使用任意一个子类对象。

理想情况下,代码应该能够“自解释”,通过清晰的命名和合理的结构让人一眼就能看懂。但在某些场景下,注释仍然是必要的,它能帮助开发者更好地理解代码的背景或潜在问题。

以下是几种“好”注释的最佳实践示例:

信息性注释:这类注释提供额外的上下文信息。例如,通过注释解释某个函数的返回值类型或用法,或说明一个复杂算法的来源和用途。然而,这种注释有时可能显得多余,尤其是在函数或变量的命名已经足够描述性和直观的情况下。TODO 注释:TODO 注释有助于提醒其他程序员某个功能或任务尚未完成或需要进一步改进。比如,有可能存在一种更好的实现方法,或者当前代码存在一些问题需要修复。这类注释的价值在于,它能防止遗忘问题,但需要记得在修复或优化完成后及时将注释删除。警告注释:当一段代码可能存在风险时,可以用注释提前告知其他开发者。例如,如果一段代码可能导致系统过载,那么类似于 #CONSUMES A LOT OF COMPUTING RESOURCES 的注释对于其他程序员而言会非常有帮助。这种注释能让团队成员更好地理解可能存在的性能瓶颈或资源消耗,避免代码在不恰当的环境下使用。

注释的主要目的是帮助别人理解代码,但如果注释没有提供额外的信息,甚至重复了代码已经表达的内容,那么这样的注释就是多余的。多余的注释不仅起不到作用,反而会让代码显得冗长和杂乱。下面是几种常见的“无效注释”场景:

噪音注释
噪音注释是指那些重复了代码中已经显而易见信息的注释,它们并没有提供任何额外的信息,反而只会让代码显得冗长,因此完全可以省略。

噪音注释的示例如下:

# 添加到动物列表 animal.append(dog)

非局部信息的注释

另一个常见问题是注释中包含与当前上下文无关的全局信息,而这些信息和当前的局部上下文无关,这类注释往往会分散注意力,甚至造成误解。在编写注释时,确保注释仅与它所针对的函数或语句直接相关。

不清晰的注释

有时我们会写一些在自己看来显而易见的注释,但对于别人来说却难以理解。

对于短小的函数来说,通常不需要额外的注释,因为这些函数往往可以通过一个良好的命名来自我解释。函数越短小、简洁,越有可能使用一个直观的名称来表达其功能和意图,因此无需额外的注释来说明。

例如,有一个函数,它的作用是返回一个字符串的大写形式。你可以这样写:

def to_uppercase(text): return text.upper

这个函数很短,而且函数名 to_uppercase 已经很清楚地表达了它的功能,所以不需要额外的注释。如果函数名不够直观,应当要做的是修改函数名,而不是添加注释。

源文件的长度应控制在 100 到 200 行之间,最多不超过 500 行,除非有充分的理由打破这个原则。

保持源文件简短有许多显而易见的好处,例如提高代码的可重用性和可读性。更短的文件更容易维护和更新。

合理的空行可以让代码的结构更清晰,使其更易于理解。

空行在代码中扮演着视觉分隔符的角色,它提醒读者:当前的逻辑已经结束,接下来进入新的部分。这样一来,代码的层次就更加分明,对于理解代码的结构和流程也有帮助。

假设有一个函数,用来计算两个数字的和与差:

❌不推荐的写法:

def calculate(a, b): sum = a + b print("Sum:", sum) diff = a - b print("Difference:", diff)

✅推荐的写法:

在“计算和”和“计算差”之间加一行空行,就能立刻凸显这是两个独立的逻辑步骤。

想象一下,当你必须不断滚动到脚本的顶部去查找某个函数的定义,才能知道某个函数的作用以及它与调用它的位置有何关系,这样的体验是非常糟糕的。

为了避免这种情况,建议将函数、变量或类尽量放置在它们最需要的地方,而远离不相关的代码,这样有助于代码的逻辑性和可读性。

空格的使用是一种表示代码中不同元素之间关系强弱的方式。没有空格表示更紧密的关联,而空格则表示较弱的关联。例如,在定义函数时:

def create(name): print(name)def create (name): print (name)

而在传递多个参数时,我们应该使用空格将它们分开,以表示它们是独立的实体。

def greet(first_name, last_name): print("Hello", first_name, last_name)

每个开发人员都有自己独特的编程风格,比如命名方式、缩进习惯,甚至打印语句的格式。但在团队合作中,需要放弃个人偏好,遵循团队统一的编码规范才是最重要的。

想象一下,如果团对中的每个人都按照自己的习惯写代码,最终的代码库就会显得混乱不堪。

例如团队的编码规范要求函数名用小写字母和下划线分隔(比如 calculate_sum),但你个人喜欢用驼峰命名法(比如 calculateSum)。如果你坚持用自己的风格,其他人读你的代码时就会感到困惑。但如果大家都按照团队的规则来,代码就会更整齐、更易读。同时遵守统一的编码规范可以提高团队协作的效率。

来源:心平氣和

相关推荐