Python环境搭建&Flask项目架构&代码风格

Python 最佳实践

Python 日常使用的最佳实践,包括:

  • Python环境搭建
  • 项目工程的架构
  • 单文件的结构
  • pythonic代码风格
  • 优质项目代码
  • 文档
  • 测试
  • 日志记录
  • 配置文件和数据

安装Python

  1. 从Python的官方网站下载Python 3.7对应的或32位安装程序
  2. 然后,运行下载的exe安装包。特别要注意勾上Add Python 3.7 to PATH,然后点“Install Now”即可完成安装。

底层虚拟环境 virtualenv

参考链接1 参考链接2

virtualenv 是一个 Python 项目依赖管理工具

建议在开发项目时使用virtualenv做依赖隔离,便于使用pip freeze自动生成requirements文件

通过 pip 安装 virtualenv :

1
2
$ pip install virtualenv
$ virtualenv --version

为项目创建一个虚拟环境,名叫my_project

1
2
$ cd my_project_folder
$ virtualenv my_project

virtualenv 会创建一个文件夹,其中包含使用 Python 项目所有所需的可执行文件。

开始使用虚拟环境前,需要先激活:

1
2
$ source my_project/bin/activate	# linux
$ my_project\Scripts\activate # windows

安装包的话就与往常一样,如:

1
$ pip install XXX

如果你在虚拟环境中暂时完成了工作,可以这样停用它:

1
$ deactivate

为了保持环境的一致性,“冻结” 当前环境包的状态是正确的选择:

1
$ pip freeze > requirements.txt

该命令将创建一个 requirements.txt 文件,里面包含有当前环境所有包的简单列表及对应的版本,这样就能完全搭建出与之前一致的环境了:

1
$ pip install -r requirements.txt

这样有助于在跨设备,跨部署,跨人员的情况下保证环境的一致性。

最后,记得将虚拟环境文件夹从源代码控制中排除,也就是将其添加到 ignore 列表中 ( 详见 Version Control Ignores).

写出优雅的 Python 代码

结构化您的工程

工程化的项目目录具备让项目跑起来的所有基本内容。它里边会包含你的项目文件布局、自动化测试代码,模组,以及安装脚本。当你建立一个新项目的时候,只要把这个目录复制过去,改改目录的名字,再编辑里边的文件就行了。

“结构化”意味着通过编写简洁的代码,并且正如文件系统中文件和目录的组织一样, 代码应该具有逻辑和依赖清晰。

目的:为了防止各个模块的依赖混乱,一般通过模块划分,对Python项目进行结构化。之后,就只剩下架构性的工作,包括设计、实现项目各个模块,并整理清他们之间 的交互关系。

项目架构&仓库

简单的仓库结构模板: 可以在GitHub上找到

布局 作用
README.rst 项目介绍
LICENSE 许可证. 法律相关
setup.py 安装、部署、打包的脚本-分发管理
使用 python setup.py install 安装
requirements.txt 项目所需的依赖库
sample/__init__.py
sample/core.py
sample/helpers.py
sample/setting.py
sample/configs/
sample/data/
sample/static/
sample/templates/
初始化应用并组合所有其它组件
核心代码/具体代码
helpers 工具模块
setting 用来作变量和常量的初始化
configs 配置文件包
data 数据文件包
包括了公共CSS, Javascript等
放置应用的Jinja2模板
docs/conf.py
docs/index.rst
项目的参考文档
tests/ 包的集合和单元测试
上下文环境的文件 context.py -方便测试导包
Makefile 通用的管理任务

Makefile 模板:

1
2
3
4
5
6
7
init:
pip install -r requirements.txt

test:
py.test tests

.PHONY: init test

setup.py 模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from setuptools import setup, find_packages

with open('README.rst') as f:
readme = f.read()
with open('LICENSE') as f:
license = f.read()
setup(
name='slot-extract',
version='0.1.0',
description='龙小湖对话机器人-槽位提取服务',
long_description=readme,
author='Ning Shixian',
author_email='',
url='http://git.longhu.net/ningshixian/slot-extract',
license=license,
packages=find_packages(exclude=('tests', 'docs'))
)

Python中global的用法

global 是python中的一个关键字,作用在变量上,该关键字通常放在函数块中,用来声明该变量为全局变量。

例如下面变量a,定义在函数外面的是全局变量a,定义在fun函数里面的a是另一个a,是局部变量a,两者没有任何关系。好比这个地区有个叫张三的人,公办室里有个另一个叫张三的人。他们是两个不同的人。

1
2
3
4
5
a = 10
def fun():
a = 2
fun()
print(a) # 输出 10

