改善 Python 程序的 91 个建议(一)

1,362次阅读
没有评论

第 1 章 引论

建议 1:理解 Pythonic 概念

Pythonic

Tim Peters 的 《The Zen of Python》相信学过 Python 的都耳熟能详,在交互式环境中输入import this可以查看,其实有意思的是这段 Python 之禅的源码:

d = {}
<span class="hljs-keyword">for</span> <span class="hljs-built_in">c</span> <span class="hljs-keyword">in</span> (<span class="hljs-number">65</span>, <span class="hljs-number">97</span>):
    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(<span class="hljs-number">26</span>):
        d[chr(i+<span class="hljs-built_in">c</span>)] = chr((i+<span class="hljs-number">13</span>) % <span class="hljs-number">26</span> + <span class="hljs-built_in">c</span>)

<span class="hljs-built_in">print</span> <span class="hljs-string">""</span>.<span class="hljs-built_in">join</span>([d.<span class="hljs-keyword">get</span>(<span class="hljs-built_in">c</span>, <span class="hljs-built_in">c</span>) <span class="hljs-keyword">for</span> <span class="hljs-built_in">c</span> <span class="hljs-keyword">in</span> s])

哈哈哈,相信这是大佬在跟我们举反例吧。

书中还举了一个快排的例子:

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">quicksort</span><span class="hljs-params">(array)</span>:</span>
    less = []
    greater = []
    <span class="hljs-keyword">if</span> len(array) <= <span class="hljs-number">1</span>:
        <span class="hljs-keyword">return</span> array
    pivot =array.pop()
    <span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> array:
        <span class="hljs-keyword">if</span> x <= pivot:
            less.append(x)
        <span class="hljs-keyword">else</span>:
            greater.append(x)
    <span class="hljs-keyword">return</span> quicksort(less) + [pivot] + quicksort(greater)

代码风格

通过对语法、库和应用程序的理解来编写代码,充分体现 Python 自身的特色:

<span class="hljs-comment"># 变量交换</span>
a, b = b, a
<span class="hljs-comment"># 上下文管理</span>
with open(path, <span class="hljs-string">'r'</span>) <span class="hljs-keyword">as</span> f:
    do_sth_with(f)
<span class="hljs-comment"># 不应当过分地追求奇技淫巧</span>
a = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>]
a[::<span class="hljs-number">-1</span>] <span class="hljs-comment"># 不推荐。好吧,自从学了切片我一直用的这个</span>
<span class="hljs-keyword">list</span>(reversed(a))   <span class="hljs-comment"># 推荐</span>

然后表扬了 Flask 框架,提到了 generator 之类的特性尤为 Pythonic,有个包和模块的约束:

  • 包和模块的命名采用小写、单数形式,而且短小
  • 包通常仅作为命名空间,如只含空的__init__.py文件

建议 2:编写 Pythonic 代码

命名的规范:

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">find_num</span><span class="hljs-params">(searchList, num)</span>:</span>
    <span class="hljs-keyword">for</span> listValue <span class="hljs-keyword">in</span> searchList:
        <span class="hljs-keyword">if</span> num == listValue:
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">True</span>
        <span class="hljs-keyword">else</span>:
            <span class="hljs-keyword">pass</span>

尝试去通读官方手册,掌握不断发展的新特性,这将使你编写代码的执行效率更高,推荐深入学习 Flask、gevent 和 requests。

建议 3:理解 Python 与 C 语言的不同之处

提到了三点:

  • Python 使用代码缩进的方式来分割代码块,不要混用 Tab 键和空格
  • Python 中单、双引号的使用
  • 三元操作符:x if bool else y

建议 4:在代码中适当添加注释

这一点已经受教了,现在编写代码都会合理地加入块注释、行注释和文档注释,可以使用__doc__输出。

建议 5:通过适当添加空行使代码布局更为优雅、合理

建议 6:编写函数的 4 个原则

  1. 函数设计要尽量短小,嵌套层次不宜过深
  2. 函数申明应该做到合理、简单、易于使用
  3. 函数参数设计应该考虑向下兼容
  4. 一个函数只做一件事,尽量保证函数语句粒度的一致性

Python 中函数设计的好习惯还包括:不要在函数中定义可变对象作为默认值,使用异常替换返回错误,保证通过单元测试等。

<span class="hljs-comment"># 关于函数设计的向下兼容</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">readfile</span><span class="hljs-params">(filename)</span>:</span>         <span class="hljs-comment"># 第一版本</span>
    <span class="hljs-keyword">pass</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">readfile</span><span class="hljs-params">(filename, log)</span>:</span>    <span class="hljs-comment"># 第二版本</span>
    <span class="hljs-keyword">pass</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">readfile</span><span class="hljs-params">(filename, logger=logger.info)</span>:</span>     <span class="hljs-comment"># 合理的设计</span>
    <span class="hljs-keyword">pass</span>

