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