第三章:组织你的代码

来源:互联网 发布:石墨文档 mac 编辑:程序博客网 时间:2024/05/22 04:28

3.1 模块和包

3.1.1 使用模块进行封装而不是像其它语言一样使用对象

虽然Python支持面向对象编程,但这不是必须的。很多有经验的Python程序员相对较少的使用类和多态机制。主要有几个原因:

类中大部分数据用列表,字典,和集合存储。Python有更多种类的内建函数和标准库模块对数据交互做了很多优化。一个令人信服的理由,只有在需要的时候类才被使用,几乎不会在API的边界。

在Java中类是基本的封装单元。一个文件代表一个Java类,不管对于解决手边的问题是否是有帮助的。如果我有几个工具函数,把他们封装在Utility类中。如果我们不能直观理解Utility对象意味着什么,没关系。当然,我有点夸张,道理是显然的。一旦一个人强制将所有的东西都变为一个类,将这种符号延伸到其它编程语言是容易的。

在Python中,一组相关的函数和数据很自然的被封装进模块。如果我正在使用MVC框架构建”Chirp”, 我可以用一个名为chirp的包,包含model, view, controller模块。如果”Chirp”是一个野心勃勃的项目,代码量很大,这些模块自己可以很容易的变成包。Controller包可以有一个persistence模块和一个processing模块。包中的任何东西都不相关,除了一个直观的感觉它们应该属于controller。

如果所有的模块都变成类,类与类之间的交互立刻变成一个问题。我们需要小心谨慎的决定方法是否应该被设置为public,状态怎么更新,我们的类支持测试的方式。取代列表和字典,我们有processing和persistence对象,我们必须写代码提供支持。

注意,没有任何描述信息表明”Chirp”应该使用类。简单的导入语句使代码的封装和共享更简单。作为函数参数显式传递状态,使事情变的松耦合。通过我们的系统获取,处理,转换数据流将更简单。

不可否认,类是一种更整洁和自然的代表某些事物的方式。在很多情况下,面向对象编程是一种很方便的范式。但是,别让它成为你可以使用的唯一范式。

3.2 格式化

3.2.1 全局常量全部使用大写字母

为了把定义在模块级别的常量和导入程序中的名字区分开,常量所有字母都为大写。

3.2.1.1 糟糕 的写法

seconds_in_a_day = 60 * 60 * 24 # ... def display_uptime(uptime_in_seconds):     percentage_run_time = (         uptime_in_seconds/seconds_in_a_day) * 100     # "Huh!? Where did seconds_in_a_day come from?"     return 'The process was up {percent} percent of the day'.format(         percent=int(percentage_run_time)) # ... uptime_in_seconds = 60 * 60 * 24 display_uptime(uptime_in_seconds)

3.2.1.2 地道的表达

SECONDS_IN_A_DAY = 60 * 60 * 24 # ... def display_uptime(uptime_in_seconds):     percentage_run_time = (         uptime_in_seconds/SECONDS_IN_A_DAY) * 100     # "Clearly SECONDS_IN_A_DAY is a constant defined     return 'The process was up {percent} percent of the day'.format(        percent=int(percentage_run_time)) # ... uptime_in_seconds = 60 * 60 * 24 display_uptime(uptime_in_seconds)

3.2.2 避免在一行放置多个语句

尽管语言定义允许我们这样做,但是这样写会让代码变的很难阅读。当像if, else, elif这样的语句出现在同一行,这种情况更令人困惑。

3.2.2.1 糟糕的写法

if this_is_bad_code: rewrite_code(); make_it_more_readable();

3.2.2.2 地道的表达

if this_is_bad_code:     rewrite_code()     make_it_more_readable()

3.2.3 根据PEP8格式化你的代码

Python定义了一个标准的化格式化规则的集合,被称为PEP8.如果你浏览Python项目的提交信息,你会发现到处都会提及PEP8整洁。原因很简单:如果我们商定一套共同的命名和格式化习惯约定,无论是对初学者还是经验丰富的开发者,python代码都将立刻变的更容易使用理解。PEP8应该是Python社区里关于地道代码最明显的例子。读PEP,在你的编辑器里安装一个PEP8风格检查插件,开始用这种风格写你的代码,其它开发者会很感激。下面列出一些例子。

这里写图片描述

没有列出来的几乎所有的事情都遵循变量/函数命名习惯“字母用下划线连接”。

3.3 可执行脚本

3.3.1 在你的脚本里使用sys.exit返回恰当的错误代码

Python脚本应该是好的shell公民。在if__name__ == ‘__main__’语句后添加大量代码而不返回任何事情是有诱惑的。避免这种倾向。

创建一个main函数,包含将要作为脚本运行的代码。在main函数中使用sys.exit返回错误代码,当有些东西发生错误,或者所有东西运行完返回0.在if __name == ‘main’语句中唯一应该被调用的代码是sys.exit以参数形式返回main函数中的返回值。

这样做,允许脚本在Unix的管道中使用,被用来监控失败不需要客户规则,也可以被其它程序安全的调用。

3.3.3.1 糟糕的写法