最后还有个函数可读性良好的例子:

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">GetContent</span><span class="hljs-params">(ServerAdr, PagePath)</span>:</span>
    http = httplib.HTTP(ServerAdr)
    http.putrequest(<span class="hljs-string">'GET'</span>, PagePath)
    http.putheader(<span class="hljs-string">'Accept'</span>, <span class="hljs-string">'text/html'</span>)
    http.putheader(<span class="hljs-string">'Accept'</span>, <span class="hljs-string">'text/plain'</span>)
    http.endheaders()
    httpcode, httpmsg, headers = http.getreply()
    <span class="hljs-keyword">if</span> httpcode != <span class="hljs-number">200</span>:
        <span class="hljs-keyword">raise</span> <span class="hljs-string">"Could not get document: Check URL and Path."</span>
    doc = http.getfile()
    data = doc.read()       <span class="hljs-comment"># 此处是不是应该使用 with ?</span>
    doc.close
    <span class="hljs-keyword">return</span> data

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">ExtractData</span><span class="hljs-params">(inputstring, start_line, end_line)</span>:</span>
    lstr = inputstring.splitlines()             <span class="hljs-comment"># split</span>
    j = <span class="hljs-number">0</span>
    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> lstr:
        j += <span class="hljs-number">1</span>
        <span class="hljs-keyword">if</span> i.strip() == start_line: slice_start = j
        <span class="hljs-keyword">elif</span> i.strip() == end_line: slice_end = j
    <span class="hljs-keyword">return</span> lstr[slice_start:slice_end]

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">SendEmail</span><span class="hljs-params">(sender, receiver, smtpserver, username, password, content)</span>:</span>
    subject = <span class="hljs-string">"Contented get from the web"</span>
    msg = MIMEText(content, <span class="hljs-string">'plain'</span>, <span class="hljs-string">'utf-8'</span>)
    msg[<span class="hljs-string">'Subject'</span>] = Header(subject, <span class="hljs-string">'utf-8'</span>)
    smtp = smtplib.SMTP()
    smtp.connect(smtpserver)
    smtp.login(username, password)
    smtp.sendmail(sender, receiver, msg.as_string())
    smtp.quit()

建议 7:将常量集中到一个文件

在 Python 中应当如何使用常量:

  • 通过命名风格提醒使用者该变量代表常量,如常量名全部大写
  • 通过自定义类实现常量功能:将存放常量的文件命名为constant.py,并在其中定义一系列常量
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_const</span>:</span>
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ConstError</span><span class="hljs-params">(TypeError)</span>:</span> <span class="hljs-keyword">pass</span>
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ConstCaseError</span><span class="hljs-params">(ConstError)</span>:</span> <span class="hljs-keyword">pass</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__setattr__</span><span class="hljs-params">(self, name, value)</span>:</span>
        <span class="hljs-keyword">if</span> self.__dict__.has_key(name):
            <span class="hljs-keyword">raise</span> self.ConstError, <span class="hljs-string">"Can't change const.%s"</span> % name
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> name.isupper():
            <span class="hljs-keyword">raise</span> self.ConstCaseError, \
                    <span class="hljs-string">'const name "%s" is not all uppercase'</span> % name
        self.__dict__(name) = value

<span class="hljs-keyword">import</span> sys
sys.modules[__name__] = _const()
<span class="hljs-keyword">import</span> const
const.MY_CONSTANT = <span class="hljs-number">1</span>
const.MY_SECOND_CONSTANT = <span class="hljs-number">2</span>
const.MY_THIRD_CONSTANT = <span class="hljs-string">'a'</span>
const.MY_FORTH_CONSTANT = <span class="hljs-string">'b'</span>

其他模块中引用这些常量时,按照如下方式进行即可:

<span class="hljs-keyword">from</span> constant <span class="hljs-keyword">import</span> <span class="hljs-keyword">const</span>
print(<span class="hljs-keyword">const</span>.MY_CONSTANT)

第 2 章 编程惯用法

建议 8:利用 assert 语句来发现问题

<span class="hljs-meta">>></span>> y = <span class="hljs-number">2</span>
<span class="hljs-meta">>></span>> assert x == y, <span class="hljs-string">"not equals"</span>
Traceback (most recent call last):
  File <span class="hljs-string">"<stdin>"</span>, line <span class="hljs-number">1</span>, <span class="hljs-keyword">in</span> <<span class="hljs-class"><span class="hljs-keyword">module</span>></span>
<span class="hljs-symbol">AssertionError:</span> <span class="hljs-keyword">not</span> equals
<span class="hljs-meta">>></span>> x = <span class="hljs-number">1</span>
<span class="hljs-meta">>></span>> y = <span class="hljs-number">2</span>
<span class="hljs-comment"># 以上代码相当于</span>
<span class="hljs-meta">>></span>> if __debug_<span class="hljs-number">_</span> and not x == <span class="hljs-symbol">y:</span>
...     raise AssertionError(<span class="hljs-string">"not equals"</span>)
... 
Traceback (most recent call last):
  File <span class="hljs-string">"<stdin>"</span>, line <span class="hljs-number">2</span>, <span class="hljs-keyword">in</span> <<span class="hljs-class"><span class="hljs-keyword">module</span>></span>
