dead simple python

your workbench

Packages and Virtual Environments

A package is a collection of code, which is analogous to a library in most
other programming languages.

Some packages require other packages to be installed first. Certain packages
have conflicts with other packages. You can also install specific versions
of a package, depending on what exactly you need.
This is why virtual environments exist.

A virtual environment is a sandbox where you can install only the
Python packages you need for a particular project, without the risk of
those packages clashing with those for another project (or your system).
You never
actually change what Python packages are installed on your system, so
you avoid breaking important things that have nothing to do with your
project.

You may even create virtual environments that have nothing to do with
a particular project.

Creating a Virtual Environment

To create the virtual environment with the name venv_1 in the current
working directory, run the following command in the command line:

python3 -m venv venv_1

venv_1 is the desired path to the virtual environment. In this
case, venv is just a relative path, creating a venv/ directory in the current
working directory. However, you could also use an absolute path, and you
could call it whatever you want

python3 -m venv /opt/myvirtualenv


vscode

python create environment

Activating a Virtual Environment

On Windows, run this:

venv_1\Scripts\activate.bat

venv_1/Scripts/activate.bat

vscode
python select interpreter

If you have multiple shells (usually terminal windows) open, you should be
aware that the virtual environment is only active for the one shell you explicitly
activated it in!

environment paths will be overridden by the virtual environment.
Practically speaking, any packages you install in the virtual environment
are only usable there, and from the venv, the system-wide packages cannot
be accessed unless you explicitly specify otherwise.

If you want the virtual environment to also see the system-wide packages, you can do so with a special flag

python3 -m venv –system-site-packages venv_1

Leaving a Virtual Environment

venv_1\Scripts\deactivate.bat

Introducing pip

System-Wide Packages

python3 -m pip command

Installing Packages

pip install package

If you want to install a specific version of something, append two equal
signs (==), followed by the desired version number (no spaces):

pip install PySide6==6.1.2

Bonus: you can even use operators like >=, to mean “at least this version
or greater.” These are called requirement specifiers.

pip install PySide6>=6.1.2

If you’re on a UNIX-like system, you may need to use pip install
“PySide6>=6.1.2” instead, since > has another meaning in the shell.

requirements.txt

writing a requirements
.txt file for your project. This file lists the packages your project needs. When
creating a virtual environment, you and other users can install all the
required packages with a single command, using this file.

To create this file, list the name of a pip package, as well as its version (if
required), on each line.

requirements.txt

1
2
PySide2>=5.11.1
appdirs

install all those packages in one shot with this
command:

pip install -r requirements.txt

Upgrading Packages

pip install –upgrade PySide6

pip install –upgrade -r requirements.txt

Removing Packages

pip uninstall package

Installing one package will also install any
other packages it relies upon, which we call its dependencies. Uninstalling a
package does not remove its dependencies, so you may need to go through
and remove them yourself.

Herein lies one more advantage of virtual environments. Once I’m in
that sort of a pickle, I can delete the virtual environment, create a new one,
and install only those packages I need.

Seriously. Never use sudo pip

Virtual Environments and Git

If you’re using Git, create or edit a file called .gitignore in the root directory of your repository. Add this line somewhere in it:

venv_1/

the whole shebang

