Naming¶
Most of the standard library was built keeping usability in mind. Python, in this case, can be compared to the pseudocode you might think about when working on a program. Most of the code can be read out loud. For instance, this snippet could be understood even by someone that is not a programmer:
my_list = []
if 'd' not in my_list:
my_list.append('d')
The fact that Python code is so close to natural language is one of the reasons why Python is so easy to learn and use. When you are writing a program, the flow of your thoughts is quickly translated into lines of code.
1. PEP 8¶
PEP 8 (http://www.python.org/dev/peps/pep-0008) provides a style guide for writing Python code. Besides some basic rules, such as indentation, maximum line length, and other details concerning the code layout, PEP 8 also provides a section on naming conventions that most of the code bases follow.
This section provides only a quick summary of PEP 8, and a handy naming guide for each kind of Python syntax element. You should still consider reading the PEP 8 document as mandatory.
1.1. Why and when to follow PEP 8?¶
If you are creating a new software package that is intended to be open sourced, you should always follow PEP 8 because it is a widely accepted standard and is used in most of the open source projects written in Python. If you want to foster any collaboration with other programmers, then you should definitely stick to PEP 8, even if you have different views on the best code style guidelines. Doing so has the benefit of making it a lot easier for other developers to jump straight into your project. Code will be easier to read for newcomers because it will be consistent in style with most of the other Python open source packages.
Also, starting with full PEP 8 compliance saves you time and trouble in the future. If you want to release your code to the public, you will eventually face suggestions from fellow programmers to switch to PEP 8. Arguments as to whether it is really necessary to do so for a particular project tend to be never-ending flame wars that are impossible to win. This is the sad truth, but you may be eventually forced to be consistent with it or risk losing valuable contributors.
Also, restyling of the whole project’s code base if it is in a mature state of development might require a tremendous amount of work. In some cases, such restyling might require changing almost every line of code. While most of the changes can be automated (indentation, newlines, and trailing whitespaces), such massive code overhaul usually introduces a lot of conflicts in every version control workflow that is based on branching. It is also very hard to review so many changes at once. These are the reasons why many open source projects have a rule that style-fixing changes should always be included in separate pull/merge requests or patches that do not affect any feature or bug.
1.2. Team-specific style guidelines¶
Despite providing a comprehensive set of style guidelines, PEP 8 still leaves some freedom for the developers. Especially in terms of nested data literals and multiline function calls that require long lists of arguments. Some teams may decide that they require additional styling rules and the best option is to formalize them in some kind of document that is available for every team member.
Also, in some situations, it may be impossible or economically infeasible to be strictly consistent with PEP 8 in some old projects that had no style guide defined. Such projects will still benefit from formalization of the actual coding conventions even if they do not reflect the official set of PEP 8 rules. Remember, what is more important than consistency with PEP 8 is consistency within the project. If rules are formalized and available as a reference for every programmer, then it is way easier to keep consistency within a project and organization.
2. Naming styles¶
The different naming styles used in Python are:
CamelCase
mixedCase
UPPERCASE and UPPER_CASE_WITH_UNDERSCORES
lowercase and lower_case_with_underscores
_leading and trailing_ underscores, and sometimes __doubled__ underscores
Lowercase and uppercase elements are often a single word, and sometimes a few words concatenated. With underscores, they are usually abbreviated phrases. Using a single word is better. The leading and trailing underscores are used to mark the privacy and special elements.
These styles are applied to the following:
Variables
Functions and methods
Properties
Classes
Modules
Packages
2.1. Variables¶
There are the following two kinds of variables in Python:
Constants: These define values that are not supposed to change during program execution
Public and private variables: These hold the state of applications that can change during program execution
2.1.1. Constants¶
For constant global variables, an uppercase with an underscore is used. It informs the developer that the given variable represents a constant value.
Note
There are no real constants in Python like those in C++, where const can be used. You can change the value of any variable. That’s why Python uses a naming convention to mark a variable as a constant.
For example, the doctest
module provides a list of option flags and directives
(http://docs.python.org/lib/doctest-options.html)
that are small sentences, clearly defining what each option is intended for, for example:
from doctest import IGNORE_EXCEPTION_DETAIL
from doctest import REPORT_ONLY_FIRST_FAILURE
These variable names seem rather long, but it is important to clearly describe them. Their usage is mostly located in the initialization code rather than in the body of the code itself, so this verbosity is not annoying.
Note
Abbreviated names obfuscate the code most of the time. Don’t be afraid of using complete words when an abbreviation seems unclear.
Some constants’ names are also driven by the underlying technology. For instance,
the os
module uses some constants that are defined on the C side, such as
the EX_XXX
series, that defines UNIX exit code numbers. Same name code can be found, as
in the following example, in the system’s sysexits.h
C headers files:
import os
import sys
sys.exit(os.EX_SOFTWARE)
Another good practice when using constants is to gather all of them at the top of a module that uses them. It is also common to combine them under new variables if they are flags or enumerations that allow for such operations, for example:
import doctest
TEST_OPTIONS = (doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE | doctest.REPORT_ONLY_FIRST_FAILURE)
2.1.1.1. Naming and usage¶
Constants are used to define a set of values the program relies on, such as the default configuration filename.
A good practice is to gather all the constants in a single file in the package. That is how
Django works, for instance. A module named settings.py
provides all the constants as
follows:
SQL_USER = 'tarek'
SQL_PASSWORD = 'secret'
SQL_URI = 'postgres://%s:%s@localhost/db' % (SQL_USER, SQL_PASSWORD)
MAX_THREADS = 4
Another approach is to use a configuration file that can be parsed with
the ConfigParser
module, or another configuration parsing tool. But some people argue
that it is rather an overkill to use another file format in a language such as Python, where a
source file can be edited and changed as easily as a text file.
For options that act like flags, a common practice is to combine them with Boolean
operations, as the doctest
and re
modules do. The pattern taken from doctest
is quite
simple, as shown in the following code:
OPTIONS = {}
def register_option(name):
return OPTIONS.setdefault(name, 1 << len(OPTIONS))
def has_option(options, name):
return bool(options & name)
# now defining options
BLUE = register_option('BLUE')
RED = register_option('RED')
WHITE = register_option('WHITE')
This code allows for the following usage:
>>> # let's try them
>>> SET = BLUE | RED
>>> has_option(SET, BLUE)
True
>>> has_option(SET, WHITE)
False
When you define a new set of constants, avoid using a common prefix for them, unless the module has several independent sets of options. The module name itself is a common prefix.
Another good solution for option-like constants would be to use the Enum
class from the
built-in enum
module and simply rely on the set
collection instead of the binary operators.
Important
Using binary bit-wise operations to combine options is common in
Python. The inclusive OR ( |
) operator will let you combine several
options in a single integer, and the AND ( &
) operator will let you check
that the option is present in the integer (refer to
the has_option
function).
2.1.2. Public and private variables¶
For global variables that are mutable and freely available through imports, a lowercase letter with an underscore should be used when they do not need to be protected. If a variable shouldn’t be used and modified outside of its origin module we consider it a private member of that module. A leading underscore, in that case, can mark the variable as a private element of the package, as shown in the following code:
_observers = []
def add_observer(observer):
_observers.append(observer)
def get_observers():
"""Makes sure _observers cannot be modified."""
return tuple(_observers)
Variables that are located in functions, and methods, follow the same rules as public variables and are never marked as private since they are local to the function context.
For class or instance variables, you should use the private marker (the leading underscore) if making the variable a part of the public signature does not bring any useful information, or is redundant. In other words, if the variable is used only internally for the purpose of some other method that provides an actual public feature, it is better to make it private.
For instance, the attributes that are powering a property are good private citizens, as shown in the following code:
class Citizen(object):
def __init__(self, first_name, last_name):
self._first_name = first_name
self._last_name = last_name
@property
def full_name(self):
return f"{self._first_name} {self._last_name}"
Another example would be a variable that keeps some internal state that should not be disclosed to other classes. This value is not useful for the rest of the code, but participates in the behavior of the class:
class UnforgivingElephant(object):
def __init__(self, name):
self.name = name
self._people_to_stomp_on = []
def get_slapped_by(self, name):
self._people_to_stomp_on.append(name)
print('Ouch!')
def revenge(self):
print('10 years later...')
for person in self._people_to_stomp_on:
print('%s stomps on %s' % (self.name, person))
Here is what you’ll see in an interactive session:
>>> joe = UnforgivingElephant('Joe')
>>> joe.get_slapped_by('Tarek')
Ouch!
>>> joe.get_slapped_by('Bill')
Ouch!
>>> joe.revenge()
10 years later...
Joe stomps on Tarek
Joe stomps on Bill
2.2. Functions and methods¶
Functions and methods should be in lowercase with underscores. This rule was not always
true in the old standard library modules. Python 3 did a lot of reorganization of the
standard library, so most of the functions and methods have a consistent letter case. Still,
for some modules such as threading
, you can access the old function names that
used mixedCase
(for example, currentThread
). This was left to allow easier backward
compatibility, but if you don’t need to run your code in older versions of Python, then you
should avoid using these old names.
This way of writing methods was common before the lowercase norm became the standard,
and some frameworks, such as Zope and Twisted, are also still using mixedCase
for
methods. The community of developers working with them is still quite large. So the choice
between mixedCase
and lowercase with an underscore is definitely driven by the libraries
you are using.
As a Zope developer, it is not easy to stay consistent because building an application that mixes pure Python modules and modules that import Zope code is difficult. In Zope, some classes mix both conventions because the code base is still evolving and Zope developers try to adopt the common conventions accepted by so many.
A decent practice in this kind of library environment is to use mixedCase
only for elements
that are exposed in the framework, and to keep the rest of the code in PEP 8 style.
It is also worth noting that developers of the Twisted project took a completely different
approach to this problem. The Twisted project, same as Zope, predates the PEP 8
document. It was started when there were no official guidelines for Python code style, so it
had its own guidelines. Stylistic rules about the indentation, docstrings, line lengths, and so
on could be easily adopted. On the other hand, updating all the code to match naming
conventions from PEP 8 would result in completely broken backward compatibility. And
doing that for such a large project as Twisted is infeasible. So Twisted adopted as much of
PEP 8 as possible and left things such as mixedCase
for variables, functions, and methods
as part of its own coding standard. And this is completely compatible with the PEP 8
suggestion because it exactly says that consistency within a project is more important than
consistency with PEP 8’s style guide.
2.2.1 The private controversy¶
For private methods and functions, we usually use a single leading underscore. This is only a naming convention and has no syntactical meaning. But it doesn’t mean that leading underscores have no syntactical meaning at all. When a method has two leading underscores, it is renamed on the fly by the interpreter to prevent a name collision with a method from any subclass. This feature of Python is called name mangling.
So some people tend to use a double leading underscore for their private attributes to avoid name collision in the subclasses, for example:
class Base(object):
def __secret(self):
print("don't tell")
def public(self):
self.__secret()
class Derived(Base):
def __secret(self):
print("never ever")
From this you will see the following output:
>>> Base.__secret
Traceback (most recent call last):
File "<input>", line 1, in <module>
AttributeError: type object 'Base' has no attribute '__secret'
>>> dir(Base)
['_Base__secret', ..., 'public']
>>> Base().public()
don't tell
>>> Derived().public()
don't tell
The original motivation for name mangling in Python was not to provide the same isolation primitive as a private keyword in C++ but to make sure that some base classes implicitly avoid collisions in subclasses, especially if they are intended to be used in multiple inheritance contexts (for example, as mixin classes). But using it for every attribute that isn’t public obfuscates the code and makes it extremely hard to extend. This is not Pythonic at all.
For more information on this topic, an interesting thread occurred in the Python-Dev mailing list many years ago, where people argued on the utility of name mangling and its fate in the language. It can be found at
http://mail.python.org/pipermail/python-dev/2005-December/058555.html .
2.2.2. Special methods¶
Special methods (https://docs.python.org/3/reference/datamodel.html#special-method-names) start and end with a double underscore and form so-called protocols of the language. Some developers used to call them dunder methods as a portmanteau of double underscore. They are used for operator overloading, container definitions, and so on. For the sake of readability, they should be gathered at the beginning of class definitions, as shown in the following code:
class WeirdInt(int):
def __add__(self, other):
return int.__add__(self, other) + 1
def __repr__(self):
return '<weirdo %d>' % self
# public API
def do_this(self):
print('this')
def do_that(self):
print('that')
No user-defined method should use this convention unless it explicitly has to implement one of the Python object protocols. So don’t invent your own dunder methods such as this:
class BadHabits:
def __my_method__(self):
print('ok')
2.3. Arguments¶
Arguments are in lowercase, with underscores if needed. They follow the same naming
rules as variables because arguments are simply local variables that get their value as
function input values. In the following example, text
and separator
are arguments of
one_line()
function:
def one_line(text, separator=" "):
"""Convert possibly multiline text to single line"""
return separator.join(text.split())
2.4. Properties¶
The names of properties are in lowercase, or in lowercase with underscores. Most of the
time they represent an object’s state, which can be a noun or an adjective, or a small phrase
when needed. In the following code example, the Container
class is a simple data
structure that can return copies of its contents through unique_items
and
ordered_items
properties:
class Container:
_contents = []
def append(self, item):
self._contents.append(item)
@property
def unique_items(self):
return set(self._contents)
@property
def ordered_items(self):
return list(self._contents)
2.5. Classes¶
The names of classes are always in CamelCase
, and may have a leading underscore when
they are private to a module.
In object-oriented programming classes are used to encapsulate the application state.
Attributes of objects are record of that state. Methods are used to modify that state, convert
it into meaningful values or to produce side effects. This is why class names are often noun
phrases and form a usage logic with the method names that are verb phrases. The following
code example contains a Document
class definition with a single save()
method:
class Document():
file_name: str
contents: str
...
def save(self):
with open(self.file_name, 'w') as file:
file.write(self.contents)
Class instances often use the same noun phrases as the document but spelled with
lowercase. So, actual Document
class usage could be as follows:
new_document = Document()
new_document.save()
2.6. Modules and packages¶
Besides the special module __init__
, the module names are in lowercase. The following
are some examples from the standard library:
os
sys
shutil
The Python standard library does not use underscores for module names to separate words but they are used commonly in many other projects. When the module is private to the package, a leading underscore is added. Compiled C or C++ modules are usually named with an underscore and imported in pure Python modules. Package names follow the same rules, since they act more like structured modules.
3. Naming guide¶
A common set of naming rules can be applied on variables, methods, functions, and properties. The names of classes and modules play a very important role in namespace construction and greatly affect code readability. This section contains a miniguide that will help you to define meaningful and readable names for your code elements.
3.1. Using the has/is prefixes for Boolean elements¶
When an element holds a Boolean value you can mark it with is
and/or has
syntax to
make the variable more readable. In the following example, is_connected and
has_cache
are such identifiers that hold Boolean states of the DB
class instances:
class DB:
is_connected = False
has_cache = False
3.2. Using plurals for variables that are collections¶
When an element is holding a sequence, it is a good idea to use a plural form. You can also
do the same for various mapping variables and properties. In following
example, connected_users
and tables
are class attributes that hold multiple values:
class DB:
connected_users = ['Tarek']
tables = {'Customer':['id', 'first_name', 'last_name']}
3.3. Using explicit names for dictionaries¶
When a variable holds a mapping, you should use an explicit name when possible. For
example, if a dict
holds a person’s address, it can be named persons_addresses
:
persons_addresses = {'Bill': '6565 Monty Road', 'Pamela': '45 Python street'}
3.4. Avoid generic names and redundancy¶
You should generally avoid using explicit type names list
, dict
, and set
as parts of
variable names even for local variables. Python now offers function and variable
annotations and a typing hierarchy that allows you to easily mark an expected type for a
given variable so there is no longer a need to describe object types in their names. It makes
the code hard to read, understand, and use. Using a built-in name has to be avoided as well
to avoid shadowing it in the current namespace. Generic verbs should also be avoided,
unless they have a meaning in the namespace.
Instead, domain-specific terms should be used as follows:
def compute(data): # too generic
for element in data:
yield element ** 2
def squares(numbers): # better
for number in numbers:
yield number ** 2
There is also the following list of prefixes and suffixes that, despite being very common in programming, should be, in fact, avoided in function and class names:
Manager
Object
Do, handle, or perform
The reason for this is that they are vague, ambiguous, and do not add any value to the actual name. Jeff Atwood, the co-founder of Discourse and Stack Overflow, has a very good article on this topic and it can be found on his blog at http://blog.codinghorror.com/i-shall-call-it-somethingmanager/
There is also a list of package names that should be avoided. Everything that does not give
any clue about its content can do a lot of harm to the project in the long term. Names such
as misc
, tools
, utils
, common
, or core have a very strong tendency to become endless
bags of various unrelated code pieces of very poor quality that seem to grow in size
exponentially. In most cases, the existence of such a module is a sign of laziness or lack of
enough design efforts. Enthusiasts of such module names can simply forestall the future
and rename them to trash
or dumpster
because this is exactly how their teammates will
eventually treat such modules.
In most cases, it is almost always better to have more small modules even with very little
content but with names that reflect well what is inside. To be honest, there is nothing
inherently wrong with names such as utils
and common
and there is a possibility to use
them responsibly. But reality shows that in many cases they instead become a stub for
dangerous structural antipatterns that proliferate very fast. And if you don’t act fast
enough, you may not be able get rid of them ever. So the best approach is simply to avoid
such risky organizational patterns and nip them in the bud.
3.5. Avoiding existing names¶
It is a bad practice to use names that shadow other names that already exist in the same context. It makes code reading and debugging very confusing. Always try to define original names, even if they are local to the context. If you eventually have to reuse existing names or keywords, use a trailing underscore to avoid name collision, for example:
def xapian_query(terms, or_=True):
"""if or_ is true, terms are combined with the OR clause"""
...
Note that the class
keyword is often replaced by klass
or cls
:
def factory(klass, *args, **kwargs):
return klass(*args, **kwargs)
4. Best practices for arguments¶
The signatures of functions and methods are the guardians of code integrity. They drive its usage and build its APIs. Besides the naming rules that we have discussed previously, special care has to be taken for arguments. This can be done through the following three simple rules:
Build arguments by iterative design.
Trust the arguments and your tests.
Use
*args
and**kwargs
magic arguments carefully.
4.1. Building arguments by iterative design¶
Having a fixed and well-defined list of arguments for each function makes the code more robust. But this can’t be done in the first version, so arguments have to be built by iterative design. They should reflect the precise use cases the element was created for, and evolve accordingly.
Consider the following example of the first versions of some Service
class:
class Service: # version 1
def _query(self, query, type):
print('done')
def execute(self, query):
self._query(query, 'EXECUTE')
If you want to extend the signature of the execute()
method with new arguments in a
way that preserves backward compatibility, you should provide default values for these
arguments as follows:
class Service(object): # version 2
def _query(self, query, type, logger):
logger('done')
def execute(self, query, logger=logging.info):
self._query(query, 'EXECUTE', logger)
The following example from an interactive session presents two styles of calling
the execute()
method of the updated Service
class:
>>> Service().execute('my query')
# old-style call
>>> Service().execute('my query', logging.warning)
WARNING:root:done
4.2. Trusting the arguments and your tests¶
Given the dynamic typing nature of Python, some developers use assertions at the top of their functions and methods to make sure the arguments have proper content, for example:
def divide(dividend, divisor):
assert isinstance(dividend, (int, float))
assert isinstance(divisor, (int, float))
return dividend / divisor
This is often done by developers who are used to static typing and feel that something is missing in Python.
This way of checking arguments is a part of the Design by Contract (DbC) programming style, where preconditions are checked before the code is actually run.
The two main problems in this approach are as follows:
DbC’s code explains how it should be used, making it less readable
This can make it slower, since the assertions are made on each call
The latter can be avoided with the -O
option of the Python interpreter. In that case, all
assertions are removed from the code before the byte code is created, so that the checking is
lost.
In any case, assertions have to be done carefully, and should not be used to bend Python to a statically typed language. The only use case for this is to protect the code from being called nonsensically. If you really want to have some kind of static typing in Python, you should definitely try MyPy or a similar static type checker that does not affect your code runtime and allows you to provide type definitions in a more readable form as function and variable annotations.
4.3. Using *args and **kwargs magic arguments carefully¶
The *args
and **kwargs
arguments can break the robustness of a function or method.
They make the signature fuzzy, and the code often starts to become a small argument
parser where it should not, for example:
def fuzzy_thing(**kwargs):
if 'do_this' in kwargs:
print('ok i did this')
if 'do_that' in kwargs:
print('that is done')
print('ok')
>>> fuzzy_thing(do_this=1)
ok i did this
ok
>>> fuzzy_thing(do_that=1)
that is done
ok
>>> fuzzy_thing(what_about_that=1)
ok
If the argument list gets long and complex, it is tempting to add magic arguments. But this is more a sign of a weak function or method that should be broken into pieces or refactored.
When *args
is used to deal with a sequence of elements that are treated the same way in
the function, asking for a unique container argument such as an iterator is better, for
example:
def sum(*args): # okay
total = 0
for arg in args:
total += arg
return total
def sum(sequence): # better!
total = 0
for arg in sequence:
total += arg
return total
For **kwargs
, the same rule applies. It is better to fix the named arguments to make the
method’s signature meaningful, for example:
def make_sentence(**kwargs):
noun = kwargs.get('noun', 'Bill')
verb = kwargs.get('verb', 'is')
adjective = kwargs.get('adjective', 'happy')
return f'{noun} {verb} {adjective}'
def make_sentence(noun='Bill', verb='is', adjective='happy'):
return f'{noun} {verb} {adjective}'
Another interesting approach is to create a container class that groups several related
arguments to provide an execution context. This structure differs
from *args
or **kwargs
because it can provide internals that work over the values, and
can evolve independently. The code that uses it as an argument will not have to deal with
its internals.
For instance, a web request passed on to a function is often represented by an instance of a class. This class is in charge of holding the data passed by the web server, as shown in the following code:
def log_request(request): # version 1
print(request.get('HTTP_REFERER', 'No referer'))
def log_request(request): # version 2
print(request.get('HTTP_REFERER', 'No referer'))
print(request.get('HTTP_HOST', 'No host'))
Magic arguments cannot be avoided sometimes, especially in metaprogramming. For instance, they are indispensable in the creation of decorators that work on functions with any kind of signature.
5. Class names¶
The name of a class has to be concise, precise, and descriptive. A common practice is to use a suffix that informs about its type or nature, for example:
SQLEngine
MimeTypes
StringWidget
TestCase
For base or abstract classes, a Base or Abstract prefix can be used as follows:
BaseCookie
AbstractFormatter
The most important thing is to be consistent with the class attributes. For example, try to avoid redundancy between the class and its attributes’ names as follows:
>>> SMTP.smtp_send() # redundant information in the namespace
>>> SMTP.send() # more readable and mnemonic
6. Modules and packages¶
The module and package names inform about the purpose of their content. The names are short, in lowercase, and usually without underscores, for example:
sqlite
postgres
sha1
They are often suffixed with lib
if they are implementing a protocol, as in the following:
import smtplib
import urllib
import telnetlib
When choosing a name for a module, always consider its content and limit the amount of redundancy within the whole namespace, for example:
from widgets.stringwidgets import TextWidget # bad
from widgets.strings import TextWidget # better
When a module is getting complex and contains a lot of classes, it is a good practice to create a package and split the module’s elements into other modules.
The __init__
module can also be used to put back some common APIs at the top level of
the package. This approach allows you to organize the code into smaller components
without reducing the ease of use.
7. Useful tools¶
Common conventions and practices used in a software project should always be documented. But having proper documentation for guidelines is often not enough to enforce that these guidelines are actually followed. Fortunately, you can use automated tools that can check sources of your code and verify if it meets specific naming conventions and style guidelines.
The following are a few popular tools:
pylint
: This is a very flexible source code analyzerpycodestyle
andflake8
: This is a small code style checker and a wrapper that adds to it some more useful features, such as static analysis and complexity measurement
7.1. Pylint¶
Besides some quality assurance metrics, Pylint allows for checking of whether a given source code is following a naming convention. Its default settings correspond to PEP 8 and a Pylint script provides a shell report output.
To install Pylint, you can use pip
as follows:
$ pip install pylint
After this step, the command is available and can be run against a module, or several
modules using wildcards. Let’s try it on Buildout’s bootstrap.py
script as follows:
$ wget -O bootstrap.py https://bootstrap.pypa.io/bootstrap-buildout.py -q
$ pylint bootstrap.py
No config file found, using default configuration
************* Module bootstrap
C: 76, 0: Unnecessary parens after 'print' keyword (superfluous-parens)
C: 31, 0: Invalid constant name "tmpeggs" (invalid-name)
C: 33, 0: Invalid constant name "usage" (invalid-name)
C: 45, 0: Invalid constant name "parser" (invalid-name)
C: 74, 0: Invalid constant name "options" (invalid-name)
C: 74, 9: Invalid constant name "args" (invalid-name)
C: 84, 4: Import "from urllib.request import urlopen" should be placed at
the top of the module (wrong-import-position)
...
Global evaluation
-----------------
Your code has been rated at 6.12/10
Real Pylint’s output is a bit longer and here it has been truncated for the sake of brevity.
Remember that Pylint can often give you false positive warnings that decrease the overall
quality rating. For instance, an import statement that is not used by the code of the module
itself is perfectly fine in some cases (for example, building top-level __init__
modules in a
package). Always treat Pylint’s output as a hint and not an oracle.
Making calls to libraries that are using mixedCase for methods can also lower your rating. In any case, the global evaluation of your code score is not that important. Pylint is just a tool that points you to places where there is the possibility for improvements.
It is always recommended to do some tuning of Pylint. In order to do so you need to create
a .pylinrc
configuration file in your project’s root directory. You can do that using the
following -generate-rcfile
option of the pylint
command:
$ pylint --generate-rcfile > .pylintrc
This configuration file is self-documenting (every possible option is described with comment) and should already contain every available Pylint configuration option.
Besides checking for compliance with some arbitrary coding standards, Pylint can also give additional information about the overall code quality, such as:
Code duplication metrics
Unused variables and imports
Missing function, method, or class docstrings
Too long function signatures
The list of available checks that are enabled by default is very long. It is important to know that some of the rules are very arbitrary and cannot always be easily applied to every code base. Remember that consistency is always more valuable than compliance to some arbitrary rules. Fortunately, Pylint is very tunable, so if your team uses some naming and coding conventions that are different from the ones assumed by default, you can easily configure Pylint to check for consistency with your own conventions.
7.2. pycodestyle and flake8¶
pycodestyle
(formerly pep8
) is a tool that has only one purpose; it provides only style
checking against code conventions defined in PEP 8. This is the main difference from Pylint
that has many more additional features. This is the best option for programmers that are
interested in automated code style checking only for the PEP 8 standard, without any
additional tool configuration, as in Pylint’s case.
pycodestyle
can be installed with pip as follows:
$ pip install pycodestyle
When run on the Buildout’s bootstrap.py
script, it will give the following short list of
code style violations:
$ wget -O bootstrap.py https://bootstrap.pypa.io/bootstrap-buildout.py -q
$ pycodestyle bootstrap.py
bootstrap.py:118:1: E402 module level import not at top of file
bootstrap.py:119:1: E402 module level import not at top of file
bootstrap.py:190:1: E402 module level import not at top of file
bootstrap.py:200:1: E402 module level import not at top of file
The main difference from Pylint’s output is its length. pycodestyle
concentrates only on
style, so it does not provide any other warnings, such as unused variables, too long
function names, or missing docstrings. It also does not give a rating. And it really makes
sense because there is no such thing as partial consistency or partial conformance. Any,
even the slightest, violation of style guidelines makes the code immediately inconsistent.
The code of pycodestyle
is simpler than Pylint’s and its output is easier to parse, so it may
be a better choice if you want to make your code style verification part of a continuous
integration process. If you are missing some static analysis features, there is
the flake8
package that is a wrapper on pycodestyle
and a few other tools that are easily
extendable and provide a more extensive suite of features. These include the following:
McCabe complexity measurement
Static analysis via
pyflakes
Disabling whole files or single lines using comments