Python 源码样式指导及辅助工具
Python 源码样式指导及辅助工具
http://www.315ok.org/blogfolder/57895
http://www.315ok.org/logo.png
Python 源码样式指导及辅助工具
Python 源码样式指导及辅助工具
Python styleguide[font="]Introduction[align=left]We’ve modeled the following rules and recommendations based on the following documents:[/align]
[font="]Line length[align=left]All Python code in this package should be PEP8 valid. This includes adhering to the 80-char line length. If you absolutely need to break this rule, append `` # noqa: E501`` to the offending line to skip it in syntax checks.[/align][align=left]Configuring your editor to display a line at 79th column helps a lot here and saves time.[/align]
[align=left]The line length rule also applies to non-python source files, such as .zcml files, but is a bit more relaxed there.[/align]
[align=left]The rule explicitly does not apply to documentation .rst files. For .rst files including the package documentation but also README.rst, CHANGES.rst and doctests, use semantic line-breaks and add a line break after each sentence. See the :doc:` documentation styleguide </about/contributing/documentation_styleguide>` for the reasoning behind it.[/align]
Breaking lines[align=left]Based on code we love to look at (Pyramid, Requests, etc.), we allow the following two styles for breaking long lines into blocks:[/align]
[align=left]It is best to first run autopep8 in the default non aggressive mode, which means it only does whitespace changes. To run this recursively on the current directory, changing files in place:[/align][/font]
[align=left]Quickly check the changes and then commit them.[/align][align=left]WARNING: be very careful when running this in a skins directory, if you run it there at all. It will make changes to the top of the file like this, which completely breaks the skin script:[/align][/font]
[align=left]With those safe changes out of the way, you can move on to a second, more aggresive round:[/align][/font]
[align=left]Check these changes more thoroughly. At the very least check if Plone can still start in the foreground and that there are no failures or errors in the tests.[/align][align=left]Not all changes are always safe. You can ignore some checks:[/align][/font]
[align=left]This skips the following changes:[/align]
[/font]
[font="]Indentation[align=left]For Python files, we stick with the [backcolor=transparent]PEP 8 recommondation[/backcolor]: Use 4 spaces per indentation level.[/align][align=left]For ZCML and XML (GenericSetup) files, we recommend the [backcolor=transparent]Zope Toolkit’s coding style on ZCML[/backcolor]:[/align]Indentation of 2 characters to show nesting, 4 characters to list attributes on separate lines.This distinction makes it easier to see the difference between attributes and nested elements.
[/font]
[font="]Quoting[align=left]For strings and such prefer using single quotes over double quotes. The reason is that sometimes you do need to write a bit of HTML in your python code, and HTML feels more natural with double quotes so you wrap HTML string into single quotes. And if you are using single quotes for this reason, then be consistent and use them everywhere.[/align][align=left]There are two exceptions to this rule:[/align]
[font="]Docstrings style[align=left]Read and follow [backcolor=transparent]http://www.python.org/dev/peps/pep-0257/[/backcolor]. There is one exception though: We reject BDFL’s recommendation about inserting a blank line between the last paragraph in a multi-line docstring and its closing quotes as it’s Emacs specific and two Emacs users here on the Beer & Wine Sprint both support our way.[/align][align=left]The content of the docstring must be written in the active first-person form, e.g. “Calculate X from Y” or “Determine the exact foo of bar”.[/align][/font]
[align=left]If you wanna be extra nice, you are encouraged to document your method’s parameters and their return values in a [backcolor=transparent]reST field list syntax[/backcolor].[/align][/font]
[align=left]Check out the [backcolor=transparent]plone.api source[/backcolor] for more usage examples. Also, see the following for examples on how to write good Sphinxy docstrings: [backcolor=transparent]http://stackoverflow.com/questions/4547849/good-examples-of-python-docstrings-for-sphinx[/backcolor].[/align][/font]
[font="]Unit tests style[align=left]Read [backcolor=transparent]http://www.voidspace.org.uk/python/articles/unittest2.shtml[/backcolor] to learn what is new in unittest2 and use it.[/align][align=left]This is not true for in-line documentation tests. Those still use old unittest test-cases, so you cannot use assertIn and similar.[/align][/font]
[font="]String formatting[align=left]As per [backcolor=transparent]http://docs.python.org/2/library/stdtypes.html#str.format[/backcolor], we should prefer the new style string formatting (.format()) over the old one (% ()).[/align][align=left]Also use numbering or keyword arguments, like so:[/align][/font]
[align=left]and not like this:[/align][/font]
[align=left]because Python 2.6 supports only explicitly numbered placeholders.[/align][/font]
[font="]About imports
[align=left][backcolor=transparent]isort[/backcolor], a python tool to sort imports can be configured to sort exactly as described above.[/align][align=left]Add the following:[/align][/font]
[align=left]To either .isort.cfg or changing the header from [settings] to [isort] and putting it on setup.cfg.[/align][align=left]You can also use [backcolor=transparent]plone.recipe.codeanalysis[/backcolor] with the [backcolor=transparent]flake8-isort[/backcolor] plugin enabled to check for it.[/align]
[/font]
[font="]Declaring dependencies[align=left]All direct dependencies should be declared in install_requires or extras_require sections in setup.py. Dependencies, which are not needed for a production environment (like “develop” or “test” dependencies) or are optional (like “Archetypes” or “Dexterity” flavors of the same package) should go in extras_require. Remember to document how to enable specific features (and think of using zcml:condition statements, if you have such optional features).[/align][align=left]Generally all direct dependencies (packages directly imported or used in ZCML) should be declared, even if they would already be pulled in by other dependencies. This explicitness reduces possible runtime errors and gives a good overview on the complexity of a package.[/align][align=left]For example, if you depend on Products.CMFPlone and use getToolByName from Products.CMFCore, you should also declare the CMFCore dependency explicitly, even though it’s pulled in by Plone itself. If you use namespace packages from the Zope distribution like Products.Five you should explicitly declare Zope as dependency.[/align][align=left]Inside each group of dependencies, lines should be sorted alphabetically.[/align][/font]
[font="]Versioning scheme[align=left]For software versions, use a sequence-based versioning scheme, which is [backcolor=transparent]compatible with setuptools[/backcolor]:[/align][/font]
[align=left]The way, setuptools interprets versions is intuitive:[/align][/font]
[align=left]You can test it with setuptools:[/align][/font]
[align=left]dev and dev0 are treated as the same:[/align][/font]
[align=left]Setuptools recommends to separate parts with a dot. The website about [backcolor=transparent]semantic versioning[/backcolor] is also worth a read.[/align][/font]
[font="]Concrete Rules
- [backcolor=transparent]PEP8[/backcolor]
- [backcolor=transparent]PEP257[/backcolor]
- [backcolor=transparent]Rope project[/backcolor]
- [backcolor=transparent]Google Style Guide[/backcolor]
- [backcolor=transparent]Pylons Coding Style[/backcolor]
- [backcolor=transparent]Tim Pope on Git commit messages[/backcolor]
[font="]Line length[align=left]All Python code in this package should be PEP8 valid. This includes adhering to the 80-char line length. If you absolutely need to break this rule, append `` # noqa: E501`` to the offending line to skip it in syntax checks.[/align][align=left]Configuring your editor to display a line at 79th column helps a lot here and saves time.[/align]
[align=left]The line length rule also applies to non-python source files, such as .zcml files, but is a bit more relaxed there.[/align]
[align=left]The rule explicitly does not apply to documentation .rst files. For .rst files including the package documentation but also README.rst, CHANGES.rst and doctests, use semantic line-breaks and add a line break after each sentence. See the :doc:` documentation styleguide </about/contributing/documentation_styleguide>` for the reasoning behind it.[/align]
Breaking lines[align=left]Based on code we love to look at (Pyramid, Requests, etc.), we allow the following two styles for breaking long lines into blocks:[/align]
- [align=left]Break into next line with one additional indent block.[/align]
foo = do_something( very_long_argument='foo', another_very_long_argument='bar', ) # For functions the ): needs to be placed on the following line def some_func( very_long_argument='foo', another_very_long_argument='bar', ): - [align=left]If this still doesn’t fit the 80-char limit, break into multiple lines.[/align]
foo = dict( very_long_argument='foo', another_very_long_argument='bar', ) a_long_list = [ "a_fairly_long_string", "quite_a_long_string_indeed", "an_exceptionally_long_string_of_characters", ]
- Arguments on first line, directly after the opening parenthesis are forbidden when breaking lines.
- The last argument line needs to have a trailing comma (to be nice to the next developer coming in to add something as an argument and minimize VCS diffs in these cases).
- The closing parenthesis or bracket needs to have the same indentation level as the first line.
- Each line can only contain a single argument.
- The same style applies to dicts, lists, return calls, etc.
pip install autopep8 autopep8 -i filename.py autopep8 -i -r directory[font="]
[align=left]It is best to first run autopep8 in the default non aggressive mode, which means it only does whitespace changes. To run this recursively on the current directory, changing files in place:[/align][/font]
autopep8 <font color="rgb(102, 102, 102)">-</font>i <font color="rgb(102, 102, 102)">-</font>r <font color="rgb(102, 102, 102)">.</font>[font="]
[align=left]Quickly check the changes and then commit them.[/align][align=left]WARNING: be very careful when running this in a skins directory, if you run it there at all. It will make changes to the top of the file like this, which completely breaks the skin script:[/align][/font]
-##parameters=policy_in='' +# parameters=policy_in=''[font="]
[align=left]With those safe changes out of the way, you can move on to a second, more aggresive round:[/align][/font]
autopep8 <font color="rgb(102, 102, 102)">-</font>i <font color="rgb(102, 102, 102)">--</font>aggressive <font color="rgb(102, 102, 102)">-</font>r <font color="rgb(102, 102, 102)">.</font>[font="]
[align=left]Check these changes more thoroughly. At the very least check if Plone can still start in the foreground and that there are no failures or errors in the tests.[/align][align=left]Not all changes are always safe. You can ignore some checks:[/align][/font]
autopep8 <font color="rgb(102, 102, 102)">-</font>i <font color="rgb(102, 102, 102)">--</font>ignore W690,E711,E721 <font color="rgb(102, 102, 102)">--</font>aggressive <font color="rgb(102, 102, 102)">-</font>r <font color="rgb(102, 102, 102)">.</font>[font="]
[align=left]This skips the following changes:[/align]
- W690: Fix various deprecated code (via lib2to3). (Can be bad for Python 2.4.)
- E721: Use isinstance() instead of comparing types directly. (There are uses of this in for example GenericSetup and plone.api that must not be fixed.)
- E711: Fix comparison with None. (This can break SQLAlchemy code.)
autopep8 <font color="rgb(102, 102, 102)">--</font>diff <font color="rgb(102, 102, 102)">--</font>select E309 <font color="rgb(102, 102, 102)">-</font>r <font color="rgb(102, 102, 102)">.</font>[font="]
[/font]
[font="]Indentation[align=left]For Python files, we stick with the [backcolor=transparent]PEP 8 recommondation[/backcolor]: Use 4 spaces per indentation level.[/align][align=left]For ZCML and XML (GenericSetup) files, we recommend the [backcolor=transparent]Zope Toolkit’s coding style on ZCML[/backcolor]:[/align]Indentation of 2 characters to show nesting, 4 characters to list attributes on separate lines.This distinction makes it easier to see the difference between attributes and nested elements.
[/font]
[font="]Quoting[align=left]For strings and such prefer using single quotes over double quotes. The reason is that sometimes you do need to write a bit of HTML in your python code, and HTML feels more natural with double quotes so you wrap HTML string into single quotes. And if you are using single quotes for this reason, then be consistent and use them everywhere.[/align][align=left]There are two exceptions to this rule:[/align]
- docstrings should always use double quotes (as per PEP-257).
- if you want to use single quotes in your string, double quotes might make more sense so you don’t have to escape those single quotes.
# GOOD print 'short' print 'A longer string, but still using single quotes.' # BAD print "short" print "A long string." # EXCEPTIONS print "I want to use a 'single quote' in my string." """This is a docstring."""
[font="]Docstrings style[align=left]Read and follow [backcolor=transparent]http://www.python.org/dev/peps/pep-0257/[/backcolor]. There is one exception though: We reject BDFL’s recommendation about inserting a blank line between the last paragraph in a multi-line docstring and its closing quotes as it’s Emacs specific and two Emacs users here on the Beer & Wine Sprint both support our way.[/align][align=left]The content of the docstring must be written in the active first-person form, e.g. “Calculate X from Y” or “Determine the exact foo of bar”.[/align][/font]
def foo():
"""Single line docstring."""
def bar():
"""Multi-line docstring.
With the additional lines indented with the beginning quote and a
newline preceding the ending quote.
"""[font="][align=left]If you wanna be extra nice, you are encouraged to document your method’s parameters and their return values in a [backcolor=transparent]reST field list syntax[/backcolor].[/align][/font]
:param foo: blah blah :type foo: string :param bar: blah blah :type bar: int :returns: something[font="]
[align=left]Check out the [backcolor=transparent]plone.api source[/backcolor] for more usage examples. Also, see the following for examples on how to write good Sphinxy docstrings: [backcolor=transparent]http://stackoverflow.com/questions/4547849/good-examples-of-python-docstrings-for-sphinx[/backcolor].[/align][/font]
[font="]Unit tests style[align=left]Read [backcolor=transparent]http://www.voidspace.org.uk/python/articles/unittest2.shtml[/backcolor] to learn what is new in unittest2 and use it.[/align][align=left]This is not true for in-line documentation tests. Those still use old unittest test-cases, so you cannot use assertIn and similar.[/align][/font]
[font="]String formatting[align=left]As per [backcolor=transparent]http://docs.python.org/2/library/stdtypes.html#str.format[/backcolor], we should prefer the new style string formatting (.format()) over the old one (% ()).[/align][align=left]Also use numbering or keyword arguments, like so:[/align][/font]
# GOOD
print "{0} is not {1}".format(1, 2)
print "{bar} is not {foo}".format(foo=1, bar=2)[font="][align=left]and not like this:[/align][/font]
# BAD
print "{} is not {}".format(1, 2)
print "%s is not %s" % (1, 2)[font="][align=left]because Python 2.6 supports only explicitly numbered placeholders.[/align][/font]
[font="]About imports
- [align=left]Don’t use * to import everything from a module, because if you do, pyflakes cannot detect undefined names (W404).[/align]
- [align=left]Don’t use commas to import multiple things on a single line. Some developers use IDEs (like [backcolor=transparent]Eclipse[/backcolor]) or tools (such as [backcolor=transparent]mr.igor[/backcolor]) that expect one import per line. Let’s be nice to them.[/align]
- [align=left]Don’t use relative paths, again to be nice to people using certain IDEs and tools. Also Google Python Style Guide recommends against it.[/align]
# GOOD from plone.app.testing import something from zope.component import getMultiAdapter from zope.component import getSiteManager
[align=left]instead of[/align] - [align=left]Don’t catch ImportError to detect whether a package is available or not, as it might hide circular import errors. Instead, use pkg_resources.get_distribution and catch DistributionNotFound. More background at [backcolor=transparent]http://do3.cc/blog/2010/08/20/do-not-catch-import-errors,-use-pkg_resources/[/backcolor].[/align]
# GOOD import pkg_resources try: pkg_resources.get_distribution('plone.dexterity') except pkg_resources.DistributionNotFound: HAS_DEXTERITY = False else: HAS_DEXTERITY = True[align=left]instead of[/align]
# BAD from plone.app.testing import * from zope.component import getMultiAdapter, getSiteManager
# BAD
try:
import plone.dexterity
HAVE_DEXTERITY = True
except ImportError:
HAVE_DEXTERITY = False# GOOD
from __future__ import division
from Acquisition import aq_inner
from datetime import datetime
from datetime import timedelta
from plone.api import portal
from plone.api.exc import MissingParameterError
from Products.CMFCore.interfaces import ISiteRoot
from Products.CMFCore.WorkflowCore import WorkflowException
import pkg_resources
import random
try:
pkg_resources.get_distribution('plone.dexterity')
except pkg_resources.DistributionNotFound:
HAS_DEXTERITY = False
else:
HAS_DEXTERITY = True[font="][align=left][backcolor=transparent]isort[/backcolor], a python tool to sort imports can be configured to sort exactly as described above.[/align][align=left]Add the following:[/align][/font]
[settings] force_alphabetical_sort = True force_single_line = True lines_after_imports = 2 line_length = 200 not_skip = __init__.py[font="]
[align=left]To either .isort.cfg or changing the header from [settings] to [isort] and putting it on setup.cfg.[/align][align=left]You can also use [backcolor=transparent]plone.recipe.codeanalysis[/backcolor] with the [backcolor=transparent]flake8-isort[/backcolor] plugin enabled to check for it.[/align]
[/font]
[font="]Declaring dependencies[align=left]All direct dependencies should be declared in install_requires or extras_require sections in setup.py. Dependencies, which are not needed for a production environment (like “develop” or “test” dependencies) or are optional (like “Archetypes” or “Dexterity” flavors of the same package) should go in extras_require. Remember to document how to enable specific features (and think of using zcml:condition statements, if you have such optional features).[/align][align=left]Generally all direct dependencies (packages directly imported or used in ZCML) should be declared, even if they would already be pulled in by other dependencies. This explicitness reduces possible runtime errors and gives a good overview on the complexity of a package.[/align][align=left]For example, if you depend on Products.CMFPlone and use getToolByName from Products.CMFCore, you should also declare the CMFCore dependency explicitly, even though it’s pulled in by Plone itself. If you use namespace packages from the Zope distribution like Products.Five you should explicitly declare Zope as dependency.[/align][align=left]Inside each group of dependencies, lines should be sorted alphabetically.[/align][/font]
[font="]Versioning scheme[align=left]For software versions, use a sequence-based versioning scheme, which is [backcolor=transparent]compatible with setuptools[/backcolor]:[/align][/font]
MAJOR.MINOR[.MICRO][.STATUS][font="]
[align=left]The way, setuptools interprets versions is intuitive:[/align][/font]
1.0 < 1.1.dev < 1.1.a1 < 1.1.a2 < 1.1.b < 1.1.rc1 < 1.1 < 1.1.1[font="]
[align=left]You can test it with setuptools:[/align][/font]
>>> from pkg_resources import parse_version
>>> parse_version('1.0') < parse_version('1.1.dev')
... < parse_version('1.1.a1') < parse_version('1.1.a2')
... < parse_version('1.1.b') < parse_version('1.1.rc1')
... < parse_version('1.1') < parse_version('1.1.1')
True[font="][align=left]dev and dev0 are treated as the same:[/align][/font]
>>> parse_version('1.1.dev') == parse_version('1.1.dev0')
True[font="][align=left]Setuptools recommends to separate parts with a dot. The website about [backcolor=transparent]semantic versioning[/backcolor] is also worth a read.[/align][/font]
[font="]Concrete Rules
- [align=left]Do not use tabs in Python code! Use spaces as indenting, 4 spaces for each level. We don’t “require” [backcolor=transparent]PEP8[/backcolor], but most people use it and it’s good for you.[/align]
- [align=left]Indent properly, even in HTML.[/align]
- [align=left]Never use a bare except. Anything like except: pass will likely be reverted instantly.[/align]
- [align=left]Avoid tal:on-error, since this swallows exceptions.[/align]
- [align=left]Don’t use hasattr() - this swallows exceptions, use getattr(foo, 'bar', None) instead. The problem with swallowed exceptions is not just poor error reporting. This can also mask ConflictErrors, which indicate that something has gone wrong at the [backcolor=transparent]ZODB level[/backcolor]![/align]
- [align=left]Never put any HTML in Python code and return it as a string. There are exceptions, though.[/align]
- [align=left]Do not acquire anything unless absolutely necessary, especially tools. For example, instead of using context.plone_utils, use:[/align]
from Products.CMFCore.utils import getToolByName plone_utils = getToolByName(context, 'plone_utils')
- [align=left]Do not put too much logic in ZPT (use [backcolor=transparent]Views[/backcolor] instead!)[/align]
- [align=left]Remember to add [backcolor=transparent]i18n[/backcolor] tags in ZPTs and Python code.[/align]