第三章:组织你的代码

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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值