<span class="hljs-symbol">AssertionError:</span> <span class="hljs-keyword">not</span> equals

运行是加入-O参数可以禁用断言。

建议 9:数据交换的时候不推荐使用中间变量

<span class="hljs-meta">>></span>> Timer(<span class="hljs-string">'temp = x; x = y; y = temp;'</span>, <span class="hljs-string">'x = 2; y = 3'</span>).timeit()
<span class="hljs-number">0</span>.<span class="hljs-number">05</span>9251302998745814
<span class="hljs-meta">>></span>> Timer(<span class="hljs-string">'x, y = y, x'</span>, <span class="hljs-string">'x = 2; y = 3'</span>).timeit()
<span class="hljs-number">0</span>.<span class="hljs-number">050073164</span>99904846

对于表达式x, y = y, x,在内存中执行的顺序如下:

  1. 先计算右边的表达式y, x,因此先在内存中创建元组(y, x),其标识符和值分别为y, x及其对应的值,其中y和x是在初始化已经存在于内存中的对象
  2. 计算表达式左边的值并进行赋值,元组被依次分配给左边的标识符,通过解压缩,元组第一标识符y分配给左边第一个元素x,元组第二标识符x分配给左边第一个元素y,从而达到交换的目的

下面是通过字节码的分析:

<span class="hljs-meta">>>> </span><span class="hljs-keyword">import</span> dis
<span class="hljs-meta">>>> </span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">swap1</span><span class="hljs-params">()</span>:</span>
<span class="hljs-meta">... </span>    x = <span class="hljs-number">2</span>
<span class="hljs-meta">... </span>    y = <span class="hljs-number">3</span>
<span class="hljs-meta">... </span>    x, y = y, x
<span class="hljs-meta">... </span>
<span class="hljs-meta">>>> </span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">swap2</span><span class="hljs-params">()</span>:</span>
<span class="hljs-meta">... </span>    x = <span class="hljs-number">2</span>
<span class="hljs-meta">... </span>    y = <span class="hljs-number">3</span>
<span class="hljs-meta">... </span>    temp = x
<span class="hljs-meta">... </span>    x = y
<span class="hljs-meta">... </span>    y = temp
<span class="hljs-meta">... </span>
<span class="hljs-meta">>>> </span>dis.dis(swap1)
  <span class="hljs-number">2</span>           <span class="hljs-number">0</span> LOAD_CONST               <span class="hljs-number">1</span> (<span class="hljs-number">2</span>)
              <span class="hljs-number">3</span> STORE_FAST               <span class="hljs-number">0</span> (x)

  <span class="hljs-number">3</span>           <span class="hljs-number">6</span> LOAD_CONST               <span class="hljs-number">2</span> (<span class="hljs-number">3</span>)
              <span class="hljs-number">9</span> STORE_FAST               <span class="hljs-number">1</span> (y)

  <span class="hljs-number">4</span>          <span class="hljs-number">12</span> LOAD_FAST                <span class="hljs-number">1</span> (y)
             <span class="hljs-number">15</span> LOAD_FAST                <span class="hljs-number">0</span> (x)
             <span class="hljs-number">18</span> ROT_TWO                             <span class="hljs-comment"># 交换两个栈的最顶层元素</span>
             <span class="hljs-number">19</span> STORE_FAST               <span class="hljs-number">0</span> (x)
             <span class="hljs-number">22</span> STORE_FAST               <span class="hljs-number">1</span> (y)
             <span class="hljs-number">25</span> LOAD_CONST               <span class="hljs-number">0</span> (<span class="hljs-keyword">None</span>)
             <span class="hljs-number">28</span> RETURN_VALUE
<span class="hljs-meta">>>> </span>dis.dis(swap2)                                                                                                                                    
  <span class="hljs-number">2</span>           <span class="hljs-number">0</span> LOAD_CONST               <span class="hljs-number">1</span> (<span class="hljs-number">2</span>)
              <span class="hljs-number">3</span> STORE_FAST               <span class="hljs-number">0</span> (x)

  <span class="hljs-number">3</span>           <span class="hljs-number">6</span> LOAD_CONST               <span class="hljs-number">2</span> (<span class="hljs-number">3</span>)
              <span class="hljs-number">9</span> STORE_FAST               <span class="hljs-number">1</span> (y)

  <span class="hljs-number">4</span>          <span class="hljs-number">12</span> LOAD_FAST                <span class="hljs-number">0</span> (x)
             <span class="hljs-number">15</span> STORE_FAST               <span class="hljs-number">2</span> (temp)

  <span class="hljs-number">5</span>          <span class="hljs-number">18</span> LOAD_FAST                <span class="hljs-number">1</span> (y)
             <span class="hljs-number">21</span> STORE_FAST               <span class="hljs-number">0</span> (x)

  <span class="hljs-number">6</span>          <span class="hljs-number">24</span> LOAD_FAST                <span class="hljs-number">2</span> (temp)
             <span class="hljs-number">27</span> STORE_FAST               <span class="hljs-number">1</span> (y)
             <span class="hljs-number">30</span> LOAD_CONST               <span class="hljs-number">0</span> (<span class="hljs-keyword">None</span>)
             <span class="hljs-number">33</span> RETURN_VALUE