如果想要函数里面的那个a就代表外面的全局变量a,那么就要将函数里面的a 用关键字 global 声明为全局变量

1
2
3
4
5
6
7
a = 10

def fun():
global a
a = 2
fun()
print(a) # 输出 2

单个文件结构)

单个文件结构

Python提供非常简单的包管理系统,即简单地将模块管理机制扩展到一个目录上(目录扩 展为包)。

任意包含 __init__.py 文件的目录都被认为是一个Python包。导入一个包里不同 模块的方式和普通的导入模块方式相似,特别的地方是 __init__.py 文件将集合 所有包范围内的定义。

模块

Python模块对应的是一个.py 文件,是最主要的抽象层之一。抽象层允许将代码分为 不同部分,每个部分包含相关的数据与功能。

例如在项目中,一层控制用户操作 相关接口,另一层处理底层数据 操作。最自然分开这两 层的方式是,在一份文件里重组所有功能接口,并将所有底层操作封装到另一个文件中。这种情况下,接口文件需要导入封装底层操作的文件,可通过 importfrom ... import 语句完成。一旦您使用 import 语句,就可以使用这个模块。

包含函数、变量。类中有属性和方法。一个对象就是一个类的实例。

可变和不可变类型

Python提供两种内置或用户定义的类型。数字、字符串、元组是不可变的列表、字典是可变的

对不可变类型的变量重新赋值,实际上是重新创建一个不可变类型的对象,并将原来的变量重新指向新创建的对象(如果没有其他变量引用原有对象的话(即引用计数为0),原有对象就会被回收)。

字符串是不可变类型。这意味着当需要组合一个 字符串时,将每一部分放到一个可变列表里,使用字符串时再组合 (‘join’) 起来的做法更高效。

1
2
3
4
5
# 创建将019连接起来的字符串 (例 "012..1819")
nums = ""
for n in range(20):
nums += str(n) # 慢且低效
print nums

1
2
3
# 创建将0到19连接起来的字符串 (例 "012..1819")
nums = [str(n) for n in range(20)]
print "".join(nums)

函数的参数

函数的参数可以使用四种不同的方式传递给函数。

  1. 必选参数 是没有默认值的必填的参数 point(x, y)
  2. 关键字参数 是非强制的,且有默认值 point(x, y, z=None)
  3. 任意参数列表 如果函数的参数数量是动态的,该函数可以被定义成 *args 的结构 send(message, *args)
  4. 任意关键字参数字典 如果函数要求一系列待定的命名参数,我们可以使用 **kwargs 的结构。在函数体中, kwargs 是一个字典,它包含所有传递给函数但没有被其他关键字参数捕捉的命名参数

返回值

当一个函数在其正常运行过程中有多个主要出口点时,它会变得难以调试其返回结果,所以保持单个出口点可能会更好。

1
2
3
4
5
6
7
8
9
10
11
def complex_function(a, b, c):
if not a:
return None # 抛出一个异常可能会更好
if not b:
return None # 抛出一个异常可能会更好

# 一些复杂的代码试着用 a,b,c 来计算x
# 如果成功了,抵制住返回 x 的诱惑
if not x:
# 使用其他的方法来计算出 x
return x # 返回值 x 只有一个出口点有利于维护代码

pythonic风格

代码风格:

  1. 每个缩进层级使用4个空格

  2. 每行最多79个字符

  3. 顶层函数或类的定义之间空两行(特别容易漏,漏的话,是报E302 expected 2 blank lines, found 1)

  4. 采用ASCII或者UTF-8编码文件

  5. 每条import导入一个模块,导入放在代码顶端,导入顺序是先标准库,第三方库,本地库

  6. 小括号,大括号,中括号之间的逗号没有额外的空格

  7. 类命名采用骆驼命名法,CamelCase;函数用小写字符

  8. 函数命名使用小写字符,例如xxx_xxx_xxx; 用下划线开头定义私有的属性或方法,如_xxx

