浅谈如何写出整洁的Python代码

2025-01-11 00:00:00石也牧
科技风 2025年1期

摘要:很多人在编写Python代码时,只注重相关功能的实现,并不关心代码的整洁性。当软件的代码量达到一定规模后,不少开发者感到代码越来越混乱、越来越难以维护。学习Python语言,不能只关注语法规则的掌握。通过代码示例,文章阐述了与提高软件代码整洁性相关的几个方面内容:写代码时,要注意可读性;要撰写功能单一且功能清晰的函数;应充分利用装饰器、生成器和迭代器等Python独有的特性,写出高效的代码;应了解并经常使用Python软件质量保证工具Unittest、Pytest、Pylint和Flake8等测试和扫描代码。

关键词:编程规范;代码可读性;软件可维护性;单元测试

BriefDiscussiononHowtoWriteCleanPythonCode

ShiYemu

FacultyofAIinEducation,CentralChinaNormalUniversityHubeiWuhan430079

Abstract:WhenwritingPythoncode,manypeopleonlyfocusontheimplementationofrelatedfunctionsanddonotcareaboutthecleanlinessofthecode.Whentheamount ;ofsoftwarecodereachesacertainscale,manydevelopersfeelthatthecodebecomesincreasinglyconfusinganddifficulttomaintain.WhenlearningthePythonlanguage,youcan'tjustfocusonmasteringthegrammaticalrules.Throughexamples,thisarticleexplainsseveralaspectsrelatedtoimprovingthecleanlinessofsoftwarecode:payattentiontoreadabilitywhilewritingcode;writefunctionswithsingleandclearintention;makefulluseofdecorators,generatorsanditerators,whicharePython'sspecialfeatures,towriteefficientcode;youshouldunderstandandfrequentlyusePythonSWqualityassurancetoolssuchasunittest,pytest,pylintandflake8,etc.,totestandscanyourcode.

Keywords:Programmingspecifications;codereadability;softwaremaintainability;unittest

实现同样的功能,不同的程序员写出来的代码完全不一样,有的人写的代码很长,既不容易读懂又很难维护,软件的可维护性差,会降低工作效率,相应的项目会越来越难以持续下去,严重的会导致项目组不得不重新设计、编写代码。有的软件作者,在不同时期写的代码风格不一样。有的项目组,每个人都有自己的风格,组内风格不统一,大家沟通起来不顺畅。这些情况的出现,都是因为程序员没有注意到代码的整洁性。本文通过举例,论述了为写出整洁的代码而需要注意的几个地方。

1要有好的可读性

针对逻辑上并不复杂的功能需求,有的编程者写的代码别人很难读懂,有的人看不明白自己几个月前写的代码,这都是因为代码的可读性差。可读性不好的软件,维护起来非常不容易。

写代码的同时,要写相应的注释,写代码和写注释要做到同步;注释要有一定的占比,不能可有可无;定义函数的时候,要有函数功能和各个参数说明的注释;定义类的时候,类的每个属性和方法都要有相应的注释;修改代码的时候,相应的注释也必须修改。虽然注释不参与编译、不参与程序的运行,但注释非常重要,要把注释看作是程序的一个组成部分。

除了增加注释外,文件名、变量名、函数名和类名等要尽可能地做到顾名思义和一望而知[1],不要让阅读代码的人(包括将来的自己)去猜。例如:

classPerson:

def__init__(self,name,email,phone):

self.name=name

self.email=email

self.phone=phone

显然,类Person的初始化函数记录了人名、邮箱和电话。写代码能做到见其名知其意很好,但也不要过度。例如,下面的代码和前面相比,很详尽,但并没有增加可读性,反而让人觉得过于啰唆:

classPerson:

def__init__(self,personal_username,personal_email_address,personl_telephone_number):

self.personal_username=personal_username

self.personal_email_address=personal_email_address

self.personal_telephone_num=personal_telephone_number

2函数功能要单一

假设有如下函数,功能是取得一个列表,然后打印该列表的所有元素:

deffetch_and_show_users():

users=[…]#由某算法得到列表

foruserinusers:

print(user)#显示列表

这个函数有两个功能,但有的用户只需要得到列表,而有的用户只想显示列表。这时,对于某些调用该函数的用户而言,代码里出现了累赘,这增加了代码的冗余,多余的步骤对程序的调试会造成干扰。把前面的函数分解为如下的两个函数,非常方便于用户的调用:

deffetch_users():

users=[]#由某算法得到列表

returnusers

defdisplay_users(users):

foruserinusers:

print(user)#显示列表

再举个例子,某个函数具备下载、解压缩和按照某种规则选取解压之后文件的功能,最好将这个函数分解。否则,可能会出现这样的情况:有的用户只是想下载某个压缩包而调用了该函数,他不得不等着解压缩和选取文件这两个他并不需要的步骤完成。

