Python开发规范及建议

2017/02/01 python 共 6353 字,约 19 分钟

基础命令

# 更新 pip
python -m pip install --upgrade pip

# 使用国内镜像提高库安装速度
pip install  name -i https://pypi.tuna.tsinghua.edu.cn/simple

# 清华大学:https://pypi.tuna.tsinghua.edu.cn/simple
# 阿里云:https://mirrors.aliyun.com/pypi/simple/
# 中国科学技术大学:https://pypi.mirrors.ustc.edu.cn/simple/


# 批量安装库
pip install -r requirements.txt


# 创建虚拟环境
python -m venv my_project_env

# 激活虚拟环境
my_project_env\Scripts\activate

Python3

推荐使用Python3而不是Python2,即使使用Python2,也请尽量兼容到Python3,例如:在Python2.x中可以不带括号的print输出log,但是建议使用带括号的,主要是为了兼容Python3.x,推荐做法:

print(xxx)

在Python2中可以使用

Exception, e:

为了兼容Python3,推荐使用:

Exception as e:

听从建议

PyCharm给出的带有下划线的建议,多看看,并根据建议改正。例如Python推荐函数名和变量名是小写,单词中间用下划线分割。类名或文件名最好用大写。如果能够严格跟着做,基本上代码规范上会有很大提升了。

文本格式

新建的Python文件脚本第一行是:

# coding: utf-8

import语句一定是在最前的,作者信息等最好放在import语句后。 例如:

__author__ = 'sing'
# coding: utf-8
import os

可以改为:

# coding: utf-8

import os

__author__ = 'sing'

这个可以在pycharm中设置新建代码模板。

显示声明

Python的变量虽说是无须声明就可以使用,但是代码量一大,变量出现在各个函数中,很容易就跟局部变量弄混,有一个小技巧是在__init__函数中“露个脸”,这样在编写其他函数时就很容易知道该类到底有哪些变量了。类的成员变量的写法上也可以与局部变量有所区别,以免弄混,例如带个下划线的前缀。

class Config:
    def __init__(self, jsonFile):
        self._inited = None
        self._root_dir = None
        self._json = None
        self._packed_args = None
        self._validate_args = None
        self._filtered_args = None
        self._wrapper_version = None
        self._wrap_type = None
        self._in_file = None
        self._jar_configs = []
        self._out_dir = None
        self._log = None
        self._result_log = None

常量

常量最好都放一个文件里,便于后期维护,否则杂乱地分散在不同的脚本文件中很难找。给一个模板:

# coding:utf-8


class Constant:
    @staticmethod
    def test():
        pass

# xxxx
Constant.VER = "1.0"

使用时:

from Constant import Constant

print Constant.VER

参数解析

请不要自行封装解析函数,现有的三方库完全可以满足需求,没有必须重复造轮子(自己实现可能不太稳定,也很有可能存在BUG),推荐使用:

# coding: utf-8

import argparse


'''
argparse.ArgumentParser在解析参数失败时不是抛出异常,而是直接错误退出。
这里重载掉error函数,抛出异常,使得外层可以捕获该异常并输出参数帮助。
'''

class ArgumentParserError(Exception): pass


class MyArgumentParser(argparse.ArgumentParser):
    def error(self, message):
        raise ArgumentParserError(message)

使用时:

args = None
    parser = MyArgumentParser(description="自动打包发布工具参数说明")
    parser.add_argument('key', help="Redis key where items are stored")
    parser.add_argument('--file', required=True, help='设置xxx文件路径')
    parser.add_argument('--ver', help='设置版本号')
    parser.add_argument('--timeout', type=int, default=5)
    parser.add_argument('--limit', type=int, default=0)
    parser.add_argument('--progress_every', type=int, default=100)
    parser.add_argument('-v', '--verbose', action='store_true')
    try:
        args = parser.parse_args()
    except Exception, e:
        parser.print_help()
        return False
    file = args.file

当不明所以的人参数使用错误时输出帮助信息:

usage: Main.py [-h] --file FILE [--ver VER] [--timeout TIMEOUT]
               [--limit LIMIT] [--progress_every PROGRESS_EVERY] [-v]
               key

自动打包发布工具参数说明

positional arguments:
  key                   Redis key where items are stored

optional arguments:
  -h, --help            show this help message and exit
  --file FILE           设置xxx文件路径
  --ver VER             设置版本号
  --timeout TIMEOUT
  --limit LIMIT
  --progress_every PROGRESS_EVERY
  -v, --verbose

路径

路径分隔符用os.sep,不要用写死的斜杠字符或反斜杠字符。 路径拼接可以用os.path.join,少用加号(+)拼接。但是os.path.join函数似乎并不完美,第一个参数最好末尾不带斜杠,而第二个参数的第一个字符也不能是斜杠,例如 os.path.join(self._this_path, ‘/test’) 可能会得到一个不存在的路径。 不过在Python3里,路径的操作增加了pathlib 的库,路径拼接可以这么用:

path_pure = pathlib.PurePath('xxxx')
path_pure = path_pure / 'python' / 'hello.py'  # 拼接路径

我发现在很多项目里面会使用三方的tool,如何使用这些工具的路径?给一个参考:

#coding:utf-8

import os
import Utils