建议 10:充分利用 Lazy evaluation 的特性

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">fib</span><span class="hljs-params">()</span>:</span>
    a, b = <span class="hljs-number">0</span>, <span class="hljs-number">1</span>
    <span class="hljs-keyword">while</span> <span class="hljs-keyword">True</span>:
        <span class="hljs-keyword">yield</span> a
        a, b = b, a + b

哈哈哈,我猜到肯定是生成器实现菲波拉契序列的例子,不过对比我写的版本,唉。。。

建议 11:理解枚举替代实现的缺陷

利用 Python 的动态特征,可以实现枚举:

<span class="hljs-comment"># 方式一</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Seasons</span>:</span>
    Spring, Summer, Autumn, Winter = range(<span class="hljs-number">4</span>)
<span class="hljs-comment"># 方式二</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">enum</span><span class="hljs-params">(*posarg, **keysarg)</span>:</span>
    <span class="hljs-keyword">return</span> type(<span class="hljs-string">"Enum"</span>, (object,), dict(zip(posarg, range(len(posarg))), **keysarg))
Seasons = enum(<span class="hljs-string">"Spring"</span>, <span class="hljs-string">"Summer"</span>, <span class="hljs-string">"Autumn"</span>, Winter=<span class="hljs-number">1</span>)
Seasons.Spring
<span class="hljs-comment"># 方式三</span>
<span class="hljs-meta">>>> </span><span class="hljs-keyword">from</span> collections <span class="hljs-keyword">import</span> namedtuple
<span class="hljs-meta">>>> </span>Seasons = namedtuple(<span class="hljs-string">'Seasons'</span>, <span class="hljs-string">'Spring Summer Autumn Winter'</span>)._make(range(<span class="hljs-number">4</span>))
<span class="hljs-meta">>>> </span>Seasons.Spring
<span class="hljs-number">0</span>
<span class="hljs-comment"># 但通过以上方式实现枚举都有不合理的地方</span>
<span class="hljs-meta">>>> </span>Seasons._replace(Spring=<span class="hljs-number">2</span>)                                             │
Seasons(Spring=<span class="hljs-number">2</span>, Summer=<span class="hljs-number">1</span>, Autumn=<span class="hljs-number">2</span>, Winter=<span class="hljs-number">3</span>)  
<span class="hljs-comment"># Python3.4 中加入了枚举,仅在父类没有任何枚举成员的时候才允许继承</span>

建议 12:不推荐使用 type 来进行类型检查

作为动态语言,Python 解释器会在运行时自动进行类型检查并根据需要进行隐式类型转换,当变量类型不同而两者之间又不能进行隐式类型转换时便抛出TypeError异常。

<span class="hljs-meta">>>> </span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add</span><span class="hljs-params">(a, b)</span>:</span>
<span class="hljs-meta">... </span>    <span class="hljs-keyword">return</span> a + b
<span class="hljs-meta">... </span>
<span class="hljs-meta">>>> </span>add(<span class="hljs-number">1</span>, <span class="hljs-number">2j</span>)
(<span class="hljs-number">1</span>+<span class="hljs-number">2j</span>)
<span class="hljs-meta">>>> </span>add(<span class="hljs-string">'a'</span>, <span class="hljs-string">'b'</span>)
<span class="hljs-string">'ab'</span>
<span class="hljs-meta">>>> </span>add(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>)
<span class="hljs-number">3</span>
<span class="hljs-meta">>>> </span>add(<span class="hljs-number">1.0</span>, <span class="hljs-number">2.3</span>)
<span class="hljs-number">3.3</span>
<span class="hljs-meta">>>> </span>add([<span class="hljs-number">1</span>, <span class="hljs-number">2</span>], [<span class="hljs-number">3</span>, <span class="hljs-number">4</span>])
[<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>]
<span class="hljs-meta">>>> </span>add(<span class="hljs-number">1</span>, <span class="hljs-string">'a'</span>)
Traceback (most recent call last):
  File <span class="hljs-string">"<stdin>"</span>, line <span class="hljs-number">1</span>, <span class="hljs-keyword">in</span> <module>
  File <span class="hljs-string">"<stdin>"</span>, line <span class="hljs-number">2</span>, <span class="hljs-keyword">in</span> add
TypeError: unsupported operand type(s) <span class="hljs-keyword">for</span> +: <span class="hljs-string">'int'</span> <span class="hljs-keyword">and</span> <span class="hljs-string">'str'</span>

