Effective Python: 59 Specific Ways to Write Better Python (Effective Software Development Series)
Rate it:
4%
Flag icon
Protected instance attributes should be in _leading_underscore format. • Private instance attributes should be in __double_leading_underscore format.
4%
Flag icon
Instance methods in classes should use self as the name of the first parameter (which refers to the object). • Class methods should use cls as the name of the first parameter (which refers to the class).
4%
Flag icon
• Use inline negation (if a is not b) instead of negation of positive expressions (if not a is b).
5%
Flag icon
Imports should be in sections in the following order: standard library modules, third-party modules, your own modules. Each subsection should have imports in alphabetical order.
5%
Flag icon
To convert Unicode characters to binary data, you must use the encode method. To convert binary data to Unicode characters, you must use the decode method.
8%
Flag icon
Slicing deals properly with start and end indexes that are beyond the boundaries of the list. That makes it easy for your code to establish a maximum length to consider for an input sequence.
8%
Flag icon
If you leave out both the start and the end indexes when slicing, you’ll end up with a copy of the original list.
8%
Flag icon
If you assign a slice with no start or end indexes, you’ll replace its entire contents with a copy of what’s referenced (instead of allocating a new list).
9%
Flag icon
Python has special syntax for the stride of a slice in the form somelist[start:end:stride]. This lets you take every nth item when slicing a sequence.
9%
Flag icon
The problem is that the stride syntax often causes unexpected behavior that can introduce bugs. For example, a common Python trick for reversing a byte string is to slice the string with a stride of -1. x = b'mongoose' y = x[::-1] print(y) >>> b'esoognom'
9%
Flag icon
indexes. If you must use stride with start or end indexes, consider using one assignment to stride and another to slice. Click here to view code image b = a[::2]   # ['a', 'c', 'e', 'g'] c = b[1:-1]  # ['c', 'e'] Slicing and then striding will create an extra shallow copy of the data. The first operation should try to reduce the size of the resulting slice by as much as possible.
10%
Flag icon
list. You can do this by providing the expression for your computation and the input sequence to loop over. Click here to view code image a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] squares = [x**2 for x in a] print(squares) >>> [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
10%
Flag icon
Unless you’re applying a single-argument function, list comprehensions are clearer than the map built-in function for simple cases. map requires creating a lambda function for the computation, which is visually noisy. Click here to view code image squares = map(lambda x: x ** 2, a) Unlike map, list comprehensions let you easily filter items from the input list, removing corresponding outputs from the result.
10%
Flag icon
Here, I do this with a list comprehension by including two for expressions. These expressions run in the order provided from left to right. Click here to view code image matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] flat = [x for row in matrix for x in row] print(flat) >>> [1, 2, 3, 4, 5, 6, 7, 8, 9]
10%
Flag icon
my_lists = [     [[1, 2, 3], [4, 5, 6]],     # ... ] flat = [x for sublist1 in my_lists         for sublist2 in sublist1         for x in sublist2] At this point, the multiline comprehension isn’t much shorter than the alternative. Here, I produce the same result using normal loop statements. The indentation of this version makes the looping clearer than the list comprehension. flat = [] for sublist1 in my_lists:     for sublist2 in sublist1:         flat.extend(sublist2)
11%
Flag icon
Multiple conditions at the same loop level are an implicit and expression.
11%
Flag icon
The problem with list comprehensions (see Item 7: “Use List Comprehensions Instead of map and filter”) is that they may create a whole new list containing one item for each value in the input sequence. This is fine for small inputs, but for large inputs this could consume significant amounts of memory and cause your program to crash. For example, say you want to read a file and return the number of characters on each line. Doing this with a list comprehension would require holding the length of every line of the file in memory. If the file is absolutely enormous or perhaps a never-ending ...more
11%
Flag icon
Generator expressions don’t materialize the whole output sequence when they’re run. Instead, generator expressions evaluate to an iterator that yields one item at a time from the expression. A generator expression is created by putting list-comprehension-like syntax between () characters. Here, I use a generator expression that is equivalent to the code above. However, the generator expression immediately evaluates to an iterator and doesn’t make any forward progress. Click here to view code image it = (len(x) for x in open('/tmp/my_file.txt'))
12%
Flag icon
Chaining generators like this executes very quickly in Python. When you’re looking for a way to compose functionality that’s operating on a large stream of input, generator expressions are the best tool for the job.
12%
Flag icon
Python provides the enumerate built-in function for addressing this situation. enumerate wraps any iterator with a lazy generator. This generator yields pairs of the loop index and the next value from the iterator. The resulting code is much clearer.
12%
Flag icon
enumerate provides concise syntax for looping over an iterator and getting the index of each item from the iterator as
12%
Flag icon
Prefer enumerate instead of looping over a range and indexing into a sequence.
13%
Flag icon
To make this code clearer, Python provides the zip built-in function. In Python 3, zip wraps two or more iterators with a lazy generator. The zip generator yields tuples containing the next value from each iterator. The resulting code is much cleaner than indexing into multiple lists.
13%
Flag icon
This is just how zip works. It keeps yielding tuples until a wrapped iterator is exhausted. This approach works fine when you know that the iterators are of the same length, which is often the case for derived lists created by list comprehensions. In many other cases, the truncating behavior of zip is surprising and bad. If you aren’t confident that the lengths of the lists you want to zip are equal, consider using the zip_longest function from the itertools built-in module instead (also called izip_longest in Python 2).
14%
Flag icon
Python has special syntax that allows else blocks to immediately follow for and while loop interior blocks. The else block after a loop only runs if the loop body did not encounter a break statement. Avoid using else blocks after loops because their behavior isn’t intuitive and can be confusing.
15%
Flag icon
Use try/except/else to make it clear which exceptions will be handled by your code and which exceptions will propagate up. When the try block doesn’t raise an exception, the else block will run. The else block helps you minimize the amount of code in the try block and improves
16%
Flag icon
Functions that return None to indicate special meaning are error prone because None and other values (e.g., zero, the empty string) all evaluate to False in conditional expressions. Raise exceptions to indicate special situations instead of returning None. Expect the calling code to handle exceptions properly when they’re documented.
17%
Flag icon
Functions are first-class objects in Python, meaning you can refer to them directly, assign them to variables, pass them as arguments to other functions, compare them in expressions and if statements, etc. This is how the sort method can accept a closure function as the key argument.
28%
Flag icon
Avoid making dictionaries with values that are other dictionaries or long tuples. Use namedtuple for lightweight, immutable data containers before you need the flexibility of a full class. Move your bookkeeping code to use multiple helper classes when your internal state dictionaries get complicated.
37%
Flag icon
Only consider using private attributes to avoid naming conflicts with subclasses that are out of your control.
39%
Flag icon
you perform type checking and validation on values passed to your class. Here, I define a class that ensures all resistance values are above zero ohms:
49%
Flag icon
Concurrency is when a computer does many different things seemingly at the same time. For example, on a computer with one CPU core, the operating system will rapidly change which program is running on the single processor. This interleaves execution of the programs, providing the illusion that the programs are running simultaneously. Parallelism is actually doing many different things at the same time. Computers with multiple CPU cores can execute multiple programs simultaneously. Each CPU core runs the instructions of a separate program, allowing each program to make forward progress during ...more
49%
Flag icon
Python makes it easy to write concurrent programs. Python can also be used to do parallel work through system calls, subprocesses, and C-extensions. But it can be very difficult to make concurrent Python code truly run in parallel. It’s important to understand how to best utilize Python in these subtly different situations.
51%
Flag icon
The standard implementation of Python is called CPython. CPython runs a Python program in two steps. First, it parses and compiles the source text into bytecode. Then, it runs the bytecode using a stack-based interpreter. The bytecode interpreter has state that must be maintained and coherent while the Python program executes. Python enforces coherence with a mechanism called the global interpreter lock (GIL). Essentially, the GIL is a mutual-exclusion lock (mutex) that prevents CPython from being affected by preemptive multithreading, where one thread takes control of a program by ...more
52%
Flag icon
There are ways to get CPython to utilize multiple cores, but it doesn’t work with the standard Thread class (see Item 41: “Consider concurrent.futures for True Parallelism”) and it can require substantial effort. Knowing these limitations you may wonder, why does Python support threads at all? There are two good reasons. First, multiple threads make it easy for your program to seem like it’s doing multiple things at the same time. Managing the juggling act of simultaneous tasks is difficult to implement yourself (see Item 40: “Consider Coroutines to Run Many Functions Concurrently” for an ...more
This highlight has been truncated due to consecutive passage length restrictions.
52%
Flag icon
The problem is that while the slow_systemcall function is running, my program can’t make any other progress. My program’s main thread of execution is blocked on the select system call. This situation is awful in practice. You need to be able to compute your helicopter’s next move while you’re sending it a signal, otherwise it’ll crash. When you find yourself needing to do blocking I/O and computation simultaneously, it’s time to consider moving your system calls to threads.
52%
Flag icon
The parallel time is 5× less than the serial time. This shows that the system calls will all run in parallel from multiple Python threads even though they’re limited by the GIL. The GIL prevents my Python code from running in parallel, but it has no negative effect on system calls. This works because Python threads release the GIL just before they make system calls and reacquire the GIL as soon as the system calls are done.
52%
Flag icon
There are many other ways to deal with blocking I/O besides threads, such as the asyncio built-in module, and these alternatives have important benefits. But these options also require extra work in refactoring your code to fit a different model of execution (see Item 40: “Consider Coroutines to Run Many Functions Concurrently”). Using threads is the simplest way to do blocking I/O in parallel with minimal changes to your program.
52%
Flag icon
Things to Remember Python threads can’t run bytecode in parallel on multiple CPU cores because of the global interpreter lock (GIL). Python threads are still useful despite the GIL because they provide an easy way to do multiple things at seemingly the same time. Use Python threads to make multiple system call...
This highlight has been truncated due to consecutive passage length restrictions.
61%
Flag icon
Decorators have the ability to run additional code before and after any calls to the functions they wrap. This allows them to access and modify input arguments and return values. This functionality can be useful for enforcing semantics, debugging, registering functions, and more.
62%
Flag icon
The solution is to use the wraps helper function from the functools built-in module. This is a decorator that helps you write decorators. Applying it to the wrapper function will copy all of the important metadata about the inner function to the outer function.
62%
Flag icon
Decorators are Python syntax for allowing one function to modify another function at runtime. Using decorators can cause strange behaviors in tools that do introspection, such as debuggers. Use the wraps decorator from the functools built-in module when you define your own decorators to avoid any issues.
70%
Flag icon
For example, you can add documentation by providing a docstring immediately after the def statement of a function.
71%
Flag icon
A standard way of defining documentation makes it easy to build tools that convert the text into more appealing formats (like HTML). This has led to excellent documentation-generation tools for the Python community, such as Sphinx (http://sphinx-doc.org). It’s also enabled community-funded sites like Read the Docs (https://readthedocs.org) that provide free hosting of beautiful-looking documentation for open source Python projects.
72%
Flag icon
Once you’ve written docstrings for your modules, it’s important to keep the documentation up to date. The doctest built-in module makes it easy to exercise usage examples embedded in docstrings to ensure that your source code and its documentation don’t diverge over time.
73%
Flag icon
Python can limit the surface area exposed to API consumers by using the __all__ special attribute of a module or package. The value of __all__ is a list of every name to export from the module as part of its public API. When consuming code does from foo import *, only the attributes in foo.__all__ will be imported from foo. If __all__ isn’t present in foo, then only public attributes, those without a leading underscore, are imported (see Item 27: “Prefer Public Attributes Over Private Ones”).
73%
Flag icon
Simple packages are defined by adding an __init__.py file to a directory that contains other source files. These files become the child modules of the directory’s package. Package directories may also contain other packages.
74%
Flag icon
In some cases, using ValueError makes sense, but for APIs it’s much more powerful to define your own hierarchy of exceptions. You can do this by providing a root Exception in your module. Then, have all other exceptions raised by that module inherit from the root exception.
74%
Flag icon
Having a root exception in a module makes it easy for consumers of your API to catch all of the exceptions that you raise on purpose. For example, here a consumer of your API makes a function call with a try/except statement that catches your root exception:
74%
Flag icon
This try/except prevents your API’s exceptions from propagating too far upward and breaking the calling program. It insulates the calling code from your API. This insulation has three helpful effects.
« Prev 1