class _PathManager:
    def __init__(self):
        self._this_path = Utils.getthispath()

    # XXX的路径
    def get_test_proj_path(self):
        return os.path.join(self._this_path, '.../BestvPlayerSample2')

    # XXX的路径
    def get_dx_path(self):
        return os.path.join(self._this_path, 'tools/dx.jar')

    # XXX的路径
    def get_wrapper_path(self):
        return os.path.join(self._this_path, r'bin/jarwrapper.jar')

    # proguardLib路径
    def get_proguard_path(self):
        return os.path.join(self._this_path, 'tools/proguard5.2.1/lib/proguard.jar')

    # 加密dex jar包的路径
    def get_cipher_path(self):
        return os.path.join(self._this_path, 'tools/encryptDex.jar')

PathManager = _PathManager()

使用时:

from PathManager import *

PathManager.get_test_proj_path()

打开文件

打开文件推荐使用with,最后不用关闭。

with open('foo.txt', 'w') as f:
    f.write('hello!')

字符串列表的拼接

s = ["Python", "is", "good"]  

# Python is good
res =  ' '.join(s) 

不优雅的做法:

def get_classes_string(self, class_list):
    class_string = ''
    for item in class_list:
        class_string += item
        class_string += ','
    return class_string

字典默认值,GET的第二个参数可以设置默认

d = {'key1':1,'key2':"hello"}

# 字段存在没问题
print d['key1'] + 1
print d['key2'] + 'world'

# 字段不存在使用get加默认值不会有异常
print d.get('key3', 0) + 1
print d.get('key4',"") + 'world'

# 字段不存在会产生异常
print d['key3'] + 1
print d['key4'] + 'world'

测试

  • 在__main__中写测试代码
  • 巧用DEBUG开关:命令行参数没法传到SVN上,使用如下方式可以把DEBUG开关打开,填入测试性的命令行参数,测试完毕后关闭DEBUG开关即可。
DEBUG = True

@logtime(u"SDK加固")
def wrap_sdk(params):

    # 初始化日志
    initLog(None, False)

    # 解析参数
    (options, args) = Options.parse(params)

    return True

if __name__ == '__main__':
    ret = False
    try:
        if DEBUG:
            ret = wrap_sdk([__file__, '--jar=xxxxxx', ''])
        else:
            ret = wrap_sdk(sys.argv)
        if ret is False:
            print "failed"
    except Exception as e:
        print traceback.format_exc()
        os.system('pause')
    if not ret:
        sys.exit(-1)

函数耗时

# 让函数打印耗时
def logtime(name = None):
    def wrapper(func):
        def wrapper2(*args, **kwargs):
            _name = name
            if name is None:
                _name = func.func_name
            else:
                _name = name
            print(_name + u" start")
            startTime = time.time()
            res = func(*args, **kwargs)
            print(u"%s end, time used: %.1f s" % (_name, time.time() - startTime))
            return res
        return wrapper2
    return wrapper

使用时:

@logtime(u"SDK加固")
def wrap_sdk(params):
    pass

路径多用相对路径

考虑到大家是在协同编程,代码需要上传到SVN或Git,有时需要部署到服务器,绝对路径肯定不能适配到每个人,因此要养成尽量多用相对路径的习惯。即使不能直接用相对路径的全路径(例如当前工程目录),也要通过代码调用来动态获取。

流程上的建议

在主流程中处理的各个子节点尽量用logging函数分级打印输出日志,以便日后排错好知道流程走在哪个节点出错的。 如果某个小功能需要输出很多日志,在不出错的情况下可以不用输出,以免淹没其他有用的流程信息,浪费调试时间;在出错的情况下可以输出内部详细日志。 在我们的实际后台页面展示效果上,ERROR类型的日志为高亮的红色,一下子就能看到出错的位置了,排查起来很便捷。

2017-07-14 11:09:46 Main.py    [line:278 ] INFO   日志记录启动
2017-07-14 11:09:46 Main.py    [line:281 ] INFO   版本号v1.01
2017-07-14 11:09:46 Main.py    [line:284 ] INFO   
2017-07-14 11:09:46 Main.py    [line:285 ] INFO   
2017-07-14 11:09:46 Main.py    [line:287 ] INFO   配置解析结果
2017-07-14 11:09:46 Main.py    [line:288 ] INFO   ------------------------------------------------------------
2017-07-14 11:09:46 Main.py    [line:289 ] INFO   
2017-07-14 11:09:46 Main.py    [line:290 ] INFO   
2017-07-14 11:09:46 Main.py    [line:291 ] INFO   
2017-07-14 11:09:46 Main.py    [line:292 ] INFO   
2017-07-14 11:09:46 Main.py    [line:293 ] INFO   
2017-07-14 11:09:46 Main.py    [line:300 ] INFO   ------------------------------------------------------------
2017-07-14 11:09:46 Main.py    [line:121 ] ERROR  [完成]

脚本模板

打开菜单File > Settting,找到Editor > File and Code Templates,Python Script添加:

# coding: utf-8

import os
import sys
import traceback

DEBUG = True


def main(params):
    return True


if __name__ == '__main__':
    ret = False
    try:
        if DEBUG:
            ret = main([__file__, '', ''])	# 这里在调试状态下可以随意填参数,方便排查问题
        else:
            ret = main(sys.argv)
        if ret is False:
            print("failed")
    except:
        print(traceback.format_exc())
        os.system('pause')

函数嵌套层次不要太深

不建议超过五层,太深的层次理解起来太费劲,容易出错也不美观,建议拆分出子模块调用。

文档信息

Search

    Table of Contents