if __name__ == '__main__':     import sys     # What happens if no argument is passed on the     if len(sys.argv) > 1:         argument = sys.argv[1]         result = do_stuff(argument)         # Again, what if this is False? How would other         if result:             do_stuff_with_result(result)

3.3.3.2 地道的表达

def main():     import sys     if len(sys.argv) < 2:         # Calling sys.exit with a string automatically         # prints the string to stderr and exits with         sys.exit('You forgot to pass an argument')     argument = sys.argv[1]     if not result:        sys.exit(1)     do_stuff_with_result(result)     # Optional, since the return value without this return     # statment would default to None, which sys.exit treats     return 0 # The three lines below are the canonical script entry # point lines. You'll see them often in other Python scripts if __name__ == '__main__':     sys.exit(main())

3.3.2 使用if __name__ == ‘__main__’使一个文件被导入后直接运行

不像其它语言中的main()函数,Python没有内建的main入口。相反,加载一个Python源文件之后,python解释器直接开始执行代码。如果你想一个文件即作为可导入的文件又作为独立的脚本,使用if __name__ == ‘main’风格。

3..3.2.1 糟糕的写法

import sys import os FIRST_NUMBER = float(sys.argv[1]) SECOND_NUMBER = float(sys.argv[2]) def divide(a, b):     return a/b # I can't import this file (for the super # useful 'divide' function) without the following # code being executed. if SECOND_NUMBER != 0:     print(divide(FIRST_NUMBER, SECOND_NUMBER))

3..3.2.2 地道的表达

import sys import os def divide(a, b):     return a/b # Will only run if script is executed directly, # not when the file is imported as a module if __name__ == '__main__':     first_number = float(sys.argv[1])     second_number = float(sys.argv[2])         print(divide(first_number, second_number))

3.4 导入

3.4.1 优先使用绝对导入而不是相对导入

当需要导入模块时,有两种导入风格可以使用:绝对导入和相对导入。绝对导入从sys.path可以找到的路径中明确模块的位置。绝对导入明确致命模块的路径,并且该路径是sys.path可以找到的。

相对导入明确要导入模块与当前模块在文件系统中的相对位置。如果你正在创建名为package.sub_package.module的模块,需要导入package.orther_module,你可以使用由点表示的相对路径from ..orther_module import foo.一个.代表包含这个模块的当前包。每一个额外的.被用来表示“XX的父包”。相对导入必须使用from … import … 风格。Import foo 被视为绝对导入。

另一种绝对导入的写法:import package.orther_module

为什么我们要优先使用绝对导入而不是相对导入呢?相对导入弄乱的模块的命名空间。From foo import bar,你绑定了bar在你模块的命名空间。对那些读你代码的人,不是很清楚bar从哪里来,尤其在一个复杂的函数或者模块中使用。Foo.bar使更清晰的明白bar在哪里定义。

3.4.1.1 糟糕的写法

# My location is package.sub_package.module # and I want to import package.other_module. # The following should be avoided: from ...package import other_module

3.4.1.2 地道的表达

# My location is package.sub_package.another_sub_package.module # and I want to import package.other_module. # Either of the following are acceptable: import package.other_module import package.other_module as other

3.4.2 不要使用from foo import * 导入一个模块的上下文

考虑到上一条风格,这个是显而易见的。再倒入语句中使用*很容易弄乱你的命名空间。如果你自己定义的名字和包中定义的名字有冲突的话,这也可能导致问题。

如果你必须从foo包中导入一些名字呢?确保在导入语句中用小括号分组。你不必在同一个模块中写10行导入语句,你的命名空间人保持相对干净。

更好的是,简单使用绝对导入。如果报名或者模块名字太长,使用as语句简短表示。

3.4.2.1 糟糕的写法

from foo import *

3.4.2.2 地道的表达

from foo import (bar, baz, qux,         quux, quuux) # or even better... import foo

3.4.3 用标准顺序管理导入语句

随着项目的增长(尤其那些使用web框架)需要大量的导入语句。把所有导入语句放在每个文件的最上面,为导入语句选择一个标准的顺序,并坚持使用。虽然导入顺序不是特别的重要,下面是Python程序FAQ的建议:

  1. 标准库模块
  2. 安装在site-packages中的第三方库模块
  3. 位于当前项目的模块

很多人粗暴的按字符顺序组织导入。其它人认为这不重要。事实上,它没什么关系。关键是你要选择一个标准顺序,并坚持使用。

3.4.3.1 糟糕的写法

import os.path # Some function and class definitions, # one of which uses os.path # .... import concurrent.futures from flask import render_template # Stuff using futures and Flask's render_template # .... from flask import (Flask, request, session, g,     redirect, url_for, abort,     render_template, flash, _app_ctx_stack) import requests # Code using flask and requests # .... if __name__ == '__main__':     # Imports when imported as a module are not so     # costly that they need to be relegated to inside     import this_project.utilities.sentient_network as skynet     import sys

3.4.3.2 地道的表达

# Easy to see exactly what my dependencies are and where to # make changes if a module or package name changes import concurrent.futures import os.path import sys from flask import (Flask, request, session, g,     redirect, url_for, abort, import requests import this_project.utilities.sentient_network as skynet import this_project.widgets
原创粉丝点击