Twitter tidbits
Created 7 May 2022
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
- Tuples as dict keys
- Mutable default values
- itertools.product
- fileinput
- git squash
- *map()
- “or” can be misleading
- “in” checks identity first
- Rolling bytes from bits
- Lists can hold any (and different) types
- defaultdict
- strings are iterable
- __missing__
- __innit__
- Callable things
dict.get
With data in a #Python dict, you can use d.get as a function providing the same mapping: pic.twitter.com/fOs6waf5Cl
— Ned Batchelder (@nedbat) May 7, 2022
>>> # 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
Python dicts can have tuples as keys. Consider a dict with (x,y) keys instead of nested lists for a grid. It simplifies sparse grids, “resizes” are automatic, it doesn’t matter where (0,0) is, and you can use negative positions: pic.twitter.com/6IcJSUHBvl
— Ned Batchelder (@nedbat) May 6, 2022
>>> 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
A classic #python speed-bump: default values for function arguments are computed once, and shared for all calls. If you mutate them, the effect persists! Use None instead: pic.twitter.com/o7wIbUPKer
— Ned Batchelder (@nedbat) May 5, 2022
>>> # 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
Multiple loops can sometimes be replaced by one itertools.product() from the #python stdlib. pic.twitter.com/50b7F4Z7UV
— Ned Batchelder (@nedbat) May 10, 2022
>>> 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
Python’s fileinput module makes it easy to write CLI tools that operate on files or as a filter. https://t.co/XAgcp3EeaR
— Ned Batchelder (@nedbat) May 11, 2022
(BTW, bat: https://t.co/X5GldLbF0e) pic.twitter.com/6pChvWlFrr
% 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
To make squashing in #git easier: make “git rebase -i” automatically stash changes and squash marked commits. Then a “squash” alias that skips the editor completely: pic.twitter.com/JIQ6FRV5Ob
— Ned Batchelder (@nedbat) May 13, 2022
# ~/.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()
Iterable unpacking (star in a #python list) makes a list from multiple iterables. map() is a handy way to call functions (or classes!) on a set of values. Together they are powerful: pic.twitter.com/gUXFHSzBgN
— Ned Batchelder (@nedbat) May 14, 2022
>>> 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
Python’s English-like syntax can be misleading. “or” doesn’t work as many expect.
— Ned Batchelder (@nedbat) May 16, 2022
BTW: there’s a proposal to make this mistake a syntax error: https://t.co/A9AFORC49H pic.twitter.com/1AKXnVQ6F1
# 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
Classes in #Python can define their own equality. But containment checks with “in” use object identity first: pic.twitter.com/zp04Hg4gqk
— Ned Batchelder (@nedbat) May 18, 2022
>>> 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
Using a #python generator to build bytes using a rolling window over a stream of bits. pic.twitter.com/wuKZrM2t3H
— Ned Batchelder (@nedbat) May 20, 2022
>>> 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
Usually #Python lists hold objects of all the same type, but they don’t have to, and they can hold any type: pic.twitter.com/oFhZrDKH0J
— Ned Batchelder (@nedbat) May 23, 2022
>>> 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
A #Python defaultdict takes a function that can make new values as needed. You don’t have to check if a key exists before you add to its value! 😍🤓😎 pic.twitter.com/SpHLLMKf13
— Ned Batchelder (@nedbat) May 24, 2022
>>> # 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
Python strings are iterable, giving you their characters. This is sometimes inconvenient, but here it’s used three different ways to check if a string has properly balanced parentheses and brackets.
— Ned Batchelder (@nedbat) May 28, 2022
Can you spot them all? 🧐🔎 pic.twitter.com/HpsT7TwP3P
# 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__
Python dict subclasses can define __missing__: it’s called when a key is missing. Instead of hiding a dict in a function as a cache, how about hiding a function in a dict!? A Fibonacci dictionary: pic.twitter.com/N2hE8p76lO
— Ned Batchelder (@nedbat) May 31, 2022
>>> 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__
In the UK, local slang lets you define more enthusiastic #Python constructors: 🇬🇧 🤣 pic.twitter.com/hnavanWDvy
— Ned Batchelder (@nedbat) June 2, 2022
# Tired:
class MyBoringClass:
def __init__(self): # 🥱
...
# Wired:
class MyExcitedClass:
def __innit__(self): # 🤣
...
Callable things
Lots of #Python things are callable, and you don’t need to know what they are to call them: pic.twitter.com/WcTThT7TXK
— Ned Batchelder (@nedbat) June 3, 2022
>>> 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
[]