Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
A "Best of the Best Practices" (BOBP) guide to developing in Python.

The Best of the Best Practices (BOBP) Guide for Python

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 (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

for e in elements:
    e.mutate()

Avoid redundant labeling.

Yes

import audio

core = audio.Core()
controller = audio.Controller()

No

import audio

core = audio.AudioCore()
controller = audio.AudioController()

Prefer "reverse notation".

Yes

elements = ...
elements_active = ...
elements_defunct = ...

No

elements = ...
active_elements = ...
defunct_elements ...

Avoid getter and setter methods.

Yes

person.age = 42

No

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

import canteen
import canteen.sessions
from canteen import sessions

No

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.

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 and Sphinx can help to enforce these standards.

Use one-line docstrings for obvious functions.

"""Return the pathname of ``foo``."""

Multiline docstrings should include

  • Summary line
  • Use case, if appropriate
  • Args
  • Return type and semantics, unless None is returned
"""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.
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

# If the sign is a stop sign
if sign.color == 'red' and sign.sides == 8:
    stop()

Yes

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.

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 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.
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.
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...

Awesome guide..

simply great 💯

kowalcj0 commented Nov 7, 2015

+1 :)

andrecp commented Nov 7, 2015

+1 !

Nice guide, though I don't agree with some of the things. For example:

Document __init__ methods in the docstring for the class.

You place the docstring after the class name, then you have a bunch of class attributes, and now have to look at arguments of __init__ in one place and their meaning in other :(

Great source! But what about type hinting - PEP 0484?

svanoort commented Nov 8, 2015

Good stuff, big +1.

Very good!

♥ wath else ?

Well, this make things a lot easier, thanks for the tips man. ☺

Nice help!

Picked up a few new ones, thx.

Good guide, thank you.

fherdom commented Nov 13, 2015

+1

A very good guide! Kudos

laike9m commented Nov 13, 2015

Great post!

+1

Like

Very well!

In general I agree and think they are good tips.

I strongly disagree with "reverse notation"!
The readability of Python is one of its greatest strengths. However, it still takes time and effort to do the best job possible. One of the most difficult problems reading other people's code is understanding names. Another is having reading disruptions. Reverse notation is very hard to read, especially for English speakers. It may have seemed like a nice grouping mechanism half a century ago (FileOpen, FileClose, FileRead), but that time is past and we have learned much.

The simplest and almost universal rule is that objects should be named as noun or noun phrases (adjective-noun) and functions/methods should be verbs or verb phrases (verb-object). The often unmentioned part is that word order should match written English. This leads to both understandable and readable code.

Compare and contrast reverse notation vs. reading style notation.
Reverse Notation:
bob = PersonBad(name_first='Bob', name_last='Smith')
bob.throw(ball_orange)
The verb-object function form is more often useful with functions, as the is no obvious context provided by the object.
file_delete(some_string) # delete() is much less obvious.

Normal English language style.
bob = GoodPerson(first_name='Bob', last_name='Smith')
bob.throw(orange_ball)
The verb-object for is very useful for free functions.
throw_ball(somename)
There is even a good use for a more extended verb phrase, which in this case clarifies the units.
start = convert_seconds_to_milliseconds(time.time())

thanks, you save my time

Very nice, Thanks

mingbo commented Dec 30, 2015

+1 like

DuyguA commented Jan 3, 2016

Very nice, thanks.

Very informative. Got in one place.

27843002685

skychan commented Feb 22, 2016

cool, thx

Cool guide thanks

vibinr commented Mar 31, 2016

Love the "reverse notation" tip!

Very nice!

erichooi commented Jul 8, 2016

Learn a lot!

frasxav commented Jul 8, 2016

Thank you

Thanks!! +1

vlaw commented Jul 29, 2016

+1

akgeni commented Sep 26, 2016

Must have these rules by side, Thank You.

truly godsend guide for a beginner in python. kudos man

Anybody can fix anything? Taken out of context it's misleading. Khan Academy says:

We don’t have strict artificial boundaries between our teams or codebases. Nobody is forbidden from fixing what they think needs fixing, regardless of team or specialization.

heyost commented Jan 4, 2017

+1

Denkneb commented Feb 20, 2017

Cool

WebVoid commented Mar 15, 2017

+1, but I agree with julie777 on the reverse notation point

Good
Not sure about reverse notation

Cool guide. Thanks.

usptact commented Apr 27, 2017

Great guide but I still prefer tabs to 4 spaces! Modern editors are sophisticated enough to convert them to spaces and show lines of codes appropriately.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment