Examples and exercises from Beyond the Basic Stuff with Python by Al Sweigart
A free version of the book can be found at https://inventwithpython.com/beyond/.
Recommended reading:
- Python Crash Course (No Starch Press, 2019) by Eric Matthes
- Impractical Python Projects (No Starch Press, 2018) by Lee Vaughan
- Serious Python (No Starch Press, 2018) by Julien Danjou
Further reading:
- Effective Python (Addison-Wesley Professional, 2019) by Brett Slatkin
- Python Cookbook (O’Reilly Media, 2013) by David Beazley and Brian K. Jones
- Fluent Python (O’Reilly Media, 2021) by Luciano Ramalho
Part I - Getting Started
from pathlib import Path
Path.home() # home directory of user
Path.cwd() # current working directory
Recommended reading for using the command line:
- The Linux Command Line, 2nd Edition (2019) by William Shotts,
- Linux Basics for Hackers (2018) by OccupyTheWeb,
- PowerShell for Sysadmins (2020) by Adam Bertram
History command on MS Windows:
doskey /history
List subfolder contents on MS Windows:
dir /s *.py
List subfolder contents on macOS and Linux:
find . -name "*.py"
Viewing environment variables with set
(on Windows) or env
(on macOS and Linux).
View the current path variable with path
(on Windows) or echo $PATH
(on macOS and Linux).
Part 2 - Best Practices, Tools, and Techniques
A code smell is a source code pattern that signals potential bugs.
Examples:
- duplicate code
- magic numbers
- commented-out code and dead code
- print debugging
- variables with numeric suffixes
- classes that should just Be functions or modules
- list comprehensions within list comprehensions
- empty except blocks and poor error messages
import this
The Zen of Python
- by Tim PetersBeautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than right now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
Examples
- Use
enumerate()
instead ofrange()
- Use the
with
statement instead ofopen()
andclose()
- Use
is
to compare withNone
instead of==
- Use raw strings if your string has many backslashes
- Format strings with f-strings
- Making shallow copies of lists
- Use
get()
andsetdefault()
with dictionaries - Use
collections.defaultdict
for default values - Use dictionaries instead of a
switch
statement - Python’s ugly ternary operator
- Chaining assignment and comparison operators
def spam():
print('Spam! Spam! Spam!')
spam()
# Spam! Spam! Spam!
nospam = spam
nospam()
# Spam! Spam! Spam!
def twice(func):
func()
func()
twice()
# Spam! Spam! Spam!
# Spam! Spam! Spam!
- Don’t add or delete items from a list while looping over it.
The best practice is to create a new list rather than modifying an existing one.
- Don’t copy mutable values without
copy.copy()
andcopy.deepcopy()
.
Use copy.copy()
for a list and copy.deepcopy()
for a list of lists.
- Don’t use mutable values for default arguments.
Using a mutable object (e.g. a list) for the default argument has a subtle problem: the list is created when the function’s def statement executes, not each time the function is called. Any changes made to that list when the function is called, will affect the function the next time it is called.
- Don’t build strings with string concatenation
The pythonic way to build strings is to append the smaller strings to a list and then join the list together into one string.
See script buildstrings.py
.
- Don’t expect
sort()
to sort alphabetically
letters = ['z', 'A', 'a', 'Z']
letters.sort(key=str.lower)
- Don’t assume floating-point numbers are perfectly accurate
If exact precision is needed, use the decimal
module.
-
Don’t chain inequality
!=
operators -
Don’t forget the comma in single-item tuples
- Preallocated integers
- String interning
- Python’s fake increment and decrement operators
all(list)
as evaluating the claim “none of the items in this list are falsey”- Boolean values are integer values
- Chaining multiple kinds of operators
- Python’s antigravity feature
Function Names:
Descriptive and containing a verb.
Function Size Trade-Offs:
Function Parameters and Arguments
- Default Arguments
- Using * and ** to Pass Arguments to Functions
- Using * to Create Variadic Functions
- Using ** to Create Variadic Functions
- Using * and ** to Create Wrapper Functions
Functional Programming
Functional programming is a programming paradigm that emphasizes writing functions that perform calculations without modifying global variables or any external state.
- Side Effects
- Higher-Order Functions
- Lambda Functions
- Mapping and Filtering with List Comprehensions
To define a Python lambda function, use the lambda keyword, followed by a comma-delimited list of parameters (if any), a colon, and then an expression that acts as the return value. Because functions are first-class objects, you can assign a lambda function to a variable, effectively replicating what a def statement does.
Return Values Should Always Have the Same Data Type
Raising Exceptions vs. Returning Error Codes
“Programs must be written for people to read,
and only incidentally for machines to execute.”
Comments
# This is a single-line comment.
"""This is a
multiline string that
also works as a multiline comment. """
Codetags
Tag | Description |
---|---|
#TODO | Introduces a general reminder about work that needs to be done |
#FIXME | Introduces a reminder that this part of the code doesn’t entirely work |
#HACK | Introduces a reminder that this part of the code works, perhaps barely, but that the code should be improved |
#XXX | Introduces a general warning, often of high severity |
Docstrings
Docstrings are multiline comments that appear either at the top of a module’s .py
source code file or directly following a class
or def
statement.
def get(self, url, **kwargs):
"""Sends a GET request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:rtype: requests.Response
"""
...
Type Hints
def describeNumber(number: int) -> str:
if number % 2 == 1:
return 'An odd number. '
elif number == 42:
return 'The answer. '
else:
return 'Yes, that is a number. '
myLuckyNumber: int = 42
print(describeNumber(myLuckyNumber))
Although Python supports syntax for type hints, the Python interpreter completely ignores them. You will need to install a third-party type checker like Mypy.
To specify type hints with multiple types, import Union
from the built-in typing
module.
from typing import Union
spam: Union[int, str, float] = 42
spam = 'hello'
spam = 3.14
- Use cookiecutter to create new (complex) Python projects
- Pro Git by Scott Charcon
- Version Control by Example by Eric Sink
Modules used:
timeit
: for small snippets of codecProfile
: for entire functions or programs
Code from book to test cProfile
import cProfile, rsaCipher
cProfile.run("rsaCipher.encryptAndWriteToFile('encrypted_file.txt', 'al_sweigart_pubkey.txt', 'abc'*100000)")
Big O Algorithm Analysis
How code slows as data grows.
- The Tower of Hanoi
- Four in a Row
The __qualname__
attribute:
>>> type(42) # The object 42 has a type of int.
<class 'int'>
str(type(42)) # Passing the type object to str() returns a messy string.
"<class 'int'>"
>>> type(42).__qualname__ # The __qualname__ attribute is nicer looking.
'int'
Creating multiple levels of inheritance doesn’t add organization
so much as bureaucracy to your code.
Properties:
Designating an attribute as private is only a convention: all attributes in Python are technically public, meaning they’re accessible to code outside the class. There’s nothing to prevent code from intentionally or maliciously changing an attribute to an invalid value.
Dunder methods:
Python has several special method names that begin and end with double underscores, abbreviated as dunder. These methods are called dunder methods, special methods, or magic methods. You’re already familiar with the init() dunder method name, but Python has several more. We often use them for operator overloading—that is, adding custom behaviors that allow us to use objects of our classes with Python operators, such as + or >=. Other dunder methods let objects of our classes work with Python’s built-in functions, such as len() or repr().