Twitter tidbits

These are tidbits I’ve tweeted. The tweets include code as images, both so they will fit, and to be more visually appealing. I’ve included the text here inline to make it easier to find things.

dict.get

>>> # How did each student do on the test?
>>> scores = {
...     'joe': 85,
...     'jane': 90,
...     'alex': 80,
...     'beth': 82,
... }

>>> students = ['beth', 'alex', 'jane', 'joe']

>>> # Sort students by their scores, highest first:
>>> sorted(students, key=scores.get, reverse=True)
['jane', 'joe', 'beth', 'alex']

Tuples as dict keys

>>> board = {}
>>> for x, y in [(1,0), (2,1), (0,2), (1,2), (2,2)]:
...     board[x, y] = "#"

>>> def min_max(nums):
...     return min(nums), max(nums)

>>> def print_board(board):
...     minx, maxx = min_max([x for x, _ in board])
...     miny, maxy = min_max([y for _, y in board])
...     for y in range(miny - 1, maxy + 2):
...         for x in range(minx - 1, maxx + 2):
...             print(board.get((x, y), "·"), end="")
...         print()

>>> print_board(board)
·····
··#··
···#·
·###·
·····
>>> # Let's add more to the board..
>>> board[20, 3] = "$"
>>> print_board(board)
·······················
··#····················
···#···················
·###···················
·····················$·
·······················

>>> # And even more:
>>> for x, c in enumerate("@nedbat", start=20):
...     board[x, 3] = c
>>> print_board(board)
·····························
··#··························
···#·························
·###·························
·····················@nedbat·
·····························

>>> # Negative coordinates are ok:
>>> board[-10, -3] = "X"
>>> print_board(board)
·······································
·X·····································
·······································
·······································
············#··························
·············#·························
···········###·························
·······························@nedbat·
·······································

Mutable default values

>>> # Default values are shared between calls:
>>> def make_list(v, the_list=[]):
...     the_list.append(v)
...     return the_list

>>> make_list(12)
[12]                    # nice...
>>> make_list(47)
[12, 47]                # what!?  😭
>>> make_list("oh no")
[12, 47, 'oh no']       # WTF???  😡 🤬

>>> # Use None as the default instead:
>>> def make_list(v, the_list=None):
...     if the_list is None:
...         the_list = []
...     the_list.append(v)
...     return the_list

>>> make_list(12)
[12]    # ok...
>>> make_list(47)
[47]    # YES!  😎 🥳 😍

itertools.product

>>> import itertools, collections

>>> for x, y in itertools.product([-1, 1], [-1, 1]):
...     print(x, y)
-1 -1
-1 1
1 -1
1 1

>>> # All possible rolls of three dice.
>>> rolls = itertools.product(range(1, 7), repeat=3)
>>> tally = collections.Counter(sum(t) for t in rolls)
>>> for total in sorted(tally):
...     occurs = tally[total]
...     print(f"{total:3d}{occurs:3d} {'░'*occurs}▌")

  3:   1 ░▌
  4:   3 ░░░▌
  5:   6 ░░░░░░▌
  6:  10 ░░░░░░░░░░▌
  7:  15 ░░░░░░░░░░░░░░░▌
  8:  21 ░░░░░░░░░░░░░░░░░░░░░▌
  9:  25 ░░░░░░░░░░░░░░░░░░░░░░░░░▌
 10:  27 ░░░░░░░░░░░░░░░░░░░░░░░░░░░▌
 11:  27 ░░░░░░░░░░░░░░░░░░░░░░░░░░░▌
 12:  25 ░░░░░░░░░░░░░░░░░░░░░░░░░▌
 13:  21 ░░░░░░░░░░░░░░░░░░░░░▌
 14:  15 ░░░░░░░░░░░░░░░▌
 15:  10 ░░░░░░░░░░▌
 16:   6 ░░░░░░▌
 17:   3 ░░░▌
 18:   1 ░▌

fileinput