所以实际应用中,我们常常需要进行类型检查,但是不推荐使用type(),因为基于内建类型扩展的用户自定义类型,type()并不能准确返回结果:

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserInt</span><span class="hljs-params">(int)</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span><span class="hljs-params">(self, val=<span class="hljs-number">0</span>)</span>:</span>
        self._val = int(val)
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__add__</span><span class="hljs-params">(self, val)</span>:</span>
        <span class="hljs-keyword">if</span> isinstance(val, UserInt):
            <span class="hljs-keyword">return</span> UserInt(self._val + val._val)
        <span class="hljs-keyword">return</span> self._val + val
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__iadd__</span><span class="hljs-params">(self, val)</span>:</span>
        <span class="hljs-keyword">raise</span> NotImplementedError(<span class="hljs-string">"not support operation"</span>)
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__str__</span><span class="hljs-params">(self)</span>:</span>
        <span class="hljs-keyword">return</span> str(self._val)
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__repr__</span><span class="hljs-params">(self)</span>:</span>
        <span class="hljs-keyword">return</span> <span class="hljs-string">"Integer %s"</span> % self._val
<span class="hljs-meta">>>> </span>n = UserInt()
<span class="hljs-meta">>>> </span>n
Integer <span class="hljs-number">0</span>
<span class="hljs-meta">>>> </span>print(n)
<span class="hljs-number">0</span>
<span class="hljs-meta">>>> </span>m = UserInt(<span class="hljs-number">2</span>)
<span class="hljs-meta">>>> </span>print(m)
<span class="hljs-number">2</span>
<span class="hljs-meta">>>> </span>type(n) <span class="hljs-keyword">is</span> int
<span class="hljs-keyword">False</span>                   <span class="hljs-comment"># 显然不合理</span>
<span class="hljs-meta">>>> </span>isinstance(n, int)
<span class="hljs-keyword">True</span>

我们可以使用isinstance来检查:isinstance(object, classinfo)

建议 13:尽量转换为浮点类型后再做除法

<span class="hljs-comment"># 计算平均成绩绩点</span>
<span class="hljs-meta">>></span>> gpa = ((<span class="hljs-number">4</span>*<span class="hljs-number">96</span>+<span class="hljs-number">3</span>*<span class="hljs-number">85</span>+<span class="hljs-number">5</span>*<span class="hljs-number">98</span>+<span class="hljs-number">2</span>*<span class="hljs-number">70</span>)*<span class="hljs-number">4</span>) / ((<span class="hljs-number">4</span>+<span class="hljs-number">3</span>+<span class="hljs-number">5</span>+<span class="hljs-number">2</span>)*<span class="hljs-number">100</span>)
<span class="hljs-meta">>></span>> gpa
<span class="hljs-number">3.625714285714286</span>   <span class="hljs-comment"># 终于知道自己的绩点是咋算的了</span>

建议 14:警惕 eval() 的安全漏洞

eval(expression[, globals[, locals]])将字符串 str 当成有效的表达式来求值并返回计算结果,globas为字典形式,locals为任何映射对象,它们分别表示全局和局部命名空间,两者都省略表达式将在调用的环境中执行,为什么需要警惕eval()呢:

<span class="hljs-comment"># 合理正确地使用</span>
<span class="hljs-meta">>></span>> eval(<span class="hljs-string">"1+1==2"</span>)
True
<span class="hljs-meta">>></span>> eval(<span class="hljs-string">'"a"+"b"'</span>)
<span class="hljs-string">'ab'</span>
<span class="hljs-comment"># 坏心眼的geek</span>
<span class="hljs-meta">>></span>> eval(<span class="hljs-string">'__import__("os").system("dir")'</span>)
Desktop  Documents  Downloads  examples.desktop  Music  Pictures  Public  __pycache_<span class="hljs-number">_</span>  Templates  Videos
<span class="hljs-number">0</span>
<span class="hljs-meta">>></span>> eval(<span class="hljs-string">'__import__("os").system("del * /Q")'</span>)     <span class="hljs-comment"># 嘿嘿嘿</span>

如果确实需要使用eval,建议使用安全性更好的ast.literal_eval。

建议 15:使用 enumerate() 获取序列迭代的索引和值

<span class="hljs-meta">>>> </span>li = [<span class="hljs-string">'a'</span>, <span class="hljs-string">'b'</span>, <span class="hljs-string">'c'</span>, <span class="hljs-string">'d'</span>, <span class="hljs-string">'e'</span>]
<span class="hljs-meta">>>> </span><span class="hljs-keyword">for</span> i, e <span class="hljs-keyword">in</span> enumerate(li):
<span class="hljs-meta">... </span>    print(<span class="hljs-string">'index: '</span>, i, <span class="hljs-string">'element: '</span>, e)
<span class="hljs-meta">... </span>
index:  <span class="hljs-number">0</span> element:  a
index:  <span class="hljs-number">1</span> element:  b
index:  <span class="hljs-number">2</span> element:  c
index:  <span class="hljs-number">3</span> element:  d
index:  <span class="hljs-number">4</span> element:  e
<span class="hljs-comment"># enumerate(squence, start=0) 内部实现</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">enumerate</span><span class="hljs-params">(squence, start=<span class="hljs-number">0</span>)</span>:</span>
    n = start
    <span class="hljs-keyword">for</span> elem <span class="hljs-keyword">in</span> sequence:
        <span class="hljs-keyword">yield</span> n, elem   <span class="hljs-comment"># 666</span>
        n += <span class="hljs-number">1</span>
