Linux编程
点击右侧关注,免费入门到精通!
作者丨驭风者
https://zhuanlan.zhihu.com/p/26155739
第一章 引论
建议 1:理解 Pythonic 概念
Pythonic
Tim Peters 的 《The Zen of Python》相信学过 Python 的都耳熟能详,在交互式环境中输入import this可以查看,其实有意思的是这段 Python 之禅的源码:
哈哈哈,相信这是大佬在跟我们举反例吧。
书中还举了一个快排的例子:
代码风格
通过对语法、库和应用程序的理解来编写代码,充分体现 Python 自身的特色:
然后表扬了 Flask 框架,提到了 generator 之类的特性尤为 Pythonic,有个包和模块的约束:
包和模块的命名采用小写、单数形式,而且短小
包通常仅作为命名空间,如只含空的__init__.py文件
建议 2:编写 Pythonic 代码
命名的规范:
尝试去通读官方手册,掌握不断发展的新特性,这将使你编写代码的执行效率更高,推荐深入学习 Flask、gevent 和 requests。
建议 3:理解 Python 与 C 语言的不同之处
提到了三点:
Python 使用代码缩进的方式来分割代码块,不要混用 Tab 键和空格
Python 中单、双引号的使用
三元操作符:x if bool else y
建议 4:在代码中适当添加注释
这一点已经受教了,现在编写代码都会合理地加入块注释、行注释和文档注释,可以使用__doc__输出。
建议 5:通过适当添加空行使代码布局更为优雅、合理
建议 6:编写函数的 4 个原则
1.函数设计要尽量短小,嵌套层次不宜过深
2.函数申明应该做到合理、简单、易于使用
3.函数参数设计应该考虑向下兼容
4.一个函数只做一件事,尽量保证函数语句粒度的一致性
Python 中函数设计的好习惯还包括:不要在函数中定义可变对象作为默认值,使用异常替换返回错误,保证通过单元测试等。
最后还有个函数可读性良好的例子:
建议 7:将常量集中到一个文件
在 Python 中应当如何使用常量:
通过命名风格提醒使用者该变量代表常量,如常量名全部大写
通过自定义类实现常量功能:将存放常量的文件命名为constant.py,并在其中定义一系列常量
其他模块中引用这些常量时,按照如下方式进行即可:
第二章 编程惯用法
建议 8:利用 assert 语句来发现问题
运行是加入-O参数可以禁用断言。
建议 9:数据交换的时候不推荐使用中间变量
对于表达式x, y = y, x,在内存中执行的顺序如下:
先计算右边的表达式y, x,因此先在内存中创建元组(y, x),其标识符和值分别为y, x及其对应的值,其中y和x是在初始化已经存在于内存中的对象
计算表达式左边的值并进行赋值,元组被依次分配给左边的标识符,通过解压缩,元组第一标识符y分配给左边第一个元素x,元组第二标识符x分配给左边第一个元素y,从而达到交换的目的
下面是通过字节码的分析:
建议 10:充分利用 Lazy evaluation 的特性
哈哈哈,我猜到肯定是生成器实现菲波拉契序列的例子,不过对比我写的版本,唉。。。
建议 11:理解枚举替代实现的缺陷
利用 Python 的动态特征,可以实现枚举:
建议 12:不推荐使用 type 来进行类型检查
作为动态语言,Python 解释器会在运行时自动进行类型检查并根据需要进行隐式类型转换,当变量类型不同而两者之间又不能进行隐式类型转换时便抛出TypeError异常。
所以实际应用中,我们常常需要进行类型检查,但是不推荐使用type(),因为基于内建类型扩展的用户自定义类型,type()并不能准确返回结果:
我们可以使用isinstance来检查:isinstance(object, classinfo)
建议 13:尽量转换为浮点类型后再做除法
建议 14:警惕 eval() 的安全漏洞
eval(expression[, globals[, locals]])将字符串 str 当成有效的表达式来求值并返回计算结果,globas为字典形式,locals为任何映射对象,它们分别表示全局和局部命名空间,两者都省略表达式将在调用的环境中执行,为什么需要警惕eval()呢:
如果确实需要使用eval,建议使用安全性更好的ast.literal_eval。
建议 15:使用 enumerate() 获取序列迭代的索引和值
建议 16:分清 == 与 is 的适用场景
操作符意义isobject identity==equal
is的作用是用来检查对象的标示符是否一致,也就是比较两个对象在内存中是否拥有同一块内存空间,相当于id(x) == id(y),它并不适用于判断两个字符串是否相等。==才是用来判断两个对象的值是否相等,实际是调用了内部的__eq__,所以a==b相当于a.__eq__(b),也就是说==是可以被重载的,而is不能被重载。
咦~怎么上例中的a, b又是“同一对象”了?这跟 Python 的 string interning 机制有关,为了提高系统性能,对于较小的字符串会保留其值的一个副本,当创建新的字符串时直接指向该副本,所以a和b的 id 值是一样的,同样对于小整数[-5, 257)也是如此:
建议 17:考虑兼容性,尽可能使用 Unicode
我之前也总结过编码的问题。由于最早的编码是 ASCII 码,只能表示 128 个字符,显然这对其它语言编码并不适用,Unicode就是为了不同的文字分配一套统一的编码。
建议 18:构建合理的包层次来管理 module
本质上每一个 Python 文件都是一个模块,使用模块可以增强代码的可维护性和可重用性,在较大的项目中,我们需要合理地组织项目层次来管理模块,这就是包(Package)的作用。
一句话说包:一个包含__init__.py 文件的目录。包中的模块可以通过.进行访问,即包名.模块名。那么这个__init__.py文件有什么用呢?最明显的作用就是它区分了包和普通目录,在该文件中申明模块级别的 import 语句从而变成了包级别可见,另外在该文件中定义__all__变量,可以控制需要导入的子包或模块。
这里给出一个较为合理的包组织方式,是FlaskWeb 开发:基于Python的Web应用开发实战一书中推荐而来的:
第三章基础语法
建议 19:有节制地使用 from...import 语句
Python 提供三种方式来引入外部模块:import语句、from...import语句以及__import__函数,其中__import__函数显式地将模块的名称作为字符串传递并赋值给命名空间的变量。
使用import需要注意以下几点:
优先使用import a的形式
有节制地使用from a import A
尽量避免使用from a import *
在sys.modules中搜索该模块是否存在,如果存在就导入到当前局部命名空间,如果不存在就为其创建一个字典对象,插入到sys.modules中
加载前确认是否需要对模块对应的文件进行编译,如果需要则先进行编译
执行动态加载,在当前命名空间中执行编译后的字节码,并将其中所有的对象放入模块对应的字典中
从上可以看出,对于用户自定义的模块,import 机制会创建一个新的 module 将其加入当前的局部命名空间中,同时在 sys.modules 也加入该模块的信息,但本质上是在引用同一个对象,通过test.py所在的目录会多一个字节码文件。
建议 20:优先使用 absolute import 来导入模块
建议 21: i+=1 不等于 ++i
首先++i或--i在 Python 语法上是合法,但并不是我们通常理解的自增或自减操作:
原来+或-只表示正负数符号。
建议 22:使用 with 自动关闭资源
对于打开的资源我们记得关闭它,如文件、数据库连接等,Python 提供了一种简单优雅的解决方案:with。
先来看with实现的原理吧。
with的实现得益于一个称为上下文管理器(context manager)的东西,它定义程序运行时需要建立的上下文,处理程序的进入和退出,实现了上下文管理协议,即对象中定义了__enter__()和__exit__(),任何实现了上下文协议的对象都可以称为一个上下文管理器:
__enter__():返回运行时上下文相关的对象
__exit__(exception_type, exception_value, traceback):退出运行时的上下文,处理异常、清理现场等
包含with语句的代码块执行过程如下:
1.计算表达式的值,返回一个上下文管理器对象
2.加载上下文管理器对象的__exit__()以备后用
3.调用上下文管理器对象的__enter__()
4.将__enter__()的返回值赋给目标对象
5.执行代码块,正常结束调用__exit__(),其返回值直接忽略,如果发生异常,会调用__exit__()并将异常类型、值及 traceback 作为参数传递给__exit__(),__exit__()返回值为 false 异常将会重新抛出,返回值为 true 异常将被挂起,程序继续执行
于此,我们可以自定义一个上下文管理器:
Python 还提供contextlib模块,通过 Generator 实现,其中的 contextmanager 作为装饰器来提供一种针对函数级别上的上下文管理器,可以直接作用于函数/对象而不必关心__enter__()和__exit__()的实现。
领取专属 10元无门槛券
私享最新 技术干货