不仅是函数,还有类和模块,都应功能单一,只做一件事而且要将事情做好[2]。

3函数功能要清晰

先看如下转换大小写的函数,该函数的作用不难理解:

deftransform_text(text,uppercase):

ifuppercase:

returntext.upper()

else:

returntext.lower()

显然,当调用transform_text(text,True)时,字符串text中的小写字母都转换为大写;当调用transform_text(text,False)时,字符串text中的大写字母都转换为小写。如果不看函数transform_text的定义,仅仅看transform_text(text,True/False),很难知道它在做什么。

将上面的函数分为如下两个函数:

defturn_to_uppercase(text):

returntext.upper()

defturn_to_lowercase(text):

returntext.lower()

当调用turn_to_uppercase(text)时,字符串text中的小写字母都转换为大写;当调用turn_to_lowercase(text)时,字符串text中的大写字母都转换为小写。这时,仅仅看turn_to_uppercase(text)或者turn_to_lowercase(text),就知道在做什么。函数的功能要做到清晰明确[3]。

在调用函数遇到关键字参数的时候,带上关键字名字可以增强可读性,而且不必担心参数的次序写错。例如,SendMail(from=a@x.com,to=b@y.com)比SendMail(a@x.com,b@y.com)的可读性强很多,用意一目了然,就像在读简单明了的英文句子。

4使用更Pythonic的语法

Python代码要有“Python的味道”,同等条件下,代码要写得Pythonic一些。例如,遍历一个列表,可用如下for循环:

forkinrange(len(a_list)):

print('index:',k,'value:',a_list[k])

上面的代码是正确的,但这是传统编程语言的方法。作为Python程序员,应使用针对可迭代对象的内置函数enumerate:

forindex,iteminenumerate(a_list):

print('index:',index,'value:',item)

交换两个变量a和b的值,传统语言的代码如下:

tmp=b

b=a

a=tmp

Python代码这样写也是没有问题的。实际上,Python语言有如下简便的写法,用以交换a和b的值:

a,b=b,a

判断字符串的前缀与后缀,可以使用内置的字符串方法startswith和endswith,也可以使用切片。例如,如下if语句作用是相同的:

sentence="Helloeveryone,goodmorning"

ifsentence.startswith("Hello"):……

ifsentence[:5]=="Hello":……

ifsentence.endswith("morning"):……

ifsentence[-7:]=="morning":……

很明显,startswith和endswith的可读性更好,使用切片的话,可读性下降,而且容易将字母的个数写错。

对一个列表的每个元素进行相同的操作,使用map和列表推导都可以。例如:

>>>nums=[1,2,3,4,5]

>>>squares=list(map(lambdax:x**2,nums))

>>>squares

[1,4,9,16,25]

>>>squares=[x**2forxinnums]

>>>squares

[1,4,9,16,25]

显然,列表推导的可读性更强、形式上简单,而且列表推导的速度比map快,运行效率高。完成同样的任务,应尽可能使用列表推导而不是map。

5充分利用Python的独有特性

装饰器本身是一个函数,装饰器的返回值是一个函数对象,装饰器可以让其他函数在不需要做任何代码变动的前提下增加额外功能[4]。例如,有N个函数,现在要给它们增加函数日志和函数性能测试的功能。如果是传统编程语言,需要对每个函数进行修改。对于Python而言,不需要进行N次类似甚至同样的修改。定义一个装饰器decorator,对函数进行封装,添加所需的功能(日志和性能测试),然后在每个函数的头部添加一行@decorator即可,N个函数的原始代码保持不变。这样保证了代码的稳定性,在减少重复劳动的同时,增加了函数的功能。

生成器支持延迟计算,在需要的时候可以生成相应的值,而不是一次性地生成整个序列。生成器特别适合于大型数据处理,使用生成器,可以减少内存使用,优化代码,提高程序的效率。

6使用Unittest和Pytest进行测试

有的程序员写了很长时间的代码,但从未对自己的代码进行过单元测试,只有在程序运行遇到错误时才开始检查当中的问题。单元测试是用来对函数、类或者模块进行正确性检验的工作[5]。如果代码都通过了单元测试,那么软件的质量将大大提高。Unittest是Python自带的测试框架,例如,如下代码使用断言来测试函数square()是否正确:

importunittest

defsquare(a):

returna*a

classST(unittest.TestCase):

deftest_square(self):

self.assertEqual(square(7),49)

if__name__=="__main__":

unittest.main()

Pytest是一个第三方的测试框架,兼容Unittest,比Unittest框架使用起来更简洁,效率更高,使用命令pipinstallpytest安装它。Pytest编写测试用例很容易,用例可以是类的形式,也可以是函数的形式。例如,如下代码用来测试函数double()的正确性:

defdouble(x):

return3*x

deftest_dbl():

