1. 常用if __name__ == '__main__'
假设我们在 module.py 中写了一个模拟连接到数据库的函数,并调用它:
import time
def sim_conn_to_db() -> None:
print('Connecting to database...')
time.sleep(3)
print('Connected to database successfully!')
sim_conn_to_db()
输出:
Connecting to database...
Connected to database successfully!
现在,假设我们保持 module.py 的代码不变,然后在 main.py 中调用 sim_conn_to_db() 方法:
from module import sim_conn_to_db
sim_conn_to_db()
输出:
Connecting to database...
Connected to database successfully!
Connecting to database...
Connected to database successfully!
wow,你发现了么?sim_conn_to_db() 方法执行了两遍。这是为什么呢?原因在于我们通过 from module import sim_conn_to_db 语句引入 sim_conn_to_db() 方法时,Python解释器会加载整个 module.py 脚本,逐行加载代码。因此,在加载的时候已经执行了一次 sim_conn_to_db() 方法;然后,在 main.py 脚本中我们又再一次调用了 sim_conn_to_db() 方法,所以,该方法被执行了2次。
那么改如何避免这种情况呢?这就是今天我向大家分享的第1个好习惯,总是使用 if __name__ == '__main__' 语句执行检查。比如,在 module.py 文件中加入改语句:
import time
def sim_conn_to_db() -> None:
print('Connecting to database...')
time.sleep(3)
print('Connected to database successfully!')
if __name__ == '__main__':
sim_conn_to_db()
然后,我们再次执行 main.py 文件,sim_conn_to_db() 方法就不会被执行2次。因为 if __name__ == '__main__' 语句确保了只有直接执行 module.py 文件的时候,sim_conn_to_db() 方法才会被调用,在别的 .py 文件中调用该方法时只会执行一次。
如果你使用的Pycharm IDE的话,if __name__ == '__main__' 的另一个好处就是,在行号处会出现一个绿色的运行按钮,单击该按钮你可以直接执行当前脚本文件或其他操作.
2.main函数让代码更有组织性
假设我们有以下代码片段:
def greeting(name: str) -> None:
print(f"Hello {name}!")
def bye() -> None:
print("Bye! See you soon!")
if __name__ == '__main__':
greeting(name='Jack')
bye()
这里,我们定义了两个简单的问候和告别方法,并且调用它们。完全没有问题,正确实现了预期功能。但是,如果有很多个函数呢?也在 if __name__ == '__main__': 语句中调用的话就显得臃肿且复杂,毫无组织性。因为 if __name__ == '__main__': 语句是函数的执行入口,我们应该尽量保证简洁。
这个时候就用到了今天我给大家分享的第2个好习惯,定义一个 main 函数,将所有函数调用和其他逻辑都放到该函数中,然后在入口处只需要调用 main 函数即可。
def greeting(name: str) -> None:
print(f"Hello {name}!")
def bye() -> None:
print("Bye! See you soon!")
def main() -> None:
greeting(name='Jack')
bye()
if __name__ == '__main__':
main()
这样,即使后续有新增的方法调用,只需要在main函数中添加即可,不仅确保了脚本运行入口处语句的简洁,同时让整个代码结构更具组织性。另一方面,也让你的代码更具可读性。
Tips:你可以将 main 函数当做是你的管家,无论多复杂、数量众多的事务都交给管家一个人去统筹安排,而你只需要掌控管家一个人即可。
3. 保持函数的单一性和简洁性
假设我们有一个根据姓名、年龄和身份证来判断某人是否能够加入俱乐部的方法,如下:
def enter_club(name: str, age: int, has_id: bool) -> None:
if name.lower() == 'stefan':
print('Get out of here Stefan, we don\'t want to trouble.')
if age >= 18 and has_id:
print('You may enter the job.')
else:
print('Sorry, you may not enter the club.')
def main() -> None:
enter_club(name='Stefan', age=18, has_id=True)
enter_club(name='Bob', age=29, has_id=True)
enter_club(name='Ellena', age=20, has_id=False)
enter_club(name='Bony', age=21, has_id=True)
if __name__ == '__main__':
main()
很明显,上面的代码正确实现了我们想要的功能。但我并不推荐这种做法,因为 enter_club 方法中涉及了很复杂的逻辑,并不符合Python函数编程的单一职责原则。
因此,这里就会用到今天我给大家分享的第3个好习惯——尽可能保持函数的单一性和简洁性。enter_club 方法中同时涉及到姓名、年龄和身份的判断,实际上我们可以将其拆分成多个具有单一职责的函数。
def is_black_list(name: str) -> bool:
return name.lower() == 'stefan'
def is_adult(age: int, has_id: bool) -> bool:
return age >= 18 and has_id
def enter_club(name: str, age: int, has_id: bool) -> None:
if is_black_list(name):
print('Get out of here Stefan, we don\'t want to trouble.')
if is_adult(age, has_id):
print('You may enter the job.')
else:
print('Sorry, you may not enter the club.')
def main() -> None:
enter_club(name='Stefan', age=18, has_id=True)
enter_club(name='Bob', age=29, has_id=True)
enter_club(name='Ellena', age=20, has_id=False)
enter_club(name='Bony', age=21, has_id=True)
if __name__ == '__main__':
main()
这里,我们将对姓名和年龄、身份的判断拆分为两个独立的方法 is_black_list 和 is_adult。拆分后的函数职责单一,逻辑简单清晰。确保实现相同功能的同时,增强了代码的可读性、可维护性以及可扩展性。
4. 尽可能多用类型提示
第4个好习惯就是类型注释(type hints),如果你还不知道什么是类型注释,以及它的诸多好处,你可以阅读我的上一篇文章“Python类型提示(type hints):提升代码质量与可读性的利器!”。
这里,我们只是举几个简单的例子来说明类型注释的实用之处。
4.1 降低代码潜在bug或错误的风险
比如,以下代码段:
def add(a, b):
return a + b
if __name__ == '__main__':
result = add(a=1, b=2)
print(result) # 3
正确实现了整数的加法功能。但是,由于参数 a 和 b 并没有任何类型说明,如果你的用户或同事在调用 add 方法时,给了错误的参数类型,很可能造成意料之外的结果甚至程序错误,比如,
def add(a, b):
return a + b
if __name__ == '__main__':
result = add(a='1', b=2)
print(result)
只要给参数 a传递字符串值,都会导致 TypeError,因为字符串和整数不支持连接操作。类似地,如果给参数 b 传递字符串值,同样会得到 TypeError,因为整数和字符串不支持加法操作。
然而,如果给参数 a 和 b 都传递字符串值,会发生什么呢:
if __name__ == '__main__':
result = add(a='1', b='2')
print(result) # 12
虽然程序可以正常执行,但是得到的结果是12,这是字符串连接结果,而不是我们想要的整数相加结果。
解决这种潜在错误或不期望结果的方法就是在方法定义中使用类型注释:
def add(a: int, b: int) -> int:
return a + b
这样,在方法调用时,如果给参数传递了不符合定义时所给的参数类型,编译器会提示:
有了这个提示,我们就知道应该给参数传递的是整数类型,而不是字符串。有效降低了潜在bug或错误发生的几率,特别是在大型项目中。因为解决bug永远都是一件令人十分恼火的事😫😫😫。
4.2 提高编码效率
在很多IDE中,都提供了根据上下文进行代码补齐的功能,比如Pycharm。但是,如果不知道变量类型的前提下,编译器是没办法给你任何推荐的。比如:
这里,我想对一个DataFrame进行一系列的处理,由于不知道变量的数据类型,在输入 . 之后,编译器无法给出有效的推荐。
当我们增加类型注释后:
这时,编译器就会将DataFrame具有的所有方法和属性全都列出来供我们选择,可以快速找到我们想要的方法或属性,从而提高编码效率。
关于类型注释的更多用法,你可以阅读我的上一篇文章“Python类型提示(type hints):提升代码质量与可读性的利器!”。
5. 善用列表推导式
假设有一个不同年龄构成的列表,我们想要筛选出所有大岁数(年龄≥30)的人并存储在新的列表中:
ages: list[int] = [18, 16, 20, 35, 40, 53, 65, 32, 80, 96]
olders: list[int] = list()
for age in ages:
if age >= 30:
olders.append(age)
print(f'People with older age: {olders}')
# Output: People with older age: [35, 40, 53, 65, 32, 80, 96]
这里我们通过循环实现了想要的功能。但其实,还有另一种更简洁的方法,就是今天我给大家分享的最后一个好习惯——善用列表推导式:
ages: list[int] = [18, 16, 20, 35, 40, 53, 65, 32, 80, 96]
olders: list[int] = [age for age in ages if age >= 30]
print(f'People with older age: {olders}')
完整实现相同功能的同时,一下子将4行的核心代码编程1行,是不是既简洁又高效呢?
6. 结论
感谢你的阅读,希望本文简洁、清晰的内容能够对你有所帮助!See you next time!