4 Comprehensions and Generators
Last edited
i28 multiple ifs in a comprehension
# You can have a comprehension eval multiple ifs
# the same sort of output as `and` would have, all must be true
b = [x for x in a if x > 4 if x % 2 == 0 if x == 6]
c = [x for x in a if x > 4 and x % 2 == 0]
>>> b
[6]
>>> c
[6, 8, 10]33 Chain generators with yield from
def letters():
yield 'a'
yield 'b'
def numbers():
yield 1
yield 2
# Bad: nested for loops
def combined_manual():
for x in letters():
yield x
for x in numbers():
yield x
# Good: yield from
# This just exausts the genererators same thing thats happening
# above.
def combined():
yield from letters()
yield from numbers()
print(list(combined()))
>>> ['a', 'b', 1, 2]37 Compose classes instead of nesting builtin-types
This is a walk through of how you go from using simple dicts to a full class for managing data.
start with a
dictfor key/values◇ When to move on..
- once you have a dict in a dict
collections.namedtuple- you get to name attributes thus stablizing the API for a future class
- no types
- no default attributes
- no mutables
- cant control init or repr
◇ When to move on..
- once you need types or default attributes
typing.NamedTuple- default attributes
- types
- some of the same issues as above
◇ When to move on..
- once you need the missing parts of tuples resource: https://peps.python.org/pep-0557/#why-not-just-use-namedtuple
@dataclassWhen to move on..
- you want real control
__init__at construction time (__post_init__only patches the tail end). - args that don’t map 1:1 to storage.
- or the class is more behavior than data so dataclasses’ default
__eq__is wrong/irrelevant.
- you want real control
class
38 Acccept functions instead of classes for simple interfaces
Question
What does
defdo?
Answer
defcreates a function object, storing the compiled body in code. The object is callable because its type(types.FunctionType)defines call, which inturn calls__code__So if want to make something that isn’t a function ‘callable’ we just need to meet the interface of
__call__
We all know you can pass a generic function into a function for it to be run.
The point of this is you can pass in a class AND STORE STATE.
class BetterCountMissing:
def __init__(self):
self.added = 0
def __call__(self):
self.added += 1
return 0
counter = BetterCountMissing()
assert counter() == 0
assert callable(counter)counter = BetterCountMissing()
result = defaultdict(counter, current) # Relies on __call__
for key, amount in increments:
result[key] += amount
assert counter.added == 2So we emulated a function to meet the interface and snuck in our state tracking.