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.

  1. start with a dict for key/values

    ◇ When to move on..

    • once you have a dict in a dict
  2. 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
  3. typing.NamedTuple

    • default attributes
    • types
    • some of the same issues as above

    ◇ When to move on..

  4. @dataclass

    When 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.
  5. class

38 Acccept functions instead of classes for simple interfaces

Question

What does def do?

Answer

def creates 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 == 2

So we emulated a function to meet the interface and snuck in our state tracking.