<span class="hljs-comment"># 明白了原理我们自己也来实现一个反序的</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">reversed_enumerate</span><span class="hljs-params">(squence)</span>:</span>
    n = <span class="hljs-number">-1</span>
    <span class="hljs-keyword">for</span> elem <span class="hljs-keyword">in</span> reversed(sequence):
        <span class="hljs-keyword">yield</span> len(sequence) + n, elem
        n -= <span class="hljs-number">1</span>

建议 16:分清 == 与 is 的适用场景

操作符意义isobject identity==equal

is的作用是用来检查对象的标示符是否一致,也就是比较两个对象在内存中是否拥有同一块内存空间,相当于id(x) == id(y),它并不适用于判断两个字符串是否相等。==才是用来判断两个对象的值是否相等,实际是调用了内部的__eq__,所以a==b相当于a.__eq__(b),也就是说==是可以被重载的,而is不能被重载。

<span class="hljs-meta">>></span>> s1 = <span class="hljs-string">'hello world'</span>
<span class="hljs-meta">>></span>> s2 = <span class="hljs-string">'hello world'</span>
<span class="hljs-meta">>></span>> s1 == s2
True
<span class="hljs-meta">>></span>> s1 is s2
False
<span class="hljs-meta">>></span>> s1.__eq_<span class="hljs-number">_</span>(s2)
True
<span class="hljs-meta">>></span>> a = <span class="hljs-string">'Hi'</span>
<span class="hljs-meta">>></span>> b = <span class="hljs-string">'Hi'</span>
<span class="hljs-meta">>></span>> a == b
True
<span class="hljs-meta">>></span>> a is b
True

咦~怎么上例中的a, b又是“同一对象”了?这跟 Python 的 string interning 机制有关,为了提高系统性能,对于较小的字符串会保留其值的一个副本,当创建新的字符串时直接指向该副本,所以a和b的 id 值是一样的,同样对于小整数[-5, 257)也是如此:

<span class="hljs-meta">>></span>> id(a)
<span class="hljs-number">140709793837832</span>
<span class="hljs-meta">>></span>> id(b)
<span class="hljs-number">140709793837832</span>
<span class="hljs-meta">>></span>> x = -<span class="hljs-number">5</span>
<span class="hljs-meta">>></span>> y = -<span class="hljs-number">5</span>
<span class="hljs-meta">>></span>> x is y
True
<span class="hljs-meta">>></span>> id(x) == id(y)
True

建议 17:考虑兼容性,尽可能使用 Unicode

我之前也总结过编码的问题。由于最早的编码是 ASCII 码,只能表示 128 个字符,显然这对其它语言编码并不适用,Unicode就是为了不同的文字分配一套统一的编码。

建议 18:构建合理的包层次来管理 module

本质上每一个 Python 文件都是一个模块,使用模块可以增强代码的可维护性和可重用性,在较大的项目中,我们需要合理地组织项目层次来管理模块,这就是包(Package)的作用。

一句话说包:一个包含__init__.py 文件的目录。包中的模块可以通过.进行访问,即包名.模块名。那么这个__init__.py文件有什么用呢?最明显的作用就是它区分了包和普通目录,在该文件中申明模块级别的 import 语句从而变成了包级别可见,另外在该文件中定义__all__变量,可以控制需要导入的子包或模块。

这里给出一个较为合理的包组织方式,是FlaskWeb 开发:基于Python的Web应用开发实战一书中推荐而来的:

<span class="hljs-params">|-flasky
    |</span>-app/                      <span class="hljs-comment"># Flask 程序</span>
        <span class="hljs-params">|-templates/            # 存放模板
        |</span>-static/               <span class="hljs-comment"># 静态文件资源</span>
        <span class="hljs-params">|-main/
            |</span>-__init_<span class="hljs-number">_</span>.py
            <span class="hljs-params">|-errors.py         # 蓝本中的错误处理程序
            |</span>-forms.py          <span class="hljs-comment"># 表单对象</span>
            <span class="hljs-params">|-views.py          # 蓝本中定义的程序路由
        |</span>-__init_<span class="hljs-number">_</span>.py
        <span class="hljs-params">|-email.py              # 电子邮件支持
        |</span>-models.py             <span class="hljs-comment"># 数据库模型</span>
    <span class="hljs-params">|-migrations/               # 数据库迁移脚本
    |</span>-tests/                    <span class="hljs-comment"># 单元测试</span>
        <span class="hljs-params">|-__init__.py
        |</span>-test*.py
    <span class="hljs-params">|-venv/                     # 虚拟环境
    |</span>-requirements/
        <span class="hljs-params">|-dev.txt               # 开发过程中的依赖包
        |</span>-prod.txt              <span class="hljs-comment"># 生产过程中的依赖包</span>
    <span class="hljs-params">|-config.py                 # 储存程序配置
    |</span>-manage.py                 <span class="hljs-comment"># 启动程序以及其他的程序任务</span>

