更好的 Python 代码
Table of Contents
这里,逐步记录一些 Python 代码优化经验和想法。
1. 小工具
- flake8:检查 Python 代码标准
- pep8:检查 Python 代码是否符合 PEP8
2. 减少无用变量
变量增强了表达式的可读性,但过度使用变量也会造成困扰,定义无用的中间变量是很常见的做法,如:
def this_is_a_function(a_list): result = False for i in a_list: if x > 0: result = True return result return result
可以简化:
def this_is_a_function(a_list): for i in a_list: if x > 0: return True return False
有时我也会在函数中看到类似这样的代码:
info = {} info["name"] = user.get_name() info["age"] = user.get_age() return info
可简化为:
return { "name": user.get_name(), "age": user.get_age() }
3. 让结构更美观一点
字段太多时,我喜欢用这样的格式来美化:
info = { "name": "lu4nx", "website": "www.shellcodes.org" }
而不是:
info = {"name": "lu4nx", "website": "www.shellcodes.org"}
或者:
info = {"name": "lu4nx", "website": "www.shellcodes.org"}
甚至函数参数太多的情况下,也是,这样避免挤在一起:
db.update( "User", name="lu4nx", website="www.shellcodes.org", email="lx_at_shellcodes.org", ... )
反正我是不喜欢这样的:
db.update("User", \ name="lu4nx", \ website="www.shellcodes.org", \ email="lx_at_shellcodes.org", \ ...)
4. 尽早提前结束
def this_is_a_function(): if xxx: ...此处省略N行 do_something
这样会更好:
def this_is_a_function(): if not xxx: return ...此处省略N行 do_something
5. 尽量避免 if..elif..
Python 没有 switch 语法,只有 if..elif,经常会在代码中看到类似:
if input_data == "a": return a_handler() elif input_data == "b": return b_handler() elif input_data == "c": return c_handler() else: return default_handler()
如果判断条件太多,可用字典来简化:
handler_functions = { "a": a_handler, "b": b_handler, "c": c_handler } func = handler_functions.get(input_data) if not func: return default_handler() return func()
6. 捕获 Exception 异常的陷阱
我在一段多线程代码里偷了个懒:
try: resp = http_get(url) lock.acquire() .... lock.realease() except Exception: pass
后来死活都出现死锁问题,最后发现 lock.release 写成 lock.realease 了。但由于被异常捕获并忽略了,导致调试很久。
所以:
- Exception 异常尽可能留在最后捕获,先捕获一些已知会发生的异常。
- 不要轻易捕获所有异常并且还不做任何处理,至少也该把异常记录到日志或者打印出来。
7. 编写 Docstring
更多参考 PEP 0257:https://www.python.org/dev/peps/pep-0257/
7.1. 代码风格检查工具:pydocstyle
这两点可以不用遵守:
- D400: First line should end with a period.
- D401: First line should be in imperative mood.
D203 和 D211 有冲突,选其一(参数:–ignore=D203):
- D203: 1 blank line required before class docstring (found 0)
- D203: 1 blank line required before class docstring (found 0)
D212 和 D213 有冲突,选其一(参数:–ignore=D213):
- D212: Multi-line docstring summary should start at the first line
-D213: Multi-line docstring summary should start at the second line
我的默认忽略:
--ignore=D400,D401,D203,D213
7.2. Docstring 风格
主流风格有:
- Google 风格
- Epytext/JavaDoc 风格
- reST(我比较推荐)
注意:如果字符串中有反斜杠,字符串前加 r:
r""" ... """
7.2.1. 模块
在模块的 __init__.py
中添加 docstring,可以将模块的简单使用示例等信息填入,例 requests 的 __init__.py
文件:
""" Requests HTTP Library ~~~~~~~~~~~~~~~~~~~~~ Requests is an HTTP library, written in Python, for human beings. Basic GET usage: >>> import requests >>> r = requests.get('https://www.python.org') >>> r.status_code 200 >>> 'Python is a programming language' in r.content True ... or POST: >>> payload = dict(key1='value1', key2='value2') >>> r = requests.post('https://httpbin.org/post', data=payload) >>> print(r.text) { ... "form": { "key2": "value2", "key1": "value1" }, ... } The other HTTP methods are supported - see `requests.api`. Full documentation is at <http://python-requests.org>. :copyright: (c) 2017 by Kenneth Reitz. :license: Apache 2.0, see LICENSE for more details. """
7.2.2. 单文件
可以把使用方法放在顶层的 docstring 中,例如:
""" 关于本程序 ... usage: filename.py [download|check] - download: xxx - check: zzz """ import sys if __name__ == '__main__': if len(sys.argv) != 2: print(__doc__)
7.2.3. 类
- 字符串开头不包含空格
- 类的三引号单独一行闭合
- 将
__init__
参数的解释、使用示例写进去 - docstring 和第一个方法之间空一行
class Request(RequestHooksMixin): """A user-created :class:`Request <Request>` object. Used to prepare a :class:`PreparedRequest <PreparedRequest>`, which is sent to the server. :param method: HTTP method to use. :param url: URL to send. :param headers: dictionary of headers to send. :param files: dictionary of {filename: fileobject} files to multipart upload. :param data: the body to attach to the request. If a dictionary or list of tuples ``[(key, value)]`` is provided, form-encoding will take place. :param json: json for the body to attach to the request (if files or data is not specified). :param params: URL parameters to append to the URL. If a dictionary or list of tuples ``[(key, value)]`` is provided, form-encoding will take place. :param auth: Auth handler or (user, pass) tuple. :param cookies: dictionary or CookieJar of cookies to attach to this request. :param hooks: dictionary of callback hooks, for internal usage. Usage:: >>> import requests >>> req = requests.Request('GET', 'https://httpbin.org/get') >>> req.prepare() <PreparedRequest [GET]> """ def __init__(self, method=None, url=None, headers=None, files=None, data=None, params=None, auth=None, cookies=None, hooks=None, json=None):
7.2.4. 单行 docstring
- 字符串开头不包含空格
- 三引号闭合仍然是单独一行
示例:
class RequestException(IOError): """There was an ambiguous exception that occurred while handling your request. """ def __init__(self, *args, **kwargs): """Initialize RequestException with `request` and `response` objects.""" response = kwargs.pop('response', None) self.response = response self.request = kwargs.pop('request', None) if (response is not None and not self.request and hasattr(response, 'request')): self.request = self.response.request super(RequestException, self).__init__(*args, **kwargs)
7.2.5. 函数/方法
示例,如果是单行的情况:
def prepare_body(self, data, files, json=None): """Prepares the given HTTP body data."""
示例,如果是多行的情况:
def get_cookie_header(jar, request): """ Produce an appropriate Cookie header string to be sent with `request`, or None. :rtype: str """ r = MockRequest(request) jar.add_cookie_header(r) return r.get_new_headers().get('Cookie')