% bat upper.py
 ─────┬─────────────────────────────────────────────────────────────────────────────────────────────────────
    1 │ # Convert text to upper case.
    2 │
    3 │ import fileinput
    4 │
    5 │ for line in fileinput.input(inplace=True):
    6 │     line = line.upper()
    7 │     print(line, end="")
 ─────┴─────────────────────────────────────────────────────────────────────────────────────────────────────

% echo "Hello there" | python upper.py
HELLO THERE

% echo "Four score and seven years" > getty.txt

% echo "Lorem ipsum dolor sit amet" > lorem.txt

% python upper.py *.txt

% bat *.txt
 ─────┬─────────────────────────────────────────────────────────────────────────────────────────────────────
    1 │ FOUR SCORE AND SEVEN YEARS
 ─────┴─────────────────────────────────────────────────────────────────────────────────────────────────────
 ─────┬─────────────────────────────────────────────────────────────────────────────────────────────────────
    1 │ LOREM IPSUM DOLOR SIT AMET
 ─────┴─────────────────────────────────────────────────────────────────────────────────────────────────────

git squash

# ~/.gitconfig
[rebase]
autostash = true    # stash/unstash work in progress
autosquash = true   # auto-squash marked commits

[alias]
# rebase without opening the editor
squash = "!f() { \
    : git show ; \
    git -c sequence.editor=: rebase -i ${1:-@~10}; \
}; f"

*map()

>>> from pprint import pprint

>>> def twitter(nick): return f"https://twitter.com/@{nick}"

>>> def github(user): return f"https://github.com/{user}"

>>> def pypi(proj): return f"https://pypi.org/project/{proj}"

>>> links = [
...    *map(twitter, ["nedbat", "coveragepy"]),
...    github("nedbat"),
...    *map(pypi, ["coverage", "dinghy", "scriv", "cog"]),
... ]

>>> pprint(links)
['https://twitter.com/@nedbat',
 'https://twitter.com/@coveragepy',
 'https://github.com/nedbat',
 'https://pypi.org/project/coverage',
 'https://pypi.org/project/dinghy',
 'https://pypi.org/project/scriv',
 'https://pypi.org/project/cog']

“or” can be misleading

# This seems like it would work..
answer = input("Should I?")
if answer == "y" or "yes":
    print("OK, I will")

# But it doesn't!
>>> for answer in ["y", "no 😭", "please stop 😡", "HECK NO! 🤬"]:
...     if answer == "y" or "yes":
...         print(f"OK, since you said: {answer}")
OK, since you said: y
OK, since you said: no 😭
OK, since you said: please stop 😡
OK, since you said: HECK NO! 🤬

# If the left arg is false, "or" returns its right arg:
>>> for answer in ["y", "no", "please stop", "HECK NO!"]:
...     print(f"{answer = :15}:", (answer == "y" or "yes"))
answer = y              : True
answer = no             : yes
answer = please stop    : yes
answer = HECK NO!       : yes

# This does what you want:
>>> for answer in ["y", "no", "please stop", "HECK NO!"]:
...     # another way: answer in ["y", "yes"]:
...     if answer == "y" or answer == "yes":
...         print(f"OK, since you said: {answer}")
OK, since you said: y

“in” checks identity first

>>> class Grumpy:
...     # I'm not equal to anything, even myself!
...     def __eq__(self, other):
...         return False

>>> g = Grumpy()
>>> g is g      # Identity: same object?
True
>>> g == g      # Equality: the object decides.
False

>>> # "in" checks identity before equality:
>>> g in [g]
True
>>> g in ["Doc", "Happy", g, "Sleepy", "Sneezy"]
True

Rolling bytes from bits

>>> def rolling_bytes(bits):
...     """Bytes from a rolling window over bits."""
...     byte = 0
...     for bit in bits:
...         byte = ((byte << 1) | bit) & 0xFF
...         yield byte

>>> bits = [random.choice([0, 1]) for _ in range(18)]
>>> print(bits)
[1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1]