第 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. 执行动态加载,在当前命名空间中执行编译后的字节码,并将其中所有的对象放入模块对应的字典中
<span class="hljs-meta">>></span>> dir()
[<span class="hljs-string">'__builtins__'</span>, <span class="hljs-string">'__doc__'</span>, <span class="hljs-string">'__loader__'</span>, <span class="hljs-string">'__name__'</span>, <span class="hljs-string">'__package__'</span>, <span class="hljs-string">'__spec__'</span>]
<span class="hljs-meta">>></span>> import test
testing <span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">import</span></span>
<span class="hljs-meta">>></span>> dir()
[<span class="hljs-string">'__builtins__'</span>, <span class="hljs-string">'__doc__'</span>, <span class="hljs-string">'__loader__'</span>, <span class="hljs-string">'__name__'</span>, <span class="hljs-string">'__package__'</span>, <span class="hljs-string">'__spec__'</span>, <span class="hljs-string">'test'</span>]
<span class="hljs-meta">>></span>> import sys
<span class="hljs-meta">>></span>> ‘test’ in sys.modules.keys()
True
<span class="hljs-meta">>></span>> id(test)
<span class="hljs-number">140367239464744</span>
<span class="hljs-meta">>></span>> id(sys.modules[<span class="hljs-string">'test'</span>])
<span class="hljs-number">140367239464744</span>
<span class="hljs-meta">>></span>> dir(test)
[<span class="hljs-string">'__builtins__'</span>, <span class="hljs-string">'__cached__'</span>, <span class="hljs-string">'__doc__'</span>, <span class="hljs-string">'__file__'</span>, <span class="hljs-string">'__loader__'</span>, <span class="hljs-string">'__name__'</span>, <span class="hljs-string">'__package__'</span>, <span class="hljs-string">'__spec__'</span>, <span class="hljs-string">'a'</span>, <span class="hljs-string">'b'</span>]
<span class="hljs-meta">>></span>> sys.modules[<span class="hljs-string">'test'</span>].__dict_<span class="hljs-number">_</span>.keys()
dict_keys([<span class="hljs-string">'__file__'</span>, <span class="hljs-string">'__builtins__'</span>, <span class="hljs-string">'__doc__'</span>, <span class="hljs-string">'__loader__'</span>, <span class="hljs-string">'__package__'</span>, <span class="hljs-string">'__spec__'</span>, <span class="hljs-string">'__name__'</span>, <span class="hljs-string">'b'</span>, <span class="hljs-string">'a'</span>, <span class="hljs-string">'__cached__'</span>])

从上可以看出,对于用户自定义的模块,import 机制会创建一个新的 module 将其加入当前的局部命名空间中,同时在 sys.modules 也加入该模块的信息,但本质上是在引用同一个对象,通过test.py所在的目录会多一个字节码文件。

建议 20:优先使用 absolute import 来导入模块

建议 21: i+=1 不等于 ++i

首先++i或–i在 Python 语法上是合法,但并不是我们通常理解的自增或自减操作:

<span class="hljs-meta">>></span>> ++<span class="hljs-number">1</span>     <span class="hljs-comment"># +(+1)</span>
<span class="hljs-number">1</span>
<span class="hljs-meta">>></span>> --<span class="hljs-number">1</span>     <span class="hljs-comment"># -(-1)</span>
<span class="hljs-number">1</span>
<span class="hljs-meta">>></span>> +++<span class="hljs-number">2</span>
<span class="hljs-number">2</span>
<span class="hljs-meta">>></span>> ---<span class="hljs-number">2</span>
-<span class="hljs-number">2</span>

原来+或-只表示正负数符号。

建议 22:使用 with 自动关闭资源

对于打开的资源我们记得关闭它,如文件、数据库连接等,Python 提供了一种简单优雅的解决方案:with。

先来看with实现的原理吧。

with的实现得益于一个称为上下文管理器(context manager)的东西,它定义程序运行时需要建立的上下文,处理程序的进入和退出,实现了上下文管理协议,即对象中定义了__enter__()和__exit__(),任何实现了上下文协议的对象都可以称为一个上下文管理器:

  • __enter__():返回运行时上下文相关的对象
  • __exit__(exception_type, exception_value, traceback):退出运行时的上下文,处理异常、清理现场等

包含with语句的代码块执行过程如下:

<span class="hljs-keyword">with</span> 表达式 [<span class="hljs-keyword">as</span> 目标]:
    代码块
<span class="hljs-comment"># 例</span>
<span class="hljs-meta">>>> </span><span class="hljs-keyword">with</span> open(<span class="hljs-string">'test.txt'</span>, <span class="hljs-string">'w'</span>) <span class="hljs-keyword">as</span> f:
<span class="hljs-meta">... </span>    f.write(<span class="hljs-string">'test'</span>)
<span class="hljs-meta">... </span>
<span class="hljs-number">4</span>
<span class="hljs-meta">>>> </span>f.__enter__
<built-<span class="hljs-keyword">in</span> method __enter__ of _io.TextIOWrapper object at <span class="hljs-number">0x7f1b967aaa68</span>>
<span class="hljs-meta">>>> </span>f.__exit__
<built-<span class="hljs-keyword">in</span> method __exit__ of _io.TextIOWrapper object at <span class="hljs-number">0x7f1b967aaa68</span>>
  1. 计算表达式的值,返回一个上下文管理器对象
  2. 加载上下文管理器对象的__exit__()以备后用
  3. 调用上下文管理器对象的__enter__()
  4. 将__enter__()的返回值赋给目标对象
  5. 执行代码块,正常结束调用__exit__(),其返回值直接忽略,如果发生异常,会调用__exit__()并将异常类型、值及 traceback 作为参数传递给__exit__(),__exit__()返回值为 false 异常将会重新抛出,返回值为 true 异常将被挂起,程序继续执行