assertdouble(8)==16

运行pytest,结果为:

deftest_dbl():

>assertdouble(8)==16

Eassert24==16

E+where24=double(8)

test.py:4:AssertionError

根据输出,很容易看到问题所在,将函数double()中的3*x改为2*x就解决了。

7使用Pylint等工具检查代码

在项目编码完成或者阶段性完成后,应该使用质量保证工具对代码进行扫描,就像体检一样。Pylint是一个针对Python代码中的语法错误、潜在问题和代码风格的静态检查工具,使用pipinstallpylint命令安装它。下面的代码一共三行,看上去没有任何问题:

defadd_one(x):

returnx+1;

print(add_one(15))

运行命令python3mpylint<文件名>,用Pylint分析这三行看似正确且毫无瑕疵的代码,得到的结果为:

2:0:W0311:Badindentation.Found3spaces,expected4(badindentation)

2:0:W0301:Unnecessarysemicolon(unnecessarysemicolon)

1:0:C0114:Missingmoduledocstring(missingmoduledocstring)

1:0:C0116:Missingfunctionormethoddocstring(missingfunctiondocstring)

短短的三行代码,扫描出了四个问题:(1)第二行的缩进是三个空格,最好是四个空格;(2)第二行结尾的分号没有必要;(3)文件没有注释说明;(4)函数也没有注释说明。修改如下,再使用Pylint扫描就没有问题了:

'''Thispythonscriptisforpylintstudy'''

defadd_one(x):

'''inputparameterisanumber,

add1tothenumber,andreturn'''

returnx+1

print(add_one(15))

8学习PEP8

除了语法,程序员也要学习编码规范方面的知识。PEP8是Python编码规范指南,PEP是PythonEnhancementProposals的简写,遵循该规范可以让开发者写出整洁的代码,提高代码的可读性,有助于同一个项目组内大家的编码风格保持一致[6]。使用工具Pycodestyle可以检查代码是否遵从PEP8,运行命令pipinstallpycodestyle安装它。如下是非常简短的四行代码:

a=1

b=2

print(a==b)

if(b>a):print("bisbigger")

运行命令pycodestyle<文件名>,检测这四行代码,结果如下:

1:2:E225missingwhitespacearoundoperator

3:6:E211whitespacebefore'('

4:3:E275missingwhitespaceafterkeyword

4:10:E231missingwhitespaceafter':'

4:10:E701multiplestatementsononeline(colon)

使用Pycodestyle发现的问题有:a=1的等号两边应该有空格;print和(之间的空格是不需要的;关键字if的后面应该有空格;最后一行最好分为两行。将代码修改如下,再使用Pycodestyle扫描就没有问题了:

a=1

b=2

print(a==b)

ifb>a:

print("bisbigger")

工具Flake8的功能比Pycodestyle更加强大,安装命令为pipinstallflake8。Flake8将Pyflakes(类似于Pylint)、Pycodestyle和McCabe(代码复杂性检查器)整合到一起,使用它可以一次性检查出多种问题。Flake8非常易于与其他工具结合,比如,在集成开发环境PyCharm中配置Flake8非常简单。

结语

Python诞生于1991年,近些年,Python语言已经渗透到各个领域,使用Python的人越来越多。Python用户不能仅仅学语法,还应该了解如何让代码变得更整洁。软件的维护期一般长于(甚至远远长于)开发期,不整洁的代码很难提高工作效率。作为Python开发人员,要想写出整洁的代码,除了语法规则之外,需要了解的知识点比较多,很难用一篇短文完全阐述清楚。希望本文能给新Python程序员一点启发,为代码质量的提升提供些许帮助。

参考文献:

[1]HarshitTyagi.PythonicCode:BestPracticestoMakeYourPythonMoreReadable[EB/OL].(20220530).https://www.codementor.io/blog/pythoniccode6yxqdoktzt.

[2]AlexOmeyer.10MustKnowPatternsforWritingCleanCodeWithPython[EB/OL].(20220406).https://dzone.com/articles/10mustknowpatternsforwritingcleancodewith1.

[3]KhuyenTran.PythonCleanCode:6BestPracticestoMakeYourPythonFunctionsMoreReadable[EB/OL].(20210121).https://towardsdatascience.com/pythoncleancode6bestpracticestomakeyourpythonfunctionsmorereadable7ea4c6171d60.

[4]苏尼尔·卡皮尔.Python代码整洁之道编写优雅的代码[M].连少华,译.北京:机械工业出版社,2020.

[5]马里西诺·阿纳亚.编写整洁的Python代码[M].包永帅,译.北京:人民邮电出版社,2021.

[6]ThePEPEditors.IndexofPythonEnhancementProposals[EB/OL].(20000713).https://peps.python.org/.

作者简介:石也牧(2004—),女,汉族,北京人,本科,研究方向:人工智能。