>>> for byte in rolling_bytes(bits):
...     print(f"0b{byte:08b}: 0x{byte:02x}")
0b00000001: 0x01
0b00000011: 0x03
0b00000110: 0x06
0b00001101: 0x0d
0b00011010: 0x1a
0b00110100: 0x34
0b01101000: 0x68
0b11010000: 0xd0
0b10100001: 0xa1
0b01000011: 0x43
0b10000110: 0x86
0b00001101: 0x0d
0b00011010: 0x1a
0b00110100: 0x34
0b01101000: 0x68
0b11010000: 0xd0
0b10100001: 0xa1
0b01000011: 0x43

Lists can hold any (and different) types

>>> import math
>>> nums = [1, 2, 3]

>>> things = [
...     math,               # a module
...     math.factorial,     # a function
...     int,                # a type
...     nums.append,        # a method
...     nums,               # a list
...     KeyError("Ugh!"),   # an exception
...     KeyError,           # an exception class
... ]

>>> things[0].log10(20)     # math.log10(20)
1.3010299956639813

>>> things[1](6)            # math.factorial(6)
720

>>> things[2](3.14159)      # int(3.14159)
3

>>> things[3](99)           # nums.append(99)
>>> things[4]               # nums
[1, 2, 3, 99]

>>> try:
...     raise things[5]     # KeyError("Ugh!")
... except things[6] as e:  # KeyError
...     print(e)
...
'Ugh!'

defaultdict

>>> # https://en.wikipedia.org/wiki/Gettysburg_Address
>>> speech = """
...     Four score and seven years ago our fathers brought forth
...     on this continent, a new nation, ...
...                     (... 17 lines snipped ...)
...     ... that government of the people, by the people, for the
...     people, shall not perish from the earth.
... """

>>> import collections

>>> initials = collections.defaultdict(set)
>>> for word in speech.split():
...     word = word.lower().strip(".,")
...     initials[word[0]].add(word)

>>> print(initials["n"])
{'nation', 'never', 'new', 'nobly', 'nor', 'not', 'note', 'now'}

>>> print(initials["p"])
{'people', 'perish', 'place', 'poor', 'portion', 'power', 'proper', 'proposition'}

>>> print(initials["d"])
{'dead', 'dedicate', 'dedicated', 'detract', 'devotion', 'did', 'died', 'do'}

strings are iterable

# Map closing bracket to the opening bracket:
openers = dict([")(", "][", "}{"])          # 1

def are_parens_balanced(text: str) -> bool:
    """Determine if paired brackets nest properly."""
    stack = []
    for char in text:                       # 2
        if char in "([{":                   # 3a
            stack.append(char)
        elif char in ")]}":                 # 3b
            if not stack:
                return False
            if stack[-1] != openers[char]:
                return False
            stack.pop()
    return not stack


import pytest

@pytest.mark.parametrize("text, res", [
    ("abcde", True),
    ("()()()", True),
    ("([{}])[()]()[]{{{look}}}", True),
    ("()()(]", False),
    ("]", False),
    ("[", False),
])
def test_it(text, res):
    assert are_parens_balanced(text) == res

__missing__

>>> class FibDict(dict):
...     def __init__(self):
...         self[0] = self[1] = 1
...
...     def __missing__(self, k):
...         fibk = self[k] = self[k-1] + self[k-2]
...         return fibk

>>> fibd = FibDict()
>>> fibd[10]
89

>>> fibd[100]
573147844013817084101

>>> fibd[200]
453973694165307953197296969697410619233826

>>> len(fibd)
201

__innit__

# Tired:
class MyBoringClass:
    def __init__(self):     # 🥱
        ...

# Wired:
class MyExcitedClass:
    def __innit__(self):    # 🤣
        ...

Callable things

>>> def function():
...     return "A function"

>>> class Klass:
...     def __init__(self):
...         print("A constructor")
...
...     def method(self):
...         return "A method"
...
...     def __call__(self):
...         return "An object"

>>> obj = Klass()
A constructor

>>> a_type = list

>>> for callable_thing in [
...     function,
...     lambda: "Another function",
...     Klass,
...     obj.method,
...     obj,
...     a_type,
... ]:
...     print(callable_thing())

A function
Another function
A constructor
<__main__.Klass object at 0x1031f7bb0>
A method
An object
[]