当前位置: 首页 > 后端技术 > Python

编写高质量的Python程序(三)基础语法

时间:2023-03-26 00:13:21 Python

本系列文章是对《编写高质量代码——改善Python程序的91个建议》精华的总结。文章首发于公众号【Python与算法之路】Python导入外部模块的三种方式:import语句、from...import...和__import__函数。前两个更常见。使用import时要注意:先使用importA或importA,适度使用fromAimportB,尽量避免使用fromAimport*Forfromaimport...,若无节制地使用,会带来Problem:Conflictingcircularnestedimportsofnamespaces(两个文件互相导入变量或函数或类)i+=1isnotequalto++iPythoninterpreter会将++i解释为+(+i),其中+代表一个正号。--i也是如此。因此,理解++i在Python的句法层面是合法的,但不是通常意义上的自增操作。使用with自动关闭资源后,应在文件操作完成后立即关闭,因为打开的文件不仅会占用系统资源,还可能影响其他程序或进程的运行,甚至导致用户期望与实际运行结果不一致。Python提供了with语句,语法为:withexpression[astarget]:codeblockwith语句支持嵌套,支持多个with子句,两者可以相互转换。withexpr1ase1,expr2ase2等价于下面的嵌套形式:withexpr1ase1:withexpr2ase2:使用else子句来简化循环(异常处理)在循环中,else子句隐式支持是否为循环由break语句终止。例子:#下面两段代码是等价的#found一个标志量用来判断循环结束是否是break语句引起的。defprint_prime(n):foriinrange(2,n):found=Trueforjinrange(2,i):ifi%j==0:found=Falsebreakiffound:print("{}is素数".format(i))defprint_prime2(n):foriinrange(2,n):forjinrange(2,i):ifi%j==0:breakelse:print("{}是质数".format(i))else子句在循环“自然”终止(循环条件为假)时执行一次,而在循环被break语句打断时不执行。与for语句类似,while语句中else子句的语义相同:循环正常结束且循环条件不为真时执行else块。遵循异常处理的一些基本原则Python中常用的异常处理语法是try、except、else、finally,它们可以有多种组合。语法如下:#先运行这个主动作try:#try中出现name1的异常时,处理except:#try中出现name2或name3的异常时except(name2,name3):#try中发生name4异常时的处理,得到除外的对应实例作为:#处理其他exceptexcept:#执行时无异常else:#不管是否有异常,执行finally:异常处理通常需要遵循以下基本原则:不建议在try中放入过多的代码。try中放太多代码带来的问题是,如果程序中抛出异常,很难定位,给调试修复带来不便,所以尽量只放在该语句块的前面可能会抛出异常。尝试声明。注意使用单一的except语句来处理所有的异常,最好定位到具体的异常。也不建议使用exceptException或exceptStandardError来捕获异常。如果一定要用,最好用raise语句把异常传递给上层。注意异常捕获的顺序,在合适的层级处理异常。用户还可以继承内置异常来构造自己的异常类,从而进一步扩展内置类的继承结构。在这种情况下,捕获异常的顺序非常重要。为了更准确的定位错误原因,推荐的方法是在继承结构中前面的except语句中抛出子类异常,后面的except语句中抛出父类异常。这样做的原因是,当try块中出现异常时,解释器会按照except语句的顺序进行匹配,匹配到第一条就立即处理异常。异常捕获的顺序非常重要。同时,应在适当的位置处理异常。一个原则是,如果异常可以在捕获的地方处理,就应该及时处理。如果无法处理,则应以适当的方式将其扔到上层。传递到上层时,需要警惕异常丢失。可以使用不带参数的raise来传递。使用更友好的异常信息,遵循异常参数的规范。通常有两种类型的异常读者:使用软件的人和开发软件的人。避免finally语句中可能出现的陷阱无论try语句中是否抛出异常,finally语句总是会被执行。由于这个特性,finally语句经常被用来做一些清理工作。但是在使用finally的时候,有一些陷阱需要特别小心。当try块中出现异常时,如果在except语句中找不到对应的异常处理,则异常会被暂时保存。当finally执行完成后,临时保存的异常会再次抛出,但是如果finally语句中产生了新的异常或者执行了return、break语句,临时保存的异常就会丢失,造成异常屏蔽。在实际的应用开发过程中,不推荐使用finally中的return语句来返回。这种处理方式不仅会导致误解,还可能造成非常严重的错误。深入理解None,正确判断对象是否为空以下数据在Python中会被视为空:constantNoneconstantFalse任意形式的数字类型零,如0,0L,0.0,0j空序列,如'',(),[]一个空字典,例如{}当在用户定义的类中定义了__nonzero__()和__len__()方法时,该方法返回整数0或False。iflist1#valueisnotemptyDosomethingelse:#valueisemptyDosomeotherthing在执行过程中,会调用内部方法__nonzero__()判断变量list1是否为空,并返回结果。注意:__nonzero__()方法-此内部方法用于测试自身的空值,返回0/1或True/False。如果一个对象没有定义这个方法,Python会通过调用__len__()方法的结果来判断。__len__()返回值0表示为空。如果类中既没有定义__len__()方法,也没有定义__nonzero__()方法,则对该类的实例进行if判断的结果为True。格式化字符串时尝试使用.format方法而不是%。建议使用format方法而不是%运算符来格式化字符串。原因:格式化方法在使用上比%运算符更灵活。使用格式方法时,参数的顺序不必与格式的顺序完全相同。格式化方法可以方便地作为参数传递"),("Friday","cloudy")]formatter="Weatherof'{0[0]}'is'{0[1]}'".formatforiteminmap(formatter,weather):print(item)%最终将被.format方法取代。根据Python的官方文档,之所以仍然保留%运算符是为了保持向后兼容性。%方法在某些特殊情况下需要特别小心使用。这种形式的%直接格式化字符,如果字符本身是一个元组,需要在%中使用(itemname,)来避免错误,注意逗号。区别对待可变对象和不可变对象。Python中的一切都是对象。对象根据其值是否可以修改分为可变对象和不可变对象。不可变对象编号字符串元组可变对象字典列表字节数组使用可变对象作为函数默认参数时要特别注意,对可变对象的更改将直接影响原始对象。最好的方式是传入None作为默认参数,在创建对象时动态生成可变对象。对于可变对象,切片操作相当于浅拷贝。对于不可变对象,当我们对其进行相关操作时,Python实际上是维护了原来的值,重新创建了一个新的对象,所以字符串对象是不允许按索引赋值的。当有两个对象同时指向一个字符串对象时,对一个对象的操作不会影响到另一个对象。函数传递既不是按值传递也不是按引用传递对于Python中给函数传递参数的方法,既不是按值传递也不是按引用传递。正确的名称应该是callbyobject或call-by-object-reference。函数参数在传递过程中传入整个对象。对于可变对象:其修改在函数内外可见,对象在调用者和被调用者之间共享。对于不可变对象:因为它们不能真正被修改,所以修改往往是通过生成一个新的对象,然后赋值来实现的。慎用变长参数慎用变长参数*args、**kwargs,原因如下:使用过于灵活。变长参数意味着这个函数的签名不够清晰,调用方式有很多种。此外,变长参数可能会破坏程序的健壮性。如果函数的参数列表很长,可以使用*args和**kwargs来简化函数定义,但通常函数有更好的实现,应该重构。例如,可以直接传递元组和字典。变长参数适用于以下情况:为函数添加装饰器。如果参数个数不确定,可以考虑使用变长参数来实现函数的多态性,或者在继承的情况下,子类需要调用父类。深入理解str()和repr()在某些方法上的区别。函数str()和repr()都可以将Python中的对象转换为字符串。两者的用法和输出非常相似。有以下区别:两者的目标不同:str()主要是面向用户的,其目的是可读性,返回形式是字符串类型,具有很强的用户友好性和可读性,而repr()是面向开发者的,它的目的是准确,它的返回值代表了Python解释器的内部含义。它通常用作调试。在解释器中直接输入时,默认调用repr()函数,而print调用str()。函数repr()的返回值一般是对象,可以用eval()函数恢复。通常有如下等式:obj==eval(repr(obj))一般应该在类中定义__repr__()方法,而__str__()方法是可选的,当可读性比准确性更重要的时候应该考虑定义__str__()方法。如果类中没有定义__str__()方法,则默认使用__repr__()方法的结果返回对象的字符串表示形式。当用户实现__repr__()方法时,最好确保其返回值可以使用eval()方法恢复。区分静态方法和类方法的适用场景静态方法:classC(object):@staticmethoddeff(arg1,arg2,...):类方法:classC(object):@classmethoddeff(cls,arg1,arg2,...):可以通过类名的形式访问。方法名称或实例。方法名。其中,静态方法没有常规方法的特殊行为,如绑定、非绑定、隐式参数等规则,而类方法的调用使用类本身作为其隐式参数,但调用本身不需要显式提供参数。类方法在调用时并没有显式声明cls,实际上类本身是作为隐藏参数传入的。类方法可以判断是通过基类调用还是通过子类调用。通过子类调用类方法当类方法被子类调用时,它可以返回子类的属性,而不是基类的属性。当类方法被子类调用时,可以调用子类的其他类方法。静态方法既不与特定实例相关,也不与特定类相关。静态方法定义在类中的原因是可以更有效地组织代码,使相关代码的垂直距离更近,提高代码的可维护性。ArtiPub自动出版