A "Best of the Best Practices" (BOBP) guide to developing in Python. ## In General ### Values - "Build tools for others that you want to be built for you." - Kenneth Reitz - "Simplicity is alway better than functionality." - Pieter Hintjens - "Fit the 90% use-case. Ignore the nay sayers." - Kenneth Reitz - "Beautiful is better than ugly." - [PEP 20][] - Build for open source (even for closed source projects). ### General Development Guidelines - "Explicit is better than implicit" - [PEP 20][] - "Readability counts." - [PEP 20][] - "Anybody can fix anything." - [Khan Academy Development Docs][] - Fix each [broken window](http://www.artima.com/intv/fixit2.html) (bad design, wrong decision, or poor code) *as soon as it is discovered*. - "Now is better than never." - [PEP 20][] - Test ruthlessly. Write docs for new features. - Even more important that Test-Driven Development--*Human-Driven Development* - These guidelines may--and probably will--change. ## In Particular ### Style Follow [PEP 8][], when sensible. #### Naming - Variables, functions, methods, packages, modules - `lower_case_with_underscores` - Classes and Exceptions - `CapWords` - Protected methods and internal functions - `_single_leading_underscore(self, ...)` - Private methods - `__double_leading_underscore(self, ...)` - Constants - `ALL_CAPS_WITH_UNDERSCORES` ###### General Naming Guidelines Avoid one-letter variables (esp. `l`, `O`, `I`). *Exception*: In very short blocks, when the meaning is clearly visible from the immediate context **Fine** ```python for e in elements: e.mutate() ``` Avoid redundant labeling. **Yes** ```python import audio core = audio.Core() controller = audio.Controller() ``` **No** ```python import audio core = audio.AudioCore() controller = audio.AudioController() ``` Prefer "reverse notation". **Yes** ```python elements = ... elements_active = ... elements_defunct = ... ``` **No** ```python elements = ... active_elements = ... defunct_elements ... ``` Avoid getter and setter methods. **Yes** ```python person.age = 42 ``` **No** ```python person.set_age(42) ``` #### Indentation Use 4 spaces--never tabs. Enough said. #### Imports Import entire modules instead of individual symbols within a module. For example, for a top-level module `canteen` that has a file `canteen/sessions.py`, **Yes** ```python import canteen import canteen.sessions from canteen import sessions ``` **No** ```python from canteen import get_user # Symbol from canteen/__init__.py from canteen.sessions import get_session # Symbol from canteen/sessions.py ``` *Exception*: For third-party code where documentation explicitly says to import individual symbols. *Rationale*: Avoids circular imports. See [here](https://sites.google.com/a/khanacademy.org/forge/for-developers/styleguide/python#TOC-Imports). Put all imports at the top of the page with three sections, each separated by a blank line, in this order: 1. System imports 2. Third-party imports 3. Local source tree imports *Rationale*: Makes it clear where each module is coming from. #### Documentation Follow [PEP 257][]'s docstring guidelines. [reStructured Text](http://docutils.sourceforge.net/docs/user/rst/quickref.html) and [Sphinx](http://sphinx-doc.org/) can help to enforce these standards. Use one-line docstrings for obvious functions. ```python """Return the pathname of ``foo``.""" ``` Multiline docstrings should include - Summary line - Use case, if appropriate - Args - Return type and semantics, unless ``None`` is returned ```python """Train a model to classify Foos and Bars. Usage:: >>> import klassify >>> data = [("green", "foo"), ("orange", "bar")] >>> classifier = klassify.train(data) :param train_data: A list of tuples of the form ``(color, label)``. :rtype: A :class:`Classifier <Classifier>` """ ``` Notes - Use action words ("Return") rather than descriptions ("Returns"). - Document `__init__` methods in the docstring for the class. ```python class Person(object): """A simple representation of a human being. :param name: A string, the person's name. :param age: An int, the person's age. """ def __init__(self, name, age): self.name = name self.age = age ``` ##### On comments Use them sparingly. Prefer code readability to writing a lot of comments. Often, small methods are more effective than comments. *No* ```python # If the sign is a stop sign if sign.color == 'red' and sign.sides == 8: stop() ``` *Yes* ```python def is_stop_sign(sign): return sign.color == 'red' and sign.sides == 8 if is_stop_sign(sign): stop() ``` When you do write comments, remember: "Strunk and White apply." - [PEP 8][] #### Line lengths Don't stress over it. 80-100 characters is fine. Use parentheses for line continuations. ```python wiki = ( "The Colt Python is a .357 Magnum caliber revolver formerly manufactured " "by Colt's Manufacturing Company of Hartford, Connecticut. It is sometimes " 'referred to as a "Combat Magnum". It was first introduced in 1955, the ' "same year as Smith & Wesson's M29 .44 Magnum." ) ``` ### Testing Strive for 100% code coverage, but don't get obsess over the coverage score. #### General testing guidelines - Use long, descriptive names. This often obviates the need for doctrings in test methods. - Tests should be isolated. Don't interact with a real database or network. Use a separate test database that gets torn down or use mock objects. - Prefer [factories](https://github.com/rbarrois/factory_boy) to fixtures. - Never let incomplete tests pass, else you run the risk of forgetting about them. Instead, add a placeholder like `assert False, "TODO: finish me"`. #### Unit Tests - Focus on one tiny bit of functionality. - Should be fast, but a slow test is better than no test. - It often makes sense to have one testcase class for a single class or model. ```python import unittest import factories class PersonTest(unittest.TestCase): def setUp(self): self.person = factories.PersonFactory() def test_has_age_in_dog_years(self): self.assertEqual(self.person.dog_years, self.person.age / 7) ``` #### Functional Tests Functional tests are higher level tests that are closer to how an end-user would interact with your application. They are typically used for web and GUI applications. - Write tests as scenarios. Testcase and test method names should read like a scenario description. - Use comments to write out stories, *before writing the test code*. ```python import unittest class TestAUser(unittest.TestCase): def test_can_write_a_blog_post(self): # Goes to the her dashboard ... # Clicks "New Post" ... # Fills out the post form ... # Clicks "Submit" ... # Can see the new post ... ``` Notice how the testcase and test method read together like "Test A User can write a blog post". ## Inspired by... - [PEP 20 (The Zen of Python)][PEP 20] - [PEP 8 (Style Guide for Python)][PEP 8] - [The Hitchiker's Guide to Python][python-guide] - [Khan Academy Development Docs][] - [Python Best Practice Patterns][] - [Pythonic Sensibilities][] - [The Pragmatic Programmer][] - and many other bits and bytes [Pythonic Sensibilities]: http://www.nilunder.com/blog/2013/08/03/pythonic-sensibilities/ [Python Best Practice Patterns]: http://youtu.be/GZNUfkVIHAY [python-guide]: http://docs.python-guide.org/en/latest/ [PEP 20]: http://www.python.org/dev/peps/pep-0020/ [PEP 257]: http://www.python.org/dev/peps/pep-0257/ [PEP 8]: http://www.python.org/dev/peps/pep-0008/ [Khan Academy Development Docs]: https://sites.google.com/a/khanacademy.org/forge/for-developers [The Pragmatic Programmer]: http://www.amazon.com/The-Pragmatic-Programmer-Journeyman-Master/dp/020161622X/ref=sr_1_1?ie=UTF8&qid=1381886835&sr=8-1&keywords=pragmatic+programmer
Source https://gist.github.com/sloria/7001839
Comments
Post a Comment