于此,我们可以自定义一个上下文管理器:

<span class="hljs-meta">>>> </span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyContextManager</span><span class="hljs-params">(object)</span>:</span>
<span class="hljs-meta">... </span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__enter__</span><span class="hljs-params">(self)</span>:</span>
<span class="hljs-meta">... </span>        print(<span class="hljs-string">'entering...'</span>)
<span class="hljs-meta">... </span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__exit__</span><span class="hljs-params">(self, exception_type, exception_value, traceback)</span>:</span>
<span class="hljs-meta">... </span>        print(<span class="hljs-string">'leaving...'</span>)
<span class="hljs-meta">... </span>        <span class="hljs-keyword">if</span> exception_type <span class="hljs-keyword">is</span> <span class="hljs-keyword">None</span>:
<span class="hljs-meta">... </span>            print(<span class="hljs-string">'no exceptions!'</span>)
<span class="hljs-meta">... </span>            <span class="hljs-keyword">return</span> <span class="hljs-keyword">False</span>
<span class="hljs-meta">... </span>        <span class="hljs-keyword">elif</span> exception_type <span class="hljs-keyword">is</span> ValueError:
<span class="hljs-meta">... </span>            print(<span class="hljs-string">'value error!'</span>)
<span class="hljs-meta">... </span>            <span class="hljs-keyword">return</span> <span class="hljs-keyword">True</span>
<span class="hljs-meta">... </span>        <span class="hljs-keyword">else</span>:
<span class="hljs-meta">... </span>            print(<span class="hljs-string">'other error'</span>)
<span class="hljs-meta">... </span>            <span class="hljs-keyword">return</span> <span class="hljs-keyword">True</span>
<span class="hljs-meta">... </span>
<span class="hljs-meta">>>> </span><span class="hljs-keyword">with</span> MyContextManager():
<span class="hljs-meta">... </span>    print(<span class="hljs-string">'Testing...'</span>)
<span class="hljs-meta">... </span>
entering...
Testing...
leaving...
no exceptions!
<span class="hljs-meta">>>> </span><span class="hljs-keyword">with</span> MyContextManager():
<span class="hljs-meta">... </span>    print(<span class="hljs-string">'Testing...'</span>)
<span class="hljs-meta">... </span>    <span class="hljs-keyword">raise</span>(ValueError)
<span class="hljs-meta">... </span>
entering...
Testing...
leaving...
value error!

Python 还提供contextlib模块,通过 Generator 实现,其中的 contextmanager 作为装饰器来提供一种针对函数级别上的上下文管理器,可以直接作用于函数/对象而不必关心__enter__()和__exit__()的实现。

推荐文章

建议 23:使用 else 子句简化循环(异常处理)

Python 的 else 子句提供了隐含的对循环是否由 break 语句引发循环结束的判断,有点绕哈,来看例子:

<span class="hljs-meta">>>> </span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">print_prime</span><span class="hljs-params">(n)</span>:</span>
<span class="hljs-meta">... </span>    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(<span class="hljs-number">2</span>, n):
<span class="hljs-meta">... </span>        <span class="hljs-keyword">for</span> j <span class="hljs-keyword">in</span> range(<span class="hljs-number">2</span>, i):
<span class="hljs-meta">... </span>            <span class="hljs-keyword">if</span> i % j == <span class="hljs-number">0</span>:
<span class="hljs-meta">... </span>                <span class="hljs-keyword">break</span>
<span class="hljs-meta">... </span>        <span class="hljs-keyword">else</span>:
<span class="hljs-meta">... </span>            print(<span class="hljs-string">'{} is a prime number'</span>.format(i))
<span class="hljs-meta">... </span>
<span class="hljs-meta">>>> </span>print_prime(<span class="hljs-number">7</span>)
<span class="hljs-number">2</span> <span class="hljs-keyword">is</span> a prime number
<span class="hljs-number">3</span> <span class="hljs-keyword">is</span> a prime number
<span class="hljs-number">5</span> <span class="hljs-keyword">is</span> a prime number

可以看出,else 子句在循环正常结束和循环条件不成立时被执行,由 break 语句中断时不执行,同样,我们可以利用这颗语法糖作用在 while 和 try…except 中。

转载自http://www.codeceo.com/article/91-suggestions-about-python-part-one.html

admin
版权声明:本站原创文章,由admin2017-05-23发表,共计11076字。
转载提示:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)