主页»Python»改进 Python 程序的 91 个主张(一)

改进 Python 程序的 91 个主张(一)

来历:驭风者 发布时刻:2017-05-17 阅览次数:

学习笔记一:《编写高质量代码 改进 Python 程序的 91 个主张》

第 1 章 引论

主张 1:了解 Pythonic 概念

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,有个包和模块的束缚:

  • 包和模块的命名选用小写、奇数方法,并且矮小

  • 包一般仅作为命名空间,如只含空的__init__.py文件

主张 2:编写 Pythonic 代码

命名的标准:

def find_num(searchList, num):
    for listValue in searchList:
        if num == listValue:
            return True
        else:
            pass

测验去通读官方手册,把握不断发展的新特性,这将使你编写代码的履行功率更高,引荐深化学习 Flask、gevent 和 requests。

主张 3:了解 Python 与 C 言语的不同之处

提到了三点:

  • Python 运用代码缩进的方法来切割代码块,不要混用 Tab 键和空格

  • Python 中单、双引号的运用

  • 三元操作符:x if bool else y

主张 4:在代码中恰当增加注释

这一点现已受教了,现在编写代码都会合理地参加块注释、行注释和文档注释,能够运用__doc__输出。

主张 5:经过恰当增加空行使代码布局更为高雅、合理

主张 6:编写函数的 4 个准则

  1. 函数规划要尽量矮小,嵌套层次不宜过深

  2. 函数声明应该做到合理、简略、易于运用

  3. 函数参数规划应该考虑向下兼容

  4. 一个函数只做一件事,尽量确保函数句子粒度的共同性

Python 中函数规划的好习惯还包括:不要在函数中界说可变方针作为默认值,运用反常替换回来过错,确保经过单元测试等。

# 关于函数规划的向下兼容
def readfile(filename):         # 榜首版别
    pass
def readfile(filename, log):    # 第二版别
    pass
def 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 data

def 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 中应当怎样运用常量:

  • 经过命名风格提示运用者该变量代表常量,如常量名悉数大写

  • 经过自界说类完结常量功用:将寄存常量的文件命名为constant.py,并在其间界说一系列常量

class _const:
    class ConstError(TypeError): pass
    class ConstCaseError(ConstError): pass
    
    def __setattr__(self, name, value):
        if self.__dict__.has_key(name):
            raise self.ConstError, "Can't change const.%s" % name
        if not name.isupper():
            raise self.ConstCaseError, \
                    'const name "%s" is not all uppercase' % name
        self.__dict__[name] = value

import sys
sys.modules[__name__] = _const()
import const
const.MY_CONSTANT = 1
const.MY_SECOND_CONSTANT = 2
const.MY_THIRD_CONSTANT = 'a'
const.MY_FORTH_CONSTANT = 'b'

其他模块中引证这些常量时,依照如下方法进行即可:

from constant import const
print(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,在内存中履行的次序如下:

  1. 先核算右边的表达式y, x,因而先在内存中创立元组(y, x),其标识符和值别离为y, x及其对应的值,其间y和x是在初始化现已存在于内存中的方针

  2. 核算表达式左面的值并进行赋值,元组被顺次分配给左面的标识符,经过解压缩,元组榜首标识符y分配给左面榜首个元素x,元组第二标识符x分配给左面榜首个元素y,然后到达交流的意图

下面是经过字节码的剖析:

>>> 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.Spring
0
# 但经过以上方法完结枚举都有不合理的当地
>>> 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 add
TypeError: 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()
>>> n
Integer 0
>>> print(n)
0
>>> m = UserInt(2)
>>> print(m)
2
>>> type(n) is int
False                   # 显着不合理
>>> isinstance(n, int)
True

咱们能够运用isinstance来检查:isinstance(object, classinfo)

主张 13:尽量转化为浮点类型后再做除法

# 核算均匀成果绩点
>>> gpa = ((4*96+3*85+5*98+2*70)*4) / ((4+3+5+2)*100)
>>> gpa
3.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  Videos
0
>>> 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:  a
index:  1 element:  b
index:  2 element:  c
index:  3 element:  d
index:  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 == s2
True
>>> s1 is s2
False
>>> s1.__eq__(s2)
True
>>> a = 'Hi'
>>> b = 'Hi'
>>> a == b
True
>>> a is b
True

咦~怎样上例中的a, b又是“同一方针”了?这跟 Python 的 string interning 机制有关,为了进步体系功能,关于较小的字符串会保存其值的一个副本,当创立新的字符串时直接指向该副本,所以a和b的 id 值是相同的,相同关于小整数[-5, 257)也是如此:

>>> id(a)
140709793837832
>>> id(b)
140709793837832
>>> x = -5
>>> y = -5
>>> x is y
True
>>> 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需求留意以下几点:

  • 优先运用import a的方法

  • 有控制地运用from a import A

  • 尽量防止运用from a import *

为什么呢?咱们来看看 Python 的 import 机制,Python 在初始化运转环境的时分会预先加载一批内建模块到内存中,一起将相关信息寄存在sys.modules中,咱们能够经过sys.modules.items()检查预加载的模块信息,当加载一个模块时,解说器实践上完结了如下动作:

  1. 在sys.modules中查找该模块是否存在,假如存在就导入到当时部分命名空间,假如不存在就为其创立一个字典方针,刺进到sys.modules中

  2. 加载前承认是否需求对模块对应的文件进行编译,假如需求则先进行编译

  3. 履行动态加载,在当时命名空间中履行编译后的字节码,并将其间一切的方针放入模块对应的字典中

>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__']
>>> import test
testing module import
>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'test']
>>> import sys
>>> 'test' in sys.modules.keys()
True
>>> id(test)
140367239464744
>>> id(sys.modules['test'])
140367239464744
>>> dir(test)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'a', 'b']
>>> sys.modules['test'].__dict__.keys()
dict_keys(['__file__', '__builtins__', '__doc__', '__loader__', '__package__', '__spec__', '__name__', 'b', 'a', '__cached__'])

从上能够看出,关于用户自界说的模块,import 机制会创立一个新的 module 将其参加当时的部分命名空间中,一起在 sys.modules 也参加该模块的信息,但本质上是在引证同一个方针,经过test.py地点的目录会多一个字节码文件。

主张 20:优先运用 absolute import 来导入模块

主张 21: i+=1 不等于 ++i

首要++i或--i在 Python 语法上是合法,但并不是咱们一般了解的自增或自减操作:

>>> ++1     # +(+1)
1
>>> --1     # -(-1)
1
>>> +++2
2
>>> ---2
-2

本来+或-只表明正负数符号。

主张 22:运用 with 主动封闭资源

关于翻开的资源咱们记住封闭它,如文件、数据库衔接等,Python 供给了一种简略高雅的解决方案:with。

先来看with完结的原理吧。

with的完结得益于一个称为上下文办理器(context manager)的东西,它界说程序运转时需求树立的上下文,处理程序的进入和退出,完结了上下文办理协议,即方针中界说了__enter__()和__exit__(),任何完结了上下文协议的方针都能够称为一个上下文办理器:

  • __enter__():回来运转时上下文相关的方针

  • __exit__(exception_type, exception_value, traceback):退出运转时的上下文,处理反常、整理现场等

包括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>
  1. 核算表达式的值,回来一个上下文办理器方针

  2. 加载上下文办理器方针的__exit__()以备后用

  3. 调用上下文办理器方针的__enter__()

  4. 将__enter__()的回来值赋给方针方针

  5. 履行代码块,正常完毕调用__exit__(),其回来值直接疏忽,假如发作反常,会调用__exit__()并将反常类型、值及 traceback 作为参数传递给__exit__(),__exit__()回来值为 false 反常将会从头抛出,回来值为 true 反常将被挂起,程序持续履行

于此,咱们能够自界说一个上下文办理器:

>>> 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 number
3 is a prime number
5 is a prime number

能够看出,else 子句在循环正常完毕和循环条件不成立时被履行,由 break 句子中止时不履行,相同,咱们能够运用这颗语法糖效果在 while 和 try...except 中。

QQ群:凯发娱乐官网官方群(515171538),验证音讯:10000
微信群:加小编微信 849023636 邀请您参加,验证音讯:10000
提示:更多精彩内容重视微信大众号:全栈开发者中心(fsder-com)
188bet uedbet 威廉希尔 明升 bwin 明升88 bodog bwin 明升m88.com 18luck 188bet unibet unibet Ladbrokes Ladbrokes casino m88明升 明升 明升 m88.com 188bet m88 明陞 uedbet赫塔菲官网 365bet官网 m88 help
网友谈论(共0条谈论) 正在载入谈论......
沉着谈论文明上网,回绝歹意咒骂 宣布谈论 / 共0条谈论
登录会员中心