命名风格:

  1. 类名使用 UpperCamelCase 风格,必须遵从驼峰形式,如:XmlService

  2. 方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,必须遵从驼峰形式:localValue / getHttpMessage()

  3. 常量命名全部大写,单词间用下划线隔开

  4. 包名统一使用小写、单数、_

  5. 命名时,使用尽量完整的单词组合来表达其意

  6. 方法命名规约:

    1) 获取单个对象的方法用 get 做前缀。
       2) 获取多个对象的方法用 list 做前缀。
       3) 获取统计值的方法用 count 做前缀。
       4) 插入的方法用 save/insert 做前缀。
       5) 删除的方法用 remove/delete 做前缀。
       6) 修改的方法用 update 做前缀。
    
  7. 异常处理

    对大段代码进行 try-catch,这是不负责任的表现。
    捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之
    finally 块必须对资源对象、流对象进行关闭
    日志文件推荐至少保存 15 天

  8. 单元测试

    保证测试粒度足够小,方法级别
    必须使用 assert 来验证
    编写单元测试代码遵守 BCDE 原则,以保证被测试模块的交付质量

    1
    2
    3
    4
    BBorder,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。
    CCorrect,正确的输入,并得到预期的结果。
    DDesign,与设计文档相结合,来编写单元测试。
    EError,强制错误信息输入(如:非法数据、异常流程、非业务允许输入等),并得到预期的结果。
  9. MySQL 数据库

    表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint
    表名、字段名必须使用小写字母或数字,如:aliyun_admin
    小数类型为 decimal,禁止使用 float 和 double
    varchar 是可变长字符串,不预先分配存储空间
    表必备三字段: id, gmt_create, gmt_modified
    表的命名最好是加上“业务名称_表的作用”

阅读优质的代码

  • Howdoi Howdoi 使用 Python 实现的代码搜索工具。
  • Flask Flask 是基于 Werkzeug and Jinja2 的 Python 微框架。 它的目的是快速入门并开发实现你头脑中的好主意。
  • Diamond Diamond 是使用 python 实现的用于收集监控数据的工具,主要收集 metrics 类型的数据,并将其发布到 Graphite 或其他后台。它能够收集 cpu , 内存, 网络, i/o ,负载和磁盘 metrics 数据。此外,它还提供 API 用以实现自定义收集器从任意来源中收集指标数据。
  • Werkzeug Werkzeug 最初是 WSGI 应用程序的各种实用工具的简单集合,并已成为最先进的 WSGI 实用程序模块之一。它包括强大的调试器、功能齐全的请求和响应对象、处理实体标记的 HTTP 实用程序、缓存控制头、HTTP 日期、cookie 处理、文件上传、强大的 URL 路由系统和一群社区贡献的插件模块。
  • Requests Requests 是一个用 Python 实现的 Apache2 授权的 HTTP 库供大家使用。
  • Tablib 是用 Python 实现的无格式的表格数据集库。

项目文档

建议提供相关函数的更多信息,包括它是做什么的, 所抛的任何异常,返回的内容或参数的相关细节。

通常称为 Numpy style Docstrings

代码测试

py.test 测试工具功能完备,并且可扩展,语法简单

1
2
$ pip install pytest
$ pytest tests.py

日志记录

python项目的日志体系构建实现

Python日志最佳实践

日志记录一般有两个目的:

  • 诊断日志 记录与应用程序操作相关的日志。例如,当用户遇到程序报错时, 可通过搜索诊断日志以获得上下文信息。
  • 审计日志 为商业分析而记录的日志。从审计日志中,可提取用户的交易信息, 并结合其他用户资料构成用户报告,或者用来作为优化商业目标的数据支撑。

Python的logging模块提供了通用的日志系统,包括logger,handler,filter,formatter这四个方法:

  1. logger提供日志接口,供应用代码使用。
    logger最长用的操作有两类:配置和发送日志消息。可以通过logging.getLogger(name)获取logger对象,如果不指定name则返回root对象,多次使用相同的name调用getLogger方法返回同一个logger对象。
  2. handler
    将日志记录(log record)发送到合适的目的地(destination),比如文件,socket等。一个logger对象可以通过addHandler方法添加0到多个handler,每个handler又可以定义不同日志级别,以实现日志分级过滤显示。
  3. filter
    提供一种优雅的方式决定一个日志记录是否发送到handler。
  4. formatter
    指定日志记录输出的具体格式。formatter的构造方法需要两个参数:消息的格式字符串和日期字符串,这两个参数都是可选的。

总的一个逻辑是这样的,我们需要创建一个logger,这样在代码执行中,可以使用logger.debug/info/warning/error等方法,将日志输出到指定的位置(可以是控制台、文件,可多选);创建logger之后,需要给logger添加处理句柄addHandler(),每个句柄就对应一种输出,比如StreamHandler表示输出到控制台(当然我这里简单化了),FileHandler输出到指定文件。然后给句柄设置格式Format,这就是你想要看见的格式。

1. logger的定义

Logging.Logger:Logger是Logging模块的主体,进行以下三项工作:

  1. 为程序提供记录日志的接口
  2. 判断日志所处级别,并判断是否要过滤
  3. 根据其日志级别将该条日志分发给不同handler
    常用函数有:
    Logger.setLevel()设置日志级别
    Logger.addHandler()Logger.removeHandler()添加和删除一个Handler
    Logger.addFilter()添加一个Filter,过滤作用

