More on this book
Community
Kindle Notes & Highlights
Started reading
May 16, 2024
Pytype, for example, is designed to handle codebases with no type hints and still provide useful advice. It is more lenient than Mypy, and can also generate annotations for your code.
There are many definitions of the concept of type in the literature. Here we assume that type is a set of values and a set of functions that one can apply to these values.
In practice, it’s more useful to consider the set of supported operations as the defining characteristic of a type.
duck typing is easier to get started and is more flexible, but allows unsupported operations to cause errors at runtime. Nominal typing detects errors before runtime, but sometimes can reject code that actually runs—such
The keystone of any gradual type system is the Any type, also known as the dynamic type.
More general types have narrower interfaces, i.e., they support fewer operations. The object class implements fewer operations than abc.Sequence, which implements fewer operations than abc.MutableSequence, which implements fewer operations than list.
Traditional object-oriented nominal type systems rely on the is subtype-of relationship. Given a class T1 and a subclass T2, then T2 is subtype-of T1.
Barbara Liskov7 actually defined is subtype-of in terms of supported operations: if an object of type T2 substitutes an object of type T1 and the program still behaves correctly, then T2 is subtype-of T1.
In a gradual type system, there is another relationship: consistent-with, which applies wherever subtype-of applies, with special provisions for type Any.
Every gradual type system needs a wildcard type like Any.
If possible, avoid creating functions that return Union types, as they put an extra burden on the user—forcing them to check the type of the returned value at runtime to know what to do with it.
Union is more useful with types that are not consistent among themselves. For example: Union[int, float] is redundant because int is consistent-with float. If you just use float to annotate the parameter, it will accept int values as well.
Be conservative in what you send, be liberal in what you accept. Postel’s law, a.k.a. the Robustness Principle
in general it’s better to use abc.Mapping or abc.MutableMapping in parameter type hints, instead of dict (or typing.Dict in legacy code).
Generic version of list. Useful for annotating return types. To annotate arguments it is preferred to use an abstract collection type such as Sequence or Iterable.
A restricted type variable will be set to one of the types named in the TypeVar declaration.
A bounded type variable will be set to the inferred type of the expression—as long as the inferred type is consistent-with the boundary declared in the bound= keyword argument of TypeVar.
AnyStr is used in many functions that accept either bytes or str, and return values of the given type.
in the context of type hints, a protocol is a typing.Protocol subclass defining an interface that a type checker can verify.
A type T is consistent-with a protocol P if T implements all the methods defined in P, with matching type signatures.
A key advantage of a protocol type over ABCs is that a type doesn’t need any special declaration to be consistent-with a protocol type.
Some handy features can’t be statically checked; for example, argument unpacking like config(**settings).
Advanced features like properties, descriptors, metaclasses, and metaprogramming in general are poorly supported or beyond comprehension for type checkers.
Type checkers lag behind Python releases, rejecting or even crashing while analyzing code with new language features—f...
This highlight has been truncated due to consecutive passage length restrictions.
In general, type hints are not helpful to catch errors in business logic.
“If a Python program has adequate unit tests, it can be as robust as a C++, Java, or C# program with adequate unit tests (although the tests in Python will be faster to write).”
Linguistic relativity could explain the widespread idea (also unproven) that learning different programming languages makes you a better programmer, particularly when the languages support different programming paradigms. Practicing Elixir made me more likely to apply functional patterns when I write Python or Go code.
The requests package would probably have a very different API if Kenneth Reitz was determined (or told by his boss) to annotate all its functions. His goal was to write an API that was easy to use, flexible, and powerful. He succeeded, given the amazing popularity of requests—in May 2020, it’s #4 on PyPI Stats, with 2.6 million downloads a day. #1 is urllib3, a dependency of requests.
REPL stands for Read-Eval-Print-Loop, the basic behavior of interactive interpreters.
Function decorators let us “mark” functions in the source code to enhance their behavior in some way. This is powerful stuff, but mastering it requires understanding closures—which is what we get when functions capture variables defined outside of their bodies.
A decorator is a callable that takes another function as an argument (the decorated function).
A decorator may perform some processing with the decorated function, and returns it or replaces it with another function or callable object.
A decorator is a function or another callable. A decorator may replace the decorated function with a different one. Decorators are executed immediately when a module is loaded.
function decorators are executed as soon as the module is imported, but the decorated functions
The decorator function is defined in the same module as the decorated functions. A real decorator is usually defined in one module and applied to functions in other modules.
The register decorator returns the same function passed as an argument. In practice, most decorators define an inner function and return it.
Most decorators do change the decorated function. They usually do it by defining an inner function and returning it to replace the decorated function. Code that uses inner functions almost always depends on closures to operate correctly.
But the fact is, when Python compiles the body of the function, it decides that b is a local variable because it is assigned within the function. The generated bytecode reflects this decision and will try to fetch b from the local scope. Later, when the call f2(3) is made, the body of f2 fetches and prints the value of the local variable a, but when trying to fetch the value of local variable b, it discovers that b is unbound.
If we want the interpreter to treat b as a global variable and still assign a new value to it within the function, we use the global declaration:
Load local name b. This shows that the compiler considers b a local variable, even if the assignment to b occurs later, because the nature of the variable—whether it is local or not—cannot change in the body of the function.
Actually, a closure is a function—let’s call it f—with an extended scope that encompasses variables referenced in the body of f that are not global variables or local variables of f. Such variables must come from the local scope of an outer function that encompasses f.
Within averager, series is a free variable. This is a technical term meaning a variable that is not bound in the local scope.
The closure for averager extends the scope of that function to include the binding for the free variable series.
a closure is a function that retains the bindings of the free variables that exist when the function is defined, so that they can be used later when the function is invoked and the defining scope is no longer available.
the only situation in which a function may need to deal with external variables that are nonglobal is when it is nested in another function and those variables are part of the local scope of the outer function.
the nonlocal keyword was introduced in Python 3. It lets you declare a variable as a free variable even when it is assigned within the function. If a new value is assigned to a nonlocal variable, the binding stored in the closure is changed.
If there is a global x declaration, x comes from and is assigned to the x global variable module.
If there is a nonlocal x declaration, x comes from and is assigned to the x local variable of the nearest surrounding function where x is defined.
If x is a parameter or is assigned a value in the function body, then x is the local variable.
If x is referenced but is not assigned and is not a parameter: x will be looked up in the local scopes of the surrounding function bodies (nonlocal scopes). If not found in surrounding scopes, it will be read from the module global scope. If not fou...
This highlight has been truncated due to consecutive passage length restrictions.