The shebang (short for haSH-BANG, or #!) 1 provides the path to the
Python interpreter. While it’s optional, I strongly recommend including
it in your code, as it means the file can be marked as executable and run
directly, like this:

./hello_world.py

hello_world.py

1
2
#!/usr/bin/env python3
print("Hello, world!")

If you happen to have a script which runs in both Python 2 and Python 3,
use this shebang instead:

#!/usr/bin/env python

File Encoding

Since Python 3.1, all Python files have used the UTF-8 encoding, allowing the
interpreter to use all of the characters in Unicode. (Prior to that version,
the default was to use the old ASCII encoding.)

# -*- coding: latin-1 -*-

# coding: latin-1

# This Python file uses the following encoding: latin-1

Whatever you use, it must be exactly as shown above, except for swapping latin-1 for whatever encoding you want. For this reason, the first or
second forms are preferred.

A Few Extra Virtual Environment Tricks

Using a Virtual Environment Without Activating

assuming my virtual environment is venv, I could do this
in the terminal:

1
2
3
venv/bin/pip install pylint
venv/bin/python
> import pylint

import pylint still will not work on the system-wide Python
interactive shell

Quality Control: Static Analyzers

static
analyzer, which reads your source code, looking for potential problems or
deviations from the standards.

One common type of static analyzer, called a linter,
checks your source code for common mistakes, potential errors, and style
inconsistencies. Two of the most popular linters are Pylint and PyFlakes.

Pylint

Style Janitors: Autoformatting Tools

an autoformatter, which can automatically change your Python code—spacing, indentation, and preferred equivalent expressions (such as != instead of <>)—to be PEP 8 compliant. Two
options are autopep8 and Black.

An Exhibition of Code Editors

PyCharm

The
professional edition adds tools for data, scientific development, and web
development.

SYNTA X CR ASH COURSE

Statements and Expression

Each line of code in Python that ends with a line break is a statement,
sometimes more specifically known as a simple statement.

A section of code that evaluates to a single value is called an expression.

put expressions nearly anywhere a value is expected.
The expression is evaluated down to a value, and that value is used in that
position in the statement.

The Importance of Whitespace

PEP 8 style guide stresses using either four spaces or a single tab per indentation level. Consistency is key! Python really doesn’t care whether you use
tabs, two spaces, four spaces, or even seven spaces (although that’s probably
a step too far) for each level of indentation. The point is to be consistent
within any and every given block of code.

Doing Nothing

On occasion, you will need to insert a statement that has absolutely no
effect. This is particularly useful when you need to put a syntactically valid
placeholder where a suite of code will exist later. For this purpose, Python
provides the pass keyword.

1
2
3
raining = True
if raining:
pass

Comments and Docstrings

To write comments in Python, precede the line with a hash (#). Everything
between the hash and the end of the line is a comment and will be ignored
by the interpreter.

Docstrings

Docstrings exist to provide documentation for functions, classes, and
modules, especially public ones. They conventionally begin and end with
three quotation marks (“””), allowing the string to automatically span multiple lines. You would typically place docstrings at the top, inside of whatever they’re defining, such as in the function above.

There are three important distinctions between comments and
docstrings:

  1. Docstrings are string literals, and they are seen by the interpreter; comments are ignored.
  2. Docstrings are used in automatic documentation generation.
  3. Docstrings are generally only docstrings when they appear at the top of
    the module, function, class, or method they define. Comments can live
    anywhere.

It is perfectly possible to use a triple-quoted string literal to write a sort
of “multiline comment,” but it’s not recommended, since a string literal can
easily get left in a place where Python will try to use it as a value.

Declaring Variables

Python is dynamically typed, meaning the data type of a value is determined when it is evaluated. This contrasts with statically typed languages,
in which you declare the data type initially. (C++ and Java are both statically
typed.)

using the
assignment operator (=). It infers the data type. If the name is a new variable, Python will create it; if the name already exists, Python will change
the value. It’s a pretty straightforward system.

two rules to follow with Python variables:

  1. Define a variable before you access it; otherwise, you’ll get an error.
  2. Don’t change what kind of data you’re storing in the variable, even
    when replacing a value.

Python is considered a strongly typed language, meaning you usually can’t
magically combine data of different types. For example, it won’t allow you
to add an integer and a string together.

Python is, however, weakly bound, so it is possible to assign a value of a
different type to an existing variable. While this is technically permissible,
it is strongly discouraged, as it can produce confusing code.

What About Constants?

Python doesn’t have any formally defined constants. In keeping with PEP 8,
you would indicate a variable is intended to be treated as a constant by
using all-caps names with underscores. This naming convention is sometimes humorously referred to as screaming snake case for the all-caps (screaming) and the underscores (snakes).

For example, the name INTEREST_RATE

Mathematics

Meet the Number Types

Integers (int) store whole numbers. In Python, integers are always signed
and effectively have no maximum value. Integers use decimal base (base-10)
by default,

Floating-point numbers (float) store numbers with a decimal part (for
example, 3.141592).
Internally, values are stored as double-precision, IEEE 754 floatingpoint numbers, which are subject to the limits inherent in that format.

specify an invalid number with float(“nan”), a number
larger than the largest possible value with float(“inf”), or a number smaller
than the smallest possible value with float(“-inf”).

Decimal stores fixed-point decimal numbers, while Fraction
does the same for fractions
To use either, you’ll need to import them first.

1
2
3
4
5
6
7
8
9
10
11
12
from decimal import Decimal
from fractions import Fraction
third_fraction = Fraction(1, 3)
third_fixed = Decimal("0.333")
third_float = 1 / 3
print(third_fraction) # 1/3
print(third_fixed) # 0.333
print(third_float) # 0.3333333333333333
third_float = float(third_fraction)
print(third_float) # 0.3333333333333333
third_float = float(third_fixed)
print(third_float) # 0.333
1
2
3
4
5
6
7
8
9
10
print(-42) # negative (unary), evaluates to -42
print(abs(-42)) # absolute value, evaluates to 42
print(40 + 2) # addition, evaluates to 42
print(44 - 2) # subtraction, evaluates to 42
print(21 * 2) # multiplication, evaluates to 42
print(680 / 16) # division, evaluates to 42.5
print(680 // 16) # floor division (discard remainder), evaluates to 42
print(1234 % 149) # modulo, evaluates to 42
print(7 ** 2) # exponent, evaluates to 49
print((9 + 5) * 3) # parentheses, evaluates to 42

Python offers augmented
assignment operators, sometimes informally called compound assignment
operators. These allow you to perform an operation with the current value
of the variable as the left operand.

1
2
3
4
5
6
7
8
foo = 10
foo += 10 # value is now 20 (10 + 10)
foo -= 5 # value is now 15 (20 – 5)
foo *= 16 # value is now 240 (15 * 16)
foo //= 5 # value is now 48 (240 // 5)
foo /= 4 # value is now 12.0 (48 / 4)
foo **= 2 # value is now 144.0 (12.0 ** 2)
foo %= 51 # value is now 42.0 (144.0 % 15)

the divmod() function to efficiently perform the calculation, returning the two results in a tuple. Thus, c = divmod(a, b) is the
same as c = (a // b, a % b).

bitwise operators

1
2
3
4
5
6
print(9 & 8) # bitwise AND, evaluates to 8
print(9 | 8) # bitwise OR, evaluates to 9
print(9 ^ 8) # bitwise XOR, evaluates to 1
print(~8) # unary bitwise ones complement (flip), evaluates to -9
print(1 << 3) # bitwise left shift, evaluates to 8
print(8 >> 3) # bitwise right shift, evaluates to 1

the math module

1
2
3
4
5
6
7
8
9
import math
print(math.pi) # PI 3.141592653589793
print(math.tau) # TAU 6.283185307179586
print(math.e) # Euler's number 2.718281828459045
print(math.inf) # Infinity
print(math.nan) # Not-a-Number
infinity_1 = float('inf')
infinity_2 = math.inf
print(infinity_1 == infinity_2) # prints True
1
2
3
4
5
6
7
8
9
10
import math
distance_ft = 65 # the distance to the object
angle_deg = 74 # the angle to the top of the object
# Convert from degrees to radians
angle_rad = math.radians(angle_deg)
# Calculate the height of the object
height_ft = distance_ft * math.tan(angle_rad)
# Round to one decimal place
height_ft = round(height_ft, 1)
print(height_ft) # outputs 226.7

Comparison Operators

1
2
3
4
5
6
7
8
score = 98
high_score = 100
print(score == high_score) # equals, evaluates to False
print(score != high_score) # not equals, evaluates to True
print(score < high_score) # less than, evaluates to True
print(score <= high_score) # less than or equals, evaluates to True
print(score > high_score) # greater than, evaluates to False
print(score >= high_score) # greater than or equals, evaluates to False

Boolean, None, and Identity Operators

Take particular note of the last condition, eggs is spam which illustrates an important gotcha with the is operator. It actually compares the identity of the
variables, rather than the value

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
spam = True
eggs = False
potatoes = None
if spam is True: # Evaluates to True
print("We have spam.")
if spam is not False: # Evaluates to True
print("I DON'T LIKE SPAM!")
if spam: # Implicitly evaluates to True (preferred)
print("Spam, spam, spam, spam...")
if eggs is False: # Evaluates to True
print("We're all out of eggs.")
if eggs is not True: # Evaluates to True
print("No eggs, but we have spam, spam, spam, spam...")
if not eggs: # Implicitly evaluates to True (preferred)
print("Would you like spam instead?")
if potatoes is not None: # Evaluates to False (preferred)
print("Yum") # We never reach this...potatoes is None!
if potatoes is None: # Evaluates to True (preferred)
print("Yes, we have no potatoes.")
if eggs is spam: # Evaluates to False (CAUTION!!!)
print("This won't work.")

Truthiness

Most expressions and values in Python can be evaluated to a True or False
value. This is typically done by using the value as an expression by itself,
although you can also pass it to the bool() function to convert it explicitly.

1
2
3
4
answer = 42
if answer:
print("Evaluated to True") # this runs
print(bool(answer)) # prints True

When an expression will evaluate to True, it is considered “truthy.”
When it will evaluate to False, it is “falsey.”

The None constant, values representing zero, and empty collections are all considered “falsey,” while most
other values are “truthy.”

Logical Operators

1
2
3
4
5
6
7
8
spam = True
eggs = False
if spam and eggs: # AND operator, evaluates to False
print("I do not like green eggs and spam.")
if spam or eggs: # OR operator, evaluates to True
print("Here's your meal.")
if (not eggs) and spam: # NOT (and AND) operators, evaluates to True
print("But I DON'T LIKE SPAM!")
1
2
3
4
score = 98
high_score = 100
print(score != high_score) # not equals operator, evaluates to True
print(not score == high_score) # not operator, evaluates to True

The condition employing the != operator is preferred for readability.

The Walrus Operator

Python 3.8 introduced assignment expressions, which allow you to assign a
value to a variable and use that variable in another expression at the same
time. This is possible with the so-called walrus operator (:=).

1
2
3
if (eggs := 7 + 5) == 12:
print("We have one dozen eggs")
print(eggs) # prints 12

Python first evaluates the expression on the
left (7+5) and then assigns it to the variable eggs.
The assignment expression is then evaluated to a single value, namely
the value of eggs, which is used in the comparison

Two particularly useful style rules are put forth
by this PEP:
• If either assignment statements or assignment expressions can be used,
then prefer statements; they are clear declarations of intent.
• If using assignment expressions would lead to ambiguity about execution order, then restructure to use statements instead.

Strings

String Literals

The only time you’d really need to escape either single or double
quotes with backslashes would be if you had both types of quotes in the
string at once:
question = "What do you mean, \"it's fine\"?"

triple quotes define multiline string literals. In other
words, I can use them to do this:

1
2
3
4
5
6
7
8
9
parrot = """\
This parrot is no more!
He has ceased to be!
He's expired
and gone to meet his maker!
He's a stiff!
Bereft of life,
he rests in peace!"""
print(parrot)

The only exception occurs when you use a backslash (\) to escape
particular character, like I did with that newline at the beginning 1. It is
conventional to escape the first newline after the opening triple quotes, just
to make the code look cleaner.

concatenate (combine) string literals, simply by
writing them next to one another, without any operators between them. For
example, spam = “Hello “ “world” “!” is valid, resulting in the string Hello
world!.

Raw Strings

Raw strings constitute another form of string literal, wherein the backslash
(\) is always treated as a literal character. They’re preceded with an r, such
as in this example:
print(r"I love backslashes: \ Aren't they cool?")

Formatted Strings

1
2
3
in_stock = 0
print("This cheese shop has " + str(in_stock) + " types of cheese.")
print(f"This cheese shop has {in_stock} types of cheese.")

Instead, you’d have to
evaluate that expression in advance, assign the result to a name, and use it
in the f-string.

The ord() function returns the number representing the unicode code of a specified character.

print(f”{ord(‘\n’)}”) # SyntaxError

newline_ord = ord(‘\n’)
print(f”{newline_ord}”) # prints “10”

Format Specifications

Immediately after the expression, you may choose to include one of
three special flags: !r, !a, or !s (although that last one is the default behavior, so it can be omitted in most cases). These determine which function
is used to fetch the string representation of some value: repr(), ascii(), or
str(), respectively

Next comes the format specification itself, which always begins with a
colon (:), followed by one or more flags. These have to be specified in a particular order to work, although any of them may be omitted if they’re not
desired:

Align An alignment flag, specifying left (<), right (>), center (^), or (if
numeric) split with the sign aligned left but the digits aligned right (=).
This is optionally preceded by a character that will be used to fill any
blank space in the alignment.
Sign A flag controlling when the sign is displayed on a number. The
plus (+) flag displays the sign on both positive and negative numbers,
while the minus (–) flag only displays it on negative numbers. A third
option is to show a leading space on positive numbers and a sign on
negative numbers (SPACE).
Alternative form The hash (#) flag turns on the “alternative form,”
which has different meanings for different types (see documentation).
Leading zeros The zero (0) flag causes leading zeros to be displayed
(unless a fill character is specified for alignment).
Width The width of the output string in characters. This is where the
alignment comes into play.
Grouping A flag controlling whether numbers should separate thousands with a comma (,) or an underscore (_). If omitted, no separator
is used. If enabled, the underscore separator also appears every four
digits in octal, hexadecimal, and binary numbers.
Precision A dot (.), followed by an integer for decimal precision
Type A flag controlling how numbers are displayed; common options
include binary (b), character (c), decimal (d), hexadecimal (x), exponent notation (e), fixed-point (f), and general (g). There are more (see
documentation).

1
2
3
4
5
6
spam = 1234.56789
print(f"{spam:=^+15,.2f}") # prints "===+1,234.57==="
spam = 42
print(f"{spam:#07x}") # prints "0x0002a"
spam = "Hi!"
print(f"{spam:-^20}") # prints "--------Hi!---------"

String Conversion

str(), is the one you’ll use most often, as it returns
the human-readable representation of the value.

repr() returns the canonical string representation of the value:
that is, (usually) the value as Python sees it. In the case of many basic data
types, this will return the same thing as str(), but when used on most
objects, the output contains additional information useful in debugging.

The ascii() function is the same as repr(), except that the string literal
it returns is completely ASCII-compatible, having escaped any non-ASCII
(for example, Unicode) characters.

A Note on String Concatenation

Typical concatenation with + or the join() function has the same result,
but the latter function will be as fast or faster, especially when you’re using
other implementations of Python besides CPython. Therefore, whenever
you need to concatenate and f-strings aren’t right for the job, you should
consider using join() instead of the + or += operators. In practice, f-strings
are the fastest, but join() is your next-best option.

1
2
3
4
5
6
7
8
9
greeting = "Hello"
name = "Jason"

message = greeting + ", " + name + "!" # value is "Hello, Jason!"
print(message)

message = "".join((greeting, ", ", name, "!")) # value is "Hello, Jason!"
print(message)

Functions 0

Python functions are first-class citizens, which means they can be treated like
any other object

Classes and Objects 0

Error Handling 0

1
2
3
4
5
6
7
num_from_user = input("Enter a number: ")
try:
num = int(num_from_user)
except ValueError:
print("You didn't enter a valid number.")
num = 0
print(f"Your number squared is {num**2}")

Tuples and Lists

Lists constitute the most array-like collection in Python. In CPython,
they are implemented as variable-length arrays

A tuple is somewhat similar to a list, but with a few key differences.
First, a tuple cannot have items added, reassigned, or removed after its creation. Attempting to modify the contents of a tuple with bracket notation
will result in a TypeError. This is because tuples, unlike lists, are immutable, effectively meaning their contents cannot be modified

The guideline is to use tuples for collections of items of different types
(heterogeneous collections) and to use lists for collections of items of the same
type (homogeneous collections).

Loops

Structural Pattern Matching

Python 3.10 gained structural pattern matching via PEP 634.
This provides conditional logic that is at least syntactically similar to switch
statements of other languages. In short, you test a single subject, such as a
variable, against one or more patterns. If the subject matches the pattern,
the associated suite of code runs.

lunch_order = input(“What would you like for lunch? “)
match lunch_order:
case ‘pizza’:
print(“Pizza time!”)
case ‘sandwich’:
print(“Here’s your sandwich”)
case ‘taco’:
print(‘Taco, taco, TACO, tacotacotaco!’)
case _:
print(“Yummy.”)

The underscore (_) in the last case is the wildcard, which will match any
value. This serves as a fallback case, and it must come last, as it will match
anything.

PROJECT STRUCTURE AND IMPORTS

Setting Up the Repository

Your Python code belongs in a separate subdirectory and not in the
root of the repository. This is very important, as your repository’s root directory will get mighty cluttered with build files, packaging scripts, documentation, virtual environments, and all manner of other things that aren’t
actually part of the source code.

Modules and Packages

A module is any Python (.py) file.

A package, occasionally called a regular package, is one or more modules within a directory. That directory must include a file called __init__
.py (which can be empty). The __init__.py file is important! If it isn’t there,
Python will not know the directory constitutes a package.

PEP 8 and Naming

Modules should have short, all-lowercase names. Underscores can
be used in the module name if it improves readability. Python
packages should also have short, all-lowercase names, although
the use of underscores is discouraged.

Do this: omission/data/data_loader.py
NOT this: omission/Data/DataLoader.py

another special file in my top-level package: __main__.py. This
is the file that runs when I execute my top-level package directly, via this
command:

Import Dos and Don’ts

1
2
3
4
5
6
7
8
#!/usr/bin/env python3
def open():
print("Ahhhhhhhhhhhhhh.")
def close():
print("Thank you for making a simple door very happy.")
import smart_door
smart_door.open()
smart_door.close()

The namespace of open() and close() is smart_door. A namespace is an
explicitly defined path to something, such as a function. The function
open() has the namespace smart_door, which tells me that open() belongs to
that particular module.

Importing Within Your Project

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
omission-git/
└── omission/
├── __init__.py
├── __main__.py
├── app.py
├── common/
│ ├── __init__.py
│ ├── classproperty.py
│ ├── constants.py
│ └── game_enums.py
├── data/
│ ├── __init__.py
│ ├── data_loader.py
│ ├── game_round_settings.py
│ ├── scoreboard.py
│ └── settings.py

Listing 4-8: Directory structure of omission-git/

Absolute Imports

I have a class GameMode defined within the game_enums.py module, which
lives in the omission/common package. I want to use that class within my
game_round_settings.py module, defined in the omission/data package

Because I defined omission as a top-level package and organized my modules into subpackages, it’s pretty straightforward. In game_round_settings.py,
I’d write the following:

from omission.common.game_enums import GameMode

This line is an absolute import. It starts at the top-level package, omission,
and walks down into the common package

Relative Imports

You can also import from a module in the same package or subpackage.
This is known as a relative import or an intrapackage reference.

A module knows what package it belongs to, and a package knows its
parent package (if it has one). Because of this, relative imports can start the
search from the current package and move up and down through the project structure.

Within omission/data/game_round_settings.py, I can use the following
import statement:

from ..common.game_enums import GameMode

Listing 4-11: game_round_settings.py:1c

The two dots (..) mean “the current package’s direct parent package,”
which, in this case, is omission

Importing from the Same Package

In omission/data/settings.py, I have
this statement for importing a class from the module omission/data/game
_round_settings.py:

from omission.data.game_round_settings import GameRoundSettings

from game_round_settings import GameRoundSettings

However, that will not work. It will fail to locate the game_round_settings
.py module because I am running the top-level package (python3 -m omission),
and absolute imports for anything within the package being executed
(omission) have to start from the top.

use a relative import, which looks much simpler than the
absolute import:

from .game_round_settings import GameRoundSettings

Entry Points

The parts of the project that are run first when importing or
executing are called entry points

Module Entry Points

When you import a Python module or package, it is given a special variable
called __name__. This contains the fully qualified name of the module or package, which is the name as the import system sees it. For example, the fully
qualified name of the module omission/common/game_enums.py would be
omission.common.game_enums.

when a module or package is run directly, its __name__ is set to the value “__main__“.

a package called testpkg, which contains the module awesome.py. It defines a function, greet():

1
2
3
def greet():
print("Hello, world!")
print("Awesome module was run.")

In another module in the same directory as testpkg, I have this module
(example.py), which I run directly with python3 example.py:

1
2
3
from testpkg import awesome
print(__name__) # prints "__main__"
print(awesome.__name__) # prints "testpkg.awesome"
1
2
3
Awesome module was run.
__main__
testpkg.awesome

what if I want that first message to appear only if awesome.py is executed directly, and not when the module is only imported? To accomplish
that, I’d check the value of __name__ variable in a conditional statement. I’ve
rewritten my awesome.py file to do exactly that:

1
2
3
4
def greet():
print("Hello, world!")
if __name__ == "__main__":
print("Awesome module was run.")

Package Entry Points

omission project has a file called __main__ in the top-level
package. This file is automatically run when a package is executed directly,
but never when importing the package

So, when executing omission via python3 -m omission, Python first runs
the __init__.py module (as always), followed by its __main__.py module.
Otherwise, if the package is imported instead, only __init__.py is executed.

A good __main__.py for a top-level package would look something like
the following:

1
2
3
4
def main():
# Code to start/run your package.
if __name__ == "__main__":
main()

Program Entry Points

However, you (or your eventual end user) may want to run the program
merely by double-clicking or directly executing some single Python file.
With everything else in place, this is trivial to implement.

To make my omission project easy to run, I created a single script file
outside the top-level package, named omission.py:

1
2
from omission.__main__ import main
main()

VARIABLES AND TYPES

Variables According to Python: Names and Values

A name refers to a value or an object
. A value is a particular instance of data in memory
The term variable refers to the combination of the two: a name that refers to a value.

answer = 42
insight = answer

insight is not bound to answer, but rather to the same
value that answer was already bound to when I assigned insight. A name
always points to a value.

is
doesn’t check whether a name points to equivalent values, but rather whether
it points to the same value in memory.

When you make an assignment, Python makes its own decisions behind
the scenes about whether to create a new value in memory or bind to an
existing value

spam = 123456789
maps = spam
eggs = 123456789

print(spam == maps) # prints True
print(spam == eggs) # prints True

print(spam is maps) # prints True
print(spam is eggs) # prints False (probably)

(Interactive session)

answer = 42
insight = 42
print(answer is insight) # prints True

The is operator checks identity. Unless you really know what you’re doing,
only use this to check if something is None.>

the built-in function id() returns an integer representing the identity of whatever is passed to it.

Data Types

Names are bound to values, and those values exist in memory, as long as
there is some reference to them. You can bind a name to literally any value you
want, but you are limited as to what you can do with any particular value.

The type() Function

print(type(answer)) # prints <class 'int'>

if type(answer) is int:
print(“What’s the question?”)

use isinstance() instead of type(), as it accounts for subclasses and
inheritance

if isinstance(answer, int):
print(“What’s the question?”)

Duck Typing

If it looks like a duck, walks like a duck, and quacks like a duck,
then it probably is a duck.

Python doesn’t care much about what a value’s data type is, but rather
it cares about the functionality of the value’s data type.

If your class behaves as it
should, it usually won’t matter what it inherits from.

Scope and Garbage Collection

Scope is what defines where a variable can be accessed from. It might be
available to an entire module or limited to the suite (body) of a function.

A
name can be global, meaning it is defined by itself in a module, or it can be
local, meaning it only exists within a particular function or comprehension.

Local Scope and the Reference-Counting Garbage Collector

Functions (including lambdas) and comprehensions define their own
scope; they are the only structures in the language to do so.

When a scope reaches its end, all the names defined
within it are automatically deleted.

. Modules and
classes don’t have their own scope in the strictest sense; they only have
their own namespace.

loops don’t have their own scope.

a reference count, which is simply
a count of how many references exist for that value. Every time a value is
bound to a name, a reference is created (although there are other ways the
language may create references). When there are no more references, the
value is deleted. This is the reference-counting garbage collector, and it efficiently
handles most garbage collection scenarios.

def spam():
message = “Spam”
word = “spam”
for _ in range(100):
separator = “, “
message += separator + word
message += separator
message += “spam!”
return message

output = spam()
print(output)
even though the name message is deleted when spam() exits, the value is not.

Interpreter Shutdown

When the Python interpreter is asked to shut down, such as when a Python
program terminates, it enters interpreter shutdown. During this phase, the interpreter goes through the process of releasing all allocated resources, calling
the garbage collector multiple times, and triggering destructors in objects.

Global Scope

When a name is defined within a module but outside of any function, class,
or comprehension, it is considered to be in global scope

using
the global high_score name. This means that anywhere I assign a value to the
name high_score in score(), the function will use the global name, instead of
trying to create a new local name.

If you’re only accessing the current
value bound to a global name, you don’t need to use the global keyword.

The Dangers of Global Scope

The nonlocal Keyword

trying to assign to a global
name will actually define a new local name that shadows the global one.

The same behavior is true of the inner function using names defined in
the outer function, which is known as the nested scope or enclosing scope.

To
get around this, I specify that eggs is nonlocal,

The nonlocal keyword starts looking for the indicated name in the innermost nested scope, and if it doesn’t find it, it moves to the next enclosing
scope above that. It repeats this until it either finds the name or determines
that the name does not exist in a nonglobal enclosing scope.

Scope Resolution

which scopes it searches for a name, and in what order,
is called the scope resolution order

the acronym LEGB

Local
Enclosing-function locals (that is, anything found via nonlocal)
Global
Built-in

The Curious Case of the Class

Every name
declared directly within a class is known as an attribute, and it is accessed
through the dot (.) operator on the class (or object) name.

The Immutable Truth

Values in Python can be either immutable or mutable. The difference hinges
on whether the values can be modified in place, meaning they can be changed
right where they are in memory.

Immutable types cannot be modified in place. For example, integers (int),
floating-point numbers (float), strings (str), and tuples (tuple) are all immutable.

If you attempt to mutate an immutable value, you’ll wind up with a
completely different value being created:

eggs = 12
carton = eggs
print(eggs is carton) # prints True
eggs += 1
print(eggs is carton) # prints False
print(eggs) # prints 13
print(carton) # prints 12

Mutable types, on the other hand, can be modified in place.
Lists constitute one example of a mutable type:

temps = [87, 76, 79]
highs = temps
print(temps is highs) # prints True
temps += [81]
print(temps is highs) # prints True
print(highs) # prints [87, 76, 79, 81]
print(temps) # prints [87, 76, 79, 81]