2. handler的使用

所有的handler汇总,按需自取

1
2
3
4
5
6
7
8
9
10
11
12
StreamHandler:logging.StreamHandler;日志输出到流,可以是sys.stderr,sys.stdout或者文件
FileHandler:logging.FileHandler;日志输出到文件
BaseRotatingHandler:logging.handlers.BaseRotatingHandler;基本的日志回滚方式
RotatingHandler:logging.handlers.RotatingHandler;日志回滚方式,支持日志文件最大数量和日志文件回滚
TimeRotatingHandler:logging.handlers.TimeRotatingHandler;日志回滚方式,在一定时间区域内回滚日志文件
SocketHandler:logging.handlers.SocketHandler;远程输出日志到TCP/IP sockets
DatagramHandler:logging.handlers.DatagramHandler;远程输出日志到UDP sockets
SMTPHandler:logging.handlers.SMTPHandler;远程输出日志到邮件地址
SysLogHandler:logging.handlers.SysLogHandler;日志输出到syslog
NTEventLogHandler:logging.handlers.NTEventLogHandler;远程输出日志到Windows NT/2000/XP的事件日志
MemoryHandler:logging.handlers.MemoryHandler;日志输出到内存中的指定buffer
HTTPHandler:logging.handlers.HTTPHandler;通过"GET"或者"POST"远程输出到HTTP服务器

3. Formater的使用

format:指定输出的格式和内容,format可以输出很多有用的信息

1
2
3
4
5
6
7
8
9
10
11
12
参数:作用
%(levelno)s:打印日志级别的数值
%(levelname)s:打印日志级别的名称
%(pathname)s:打印当前执行程序的路径,其实就是sys.argv[0]
%(filename)s:打印当前执行程序名
%(funcName)s:打印日志的当前函数
%(lineno)d:打印日志的当前行号
%(asctime)s:打印日志的时间
%(thread)d:打印线程ID
%(threadName)s:打印线程名称
%(process)d:打印进程ID
%(message)s:打印日志信息

举个例子

1
2
console_fmt = '%(asctime)-15s [%(TASK)s] %(message)s'
console_formatter = logging.Formatter(console_fmt)

4. Level级别

level:设置日志级别,默认为logging.WARNNING;

可以给logger和handler设置不同的级别,比如logger设置为debug,然后handler1设置为error,这样这个输出只会记录error以上的日志级别信息,另一个handler2设置为info,记录info级别以上的信息。有下面这几种可以设置

1
2
3
4
5
6
7
8
9
日志等级:使用范围

FATAL:致命错误
CRITICAL:特别糟糕的事情,如内存耗尽、磁盘空间为空,一般很少使用
ERROR:发生错误时,如IO操作失败或者连接问题
WARNING:发生很重要的事件,但是并不是错误时,如用户登录密码错误
INFO:处理请求或者状态变化等日常事务
DEBUG:调试过程中使用DEBUG等级,如算法中每个循环的中间状态
NOTSET:如果设置了日志级别为NOTSET,那么这里可以采取debug、info的级别的内容也可以显示在控制台上了

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import logging
import auxiliary_module

# 创建名为'spam_application'的记录器
logger = logging.getLogger('spam_application')
logger.setLevel(logging.DEBUG)

# 创建级别为DEBUG的日志处理器
fh = logging.FileHandler('spam.log')
fh.setLevel(logging.DEBUG)

# 创建级别为ERROR的控制台日志处理器
ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)

# 创建格式器,加到日志处理器中
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
ch.setFormatter(formatter)

logger.addHandler(fh)
logger.addHandler(ch)

logger.debug('debug message')
logger.info('info message')
logger.warn('warn message')
logger.error('error message')
logger.critical('critical message')

配置文件和数据

  1. 如框架无特殊规定,配置文件应放置于项目根目录下的config文件夹中
  2. 配置文件在部署、预发布、生产环境、开发环境等环境中会有很大差异,因此请不要将配置文件在上传到git、svn等版本库中, 而是建议在版本库中上传一个配置的示例文件(如:config.example)
  3. 上传到版本库中配置示例文件不允许出现密码、证书、token等敏感信息
  4. 数据和程序应该尽量分离,不要将数据写在代码中,需要持久化存储数据必须使用数据库

参考

Python 最佳实践指南 2018


Python 最佳实践
http://example.com/2020/06/19/2020-06-19-Python 最佳实践/
作者
NSX
发布于
2020年6月19日
许可协议