Pythonic
Tim Peters 的 《The Zen of Python》相信学过 Python 的都耳熟能详,在交互式环境中输入import this可以查看,其实有意思的是这段 Python 之禅的源码:
d = {}for c in (65, 97): for i in range(26): d[chr(i+c)] = chr((i+13) % 26 + c)print "".join([d.get(c, c) for c in s])哈哈哈,相信这是大佬在跟我们举反例吧。
书中还举了一个快排的例子:
def quicksort(array): less = [] greater = [] if len(array) <= 1: return array pivot =array.pop() for x in array: if x <= pivot: less.append(x) else: greater.append(x) return quicksort(less) + [pivot] + quicksort(greater)代码风格
通过对语法、库和应用程序的理解来编写代码,充分体现 Python 自身的特色:
# 变量交换a, b = b, a# 上下文管理with open(path, 'r') as f: do_sth_with(f)# 不应当过分地追求奇技淫巧a = [1, 2, 3, 4]a[::-1] # 不推荐。好吧,自从学了切片我一直用的这个list(reversed(a)) # 推荐然后表扬了 Flask 框架,提到了 generator 之类的特性尤为 Pythonic,有个包和模块的约束:
命名的规范:
def find_num(searchList, num): for listValue in searchList: if num == listValue: return True else: pass尝试去通读官方手册,掌握不断发展的新特性,这将使你编写代码的执行效率更高,推荐深入学习 Flask、gevent 和 requests。
建议 3:理解 Python 与 C 语言的不同之处提到了三点:
这一点已经受教了,现在编写代码都会合理地加入块注释、行注释和文档注释,可以使用__doc__输出。
建议 5:通过适当添加空行使代码布局更为优雅、合理建议 6:编写函数的 4 个原则Python 中函数设计的好习惯还包括:不要在函数中定义可变对象作为默认值,使用异常替换返回错误,保证通过单元测试等。
# 关于函数设计的向下兼容def readfile(filename): # 第一版本 passdef readfile(filename, log): # 第二版本 passdef readfile(filename, logger=logger.info): # 合理的设计 pass最后还有个函数可读性良好的例子:
def GetContent(ServerAdr, PagePath): http = httplib.HTTP(ServerAdr) http.putrequest('GET', PagePath) http.putheader('Accept', 'text/html') http.putheader('Accept', 'text/plain') http.endheaders() httpcode, httpmsg, headers = http.getreply() if httpcode != 200: raise "Could not get document: Check URL and Path." doc = http.getfile() data = doc.read() # 此处是不是应该使用 with ? doc.close return datadef ExtractData(inputstring, start_line, end_line): lstr = inputstring.splitlines() # split j = 0 for i in lstr: j += 1 if i.strip() == start_line: slice_start = j elif i.strip() == end_line: slice_end = j return lstr[slice_start:slice_end]def SendEmail(sender, receiver, smtpserver, username, password, content): subject = "Contented get from the web" msg = MIMEText(content, 'plain', 'utf-8') msg['Subject'] = Header(subject, 'utf-8') smtp = smtplib.SMTP() smtp.connect(smtpserver) smtp.login(username, password) smtp.sendmail(sender, receiver, msg.as_string()) smtp.quit()建议 7:将常量集中到一个文件在 Python 中应当如何使用常量:
其他模块中引用这些常量时,按照如下方式进行即可:
from constant import constprint(const.MY_CONSTANT)第 2 章 编程惯用法建议 8:利用 assert 语句来发现问题>>> y = 2>>> assert x == y, "not equals"Traceback (most recent call last): File "<stdin>", line 1, in <module>AssertionError: not equals>>> x = 1>>> y = 2# 以上代码相当于>>> if __debug__ and not x == y:... raise AssertionError("not equals")... Traceback (most recent call last): File "<stdin>", line 2, in <module>AssertionError: not equals运行是加入-O参数可以禁用断言。
建议 9:数据交换的时候不推荐使用中间变量>>> Timer('temp = x; x = y; y = temp;', 'x = 2; y = 3').timeit()0.059251302998745814>>> Timer('x, y = y, x', 'x = 2; y = 3').timeit()0.05007316499904846对于表达式x, y = y, x,在内存中执行的顺序如下:
下面是通过字节码的分析:
>>> import dis>>> def swap1():... x = 2... y = 3... x, y = y, x... >>> def swap2():... x = 2... y = 3... temp = x... x = y... y = temp... >>> dis.dis(swap1) 2 0 LOAD_CONST 1 (2) 3 STORE_FAST 0 (x) 3 6 LOAD_CONST 2 (3) 9 STORE_FAST 1 (y) 4 12 LOAD_FAST 1 (y) 15 LOAD_FAST 0 (x) 18 ROT_TWO # 交换两个栈的最顶层元素 19 STORE_FAST 0 (x) 22 STORE_FAST 1 (y) 25 LOAD_CONST 0 (None) 28 RETURN_VALUE>>> dis.dis(swap2) 2 0 LOAD_CONST 1 (2) 3 STORE_FAST 0 (x) 3 6 LOAD_CONST 2 (3) 9 STORE_FAST 1 (y) 4 12 LOAD_FAST 0 (x) 15 STORE_FAST 2 (temp) 5 18 LOAD_FAST 1 (y) 21 STORE_FAST 0 (x) 6 24 LOAD_FAST 2 (temp) 27 STORE_FAST 1 (y) 30 LOAD_CONST 0 (None) 33 RETURN_VALUE建议 10:充分利用 Lazy evaluation 的特性def fib(): a, b = 0, 1 while True: yield a a, b = b, a + b哈哈哈,我猜到肯定是生成器实现菲波拉契序列的例子,不过对比我写的版本,唉。。。
建议 11:理解枚举替代实现的缺陷利用 Python 的动态特征,可以实现枚举:
# 方式一class Seasons: Spring, Summer, Autumn, Winter = range(4)# 方式二def enum(*posarg, **keysarg): return type("Enum", (object,), dict(zip(posarg, range(len(posarg))), **keysarg))Seasons = enum("Spring", "Summer", "Autumn", Winter=1)Seasons.Spring# 方式三>>> from collections import namedtuple>>> Seasons = namedtuple('Seasons', 'Spring Summer Autumn Winter')._make(range(4))>>> Seasons.Spring0# 但通过以上方式实现枚举都有不合理的地方>>> Seasons._replace(Spring=2) │Seasons(Spring=2, Summer=1, Autumn=2, Winter=3) # Python3.4 中加入了枚举,仅在父类没有任何枚举成员的时候才允许继承建议 12:不推荐使用 type 来进行类型检查作为动态语言,Python 解释器会在运行时自动进行类型检查并根据需要进行隐式类型转换,当变量类型不同而两者之间又不能进行隐式类型转换时便抛出TypeError异常。
>>> def add(a, b):... return a + b... >>> add(1, 2j)(1+2j)>>> add('a', 'b')'ab'>>> add(1, 2)3>>> add(1.0, 2.3)3.3>>> add([1, 2], [3, 4])[1, 2, 3, 4]>>> add(1, 'a')Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in addTypeError: unsupported operand type(s) for +: 'int' and 'str'所以实际应用中,我们常常需要进行类型检查,但是不推荐使用type(),因为基于内建类型扩展的用户自定义类型,type()并不能准确返回结果:
class UserInt(int): def __init__(self, val=0): self._val = int(val) def __add__(self, val): if isinstance(val, UserInt): return UserInt(self._val + val._val) return self._val + val def __iadd__(self, val): raise NotImplementedError("not support operation") def __str__(self): return str(self._val) def __repr__(self): return "Integer %s" % self._val>>> n = UserInt()>>> nInteger 0>>> print(n)0>>> m = UserInt(2)>>> print(m)2>>> type(n) is intFalse # 显然不合理>>> isinstance(n, int)True我们可以使用isinstance来检查:isinstance(object, classinfo)
建议 13:尽量转换为浮点类型后再做除法# 计算平均成绩绩点>>> gpa = ((4*96+3*85+5*98+2*70)*4) / ((4+3+5+2)*100)>>> gpa3.625714285714286 # 终于知道自己的绩点是咋算的了建议 14:警惕 eval() 的安全漏洞eval(expression[, globals[, locals]])将字符串 str 当成有效的表达式来求值并返回计算结果,globas为字典形式,locals为任何映射对象,它们分别表示全局和局部命名空间,两者都省略表达式将在调用的环境中执行,为什么需要警惕eval()呢:
# 合理正确地使用>>> eval("1+1==2")True>>> eval('"a"+"b"')'ab'# 坏心眼的geek>>> eval('__import__("os").system("dir")')Desktop Documents Downloads examples.desktop Music Pictures Public __pycache__ Templates Videos0>>> eval('__import__("os").system("del * /Q")') # 嘿嘿嘿如果确实需要使用eval,建议使用安全性更好的ast.literal_eval。
建议 15:使用 enumerate() 获取序列迭代的索引和值>>> li = ['a', 'b', 'c', 'd', 'e']>>> for i, e in enumerate(li):... print('index: ', i, 'element: ', e)... index: 0 element: aindex: 1 element: bindex: 2 element: cindex: 3 element: dindex: 4 element: e# enumerate(squence, start=0) 内部实现def enumerate(squence, start=0): n = start for elem in sequence: yield n, elem # 666 n += 1# 明白了原理我们自己也来实现一个反序的def reversed_enumerate(squence): n = -1 for elem in reversed(sequence): yield len(sequence) + n, elem n -= 1建议 16:分清 == 与 is 的适用场景操作符意义isobject identity==equal
is的作用是用来检查对象的标示符是否一致,也就是比较两个对象在内存中是否拥有同一块内存空间,相当于id(x) == id(y),它并不适用于判断两个字符串是否相等。==才是用来判断两个对象的值是否相等,实际是调用了内部的__eq__,所以a==b相当于a.__eq__(b),也就是说==是可以被重载的,而is不能被重载。
>>> s1 = 'hello world'>>> s2 = 'hello world'>>> s1 == s2True>>> s1 is s2False>>> s1.__eq__(s2)True>>> a = 'Hi'>>> b = 'Hi'>>> a == bTrue>>> a is bTrue咦~怎么上例中的a, b又是“同一对象”了?这跟 Python 的 string interning 机制有关,为了提高系统性能,对于较小的字符串会保留其值的一个副本,当创建新的字符串时直接指向该副本,所以a和b的 id 值是一样的,同样对于小整数[-5, 257)也是如此:
>>> id(a)140709793837832>>> id(b)140709793837832>>> x = -5>>> y = -5>>> x is yTrue>>> id(x) == id(y)True建议 17:考虑兼容性,尽可能使用 Unicode我之前也总结过编码的问题。由于最早的编码是 ASCII 码,只能表示 128 个字符,显然这对其它语言编码并不适用,Unicode就是为了不同的文字分配一套统一的编码。
建议 18:构建合理的包层次来管理 module本质上每一个 Python 文件都是一个模块,使用模块可以增强代码的可维护性和可重用性,在较大的项目中,我们需要合理地组织项目层次来管理模块,这就是包(Package)的作用。
一句话说包:一个包含__init__.py 文件的目录。包中的模块可以通过.进行访问,即包名.模块名。那么这个__init__.py文件有什么用呢?最明显的作用就是它区分了包和普通目录,在该文件中申明模块级别的 import 语句从而变成了包级别可见,另外在该文件中定义__all__变量,可以控制需要导入的子包或模块。
这里给出一个较为合理的包组织方式,是FlaskWeb 开发:基于Python的Web应用开发实战一书中推荐而来的:
|-flasky |-app/ # Flask 程序 |-templates/ # 存放模板 |-static/ # 静态文件资源 |-main/ |-__init__.py |-errors.py # 蓝本中的错误处理程序 |-forms.py # 表单对象 |-views.py # 蓝本中定义的程序路由 |-__init__.py |-email.py # 电子邮件支持 |-models.py # 数据库模型 |-migrations/ # 数据库迁移脚本 |-tests/ # 单元测试 |-__init__.py |-test*.py |-venv/ # 虚拟环境 |-requirements/ |-dev.txt # 开发过程中的依赖包 |-prod.txt # 生产过程中的依赖包 |-config.py # 储存程序配置 |-manage.py # 启动程序以及其他的程序任务第 3 章:基础语法建议 19:有节制地使用 from...import 语句Python 提供三种方式来引入外部模块:import语句、from...import语句以及__import__函数,其中__import__函数显式地将模块的名称作为字符串传递并赋值给命名空间的变量。
使用import需要注意以下几点:
为什么呢?我们来看看 Python 的 import 机制,Python 在初始化运行环境的时候会预先加载一批内建模块到内存中,同时将相关信息存放在sys.modules中,我们可以通过sys.modules.items()查看预加载的模块信息,当加载一个模块时,解释器实际上完成了如下动作:
从上可以看出,对于用户自定义的模块,import 机制会创建一个新的 module 将其加入当前的局部命名空间中,同时在 sys.modules 也加入该模块的信息,但本质上是在引用同一个对象,通过test.py所在的目录会多一个字节码文件。
建议 20:优先使用 absolute import 来导入模块建议 21: i+=1 不等于 ++i首先++i或--i在 Python 语法上是合法,但并不是我们通常理解的自增或自减操作:
>>> ++1 # +(+1)1>>> --1 # -(-1)1>>> +++22>>> ---2-2原来+或-只表示正负数符号。
建议 22:使用 with 自动关闭资源对于打开的资源我们记得关闭它,如文件、数据库连接等,Python 提供了一种简单优雅的解决方案:with。
先来看with实现的原理吧。
with的实现得益于一个称为上下文管理器(context manager)的东西,它定义程序运行时需要建立的上下文,处理程序的进入和退出,实现了上下文管理协议,即对象中定义了__enter__()和__exit__(),任何实现了上下文协议的对象都可以称为一个上下文管理器:
包含with语句的代码块执行过程如下:
with 表达式 [as 目标]: 代码块# 例>>> with open('test.txt', 'w') as f:... f.write('test')... 4>>> f.__enter__<built-in method __enter__ of _io.TextIOWrapper object at 0x7f1b967aaa68>>>> f.__exit__<built-in method __exit__ of _io.TextIOWrapper object at 0x7f1b967aaa68>于此,我们可以自定义一个上下文管理器:
>>> class MyContextManager(object):... def __enter__(self):... print('entering...')... def __exit__(self, exception_type, exception_value, traceback):... print('leaving...')... if exception_type is None:... print('no exceptions!')... return False... elif exception_type is ValueError:... print('value error!')... return True... else:... print('other error')... return True... >>> with MyContextManager():... print('Testing...')... entering...Testing...leaving...no exceptions!>>> with MyContextManager():... print('Testing...')... raise(ValueError)... entering...Testing...leaving...value error!Python 还提供contextlib模块,通过 Generator 实现,其中的 contextmanager 作为装饰器来提供一种针对函数级别上的上下文管理器,可以直接作用于函数/对象而不必关心__enter__()和__exit__()的实现。
推荐文章
建议 23:使用 else 子句简化循环(异常处理)Python 的 else 子句提供了隐含的对循环是否由 break 语句引发循环结束的判断,有点绕哈,来看例子:
>>> def print_prime(n):... for i in range(2, n):... for j in range(2, i):... if i % j == 0:... break... else:... print('{} is a prime number'.format(i))... >>> print_prime(7)2 is a prime number3 is a prime number5 is a prime number可以看出,else 子句在循环正常结束和循环条件不成立时被执行,由 break 语句中断时不执行,同样,我们可以利用这颗语法糖作用在 while 和 try...except 中。
【转载】http://www.codeceo.com/article/9 ... ython-part-one.html
欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) | 黑马程序员IT技术论坛 X3.2 |