Get all subclasses

Get all subclasses
download icon copy icon
>>> class A: pass ... >>> A.__subclasses__() [] >>> class B(A): pass ... >>> class C(A): pass ... >>> A.__subclasses__() [<class '__main__.B'>, <class '__main__.C'>] >>> Exception.__subclasses__() [<class 'ArithmeticError'>, <class 'AssertionError'>, ..., <class 'copy.Error'>]
TIL about the __subclasses__() method which returns a list of immediate subclasses:
bbelderbos python

Code uses list comprehensions?

Code uses list comprehensions?
download icon copy icon
import ast import inspect from collections.abc import Callable def uses_list_comprehension(func: Callable) -> bool: # https://stackoverflow.com/a/35150363 src = inspect.getsource(func) program = ast.parse(src) list_comps = [ node for node in ast.walk(program) if type(node) is ast.ListComp ] return len(list_comps) > 0 def func1(): return [ i for i in range(5) ] def func2(): print("hello") print(uses_list_comprehension(func1)) # True print(uses_list_comprehension(func2)) # False
The other day I learned how to test if certain code uses a list comprehension:
bbelderbos python

Test the random module

Test the random module
download icon copy icon
import random # make it predictable >>> random.seed(12) >>> random.sample([1,2,3,4,5], 2) [4, 3] >>> random.sample([1,2,3,4,5], 2) [5, 3] >>> random.sample([1,2,3,4,5], 2) [2, 4] # same results >>> random.seed(12) >>> random.sample([1,2,3,4,5], 2) [4, 3] >>> random.sample([1,2,3,4,5], 2) [5, 3] >>> random.sample([1,2,3,4,5], 2) [2, 4]
For tests you want predictability, yet the random module is well... random. Using the random.seed() method we can take the randomness out, so the random.sample() calls after the second random.seed() produce the same results and are now easier to test.
bbelderbos python

Caching API calls

Caching API calls
download icon copy icon
>>> import requests >>> import requests_cache # supported backends: sqlite (default), mongodb, redis, memory # expiring cache in 10 seconds for example sake >>> requests_cache.install_cache('cache.db', backend='sqlite', expire_after=10) >>> resp = requests.get("https://pybit.es/") >>> resp.from_cache False >>> resp = requests.get("https://pybit.es/") >>> resp.from_cache True # waiting for 15 seconds >>> resp = requests.get("https://pybit.es/") >>> resp.from_cache False # request straight after last one, using cache again >>> resp = requests.get("https://pybit.es/") >>> resp.from_cache True
Did you know that you can cache repeated API calls using the `requests_cache` module? Super useful when developing something and you don't want to make a bunch of calls over the network. It uses an sqlite3 DB under the hood.
bbelderbos python

make a datetime timezone aware

make a datetime timezone aware
download icon copy icon
>>> from datetime import datetime, timezone >>> now = datetime.now() >>> now # "naive" datetime.datetime(2023, 1, 13, 13, 12, 43, 868109) >>> now.replace(tzinfo=timezone.utc) # "aware" datetime.datetime(2023, 1, 13, 13, 12, 43, 868109, tzinfo=datetime.timezone.utc)
I just ran into this error using python-dateutil: "can't compare offset-naive and offset-aware datetimes". I hit this because `datetime.now()` does no make a "timezone aware" datetime. But this is easy to fix with this one liner (source: https://stackoverflow.com/a/41624199). I chose this way because it's Standard Library only, no external dependencies (pytz):
bbelderbos python

tuples are immutable

tuples are immutable
download icon copy icon
>>> numbers = list(range(1, 11)) >>> numbers [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] >>> type(numbers) <class 'list'> >>> numbers[6] = "spam" # might not always be desired >>> numbers [1, 2, 3, 4, 5, 6, 'spam', 8, 9, 10] >>> numbers = tuple(range(1, 11)) >>> numbers (1, 2, 3, 4, 5, 6, 7, 8, 9, 10) >>> type(numbers) <class 'tuple'> >>> numbers[6] = "spam" Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment
A nice feature of tuples is that they are immutable so values in the collection cannot be overriden, you would have to make a new object. This can lead to safer code (aka data integrity):
bbelderbos python

dict keys should be hashable

dict keys should be hashable
download icon copy icon
>>> d = {} >>> l = [1, 2, 3] >>> t = (1, 2, 3) >>> d[l] = [] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unhashable type: 'list' >>> d[t] = [] >>>
Tuples are immutable which makes them hashable and thus usable as keys for dictionaries, where (mutable) lists are not:
bbelderbos python

see if a string only contains ascii characters

see if a string only contains ascii characters
download icon copy icon
>>> for c in list("1aA_:^ àèñº") + ["aá"]: c, c.isascii() ... ('1', True) ('a', True) ('A', True) ('_', True) (':', True) ('^', True) (' ', True) ('à', False) ('è', False) ('ñ', False) ('º', False) ('aá', False)
3.7 introduced the isascii() string method which returns True if the string is empty or all characters in the string are ASCII, False otherwise. ASCII characters have code points in the range U+0000-U+007F, see: https://www.utf8-chartable.de/unicode-utf8-table.pl
bbelderbos python

find digits in string

find digits in string
download icon copy icon
>>> s = "2 files changed, 31 insertions(+), 3 deletions(-)" >>> import re >>> re.findall("\d+", s) ['2', '31', '3']
A quick way to get the change stats from this commit message line:
bbelderbos python

char to number

char to number
download icon copy icon
>>> import string >>> for c in string.ascii_lowercase[:5]: c, ord(c) ... ('a', 97) ('b', 98) ('c', 99) ('d', 100) ('e', 101)
The ord() built-in function returns the integer representing the Unicode character:
bbelderbos python

string to object

string to object
download icon copy icon
>>> s = "[1, 2, [3, 4], [5]]" >>> import ast >>> ast.literal_eval(s) [1, 2, [3, 4], [5]]
You can use the literal_eval() function from the ast module to parse a string into a Python object. Here we get a nested list from a string:
bbelderbos python

Transposing a matrix

Transposing a matrix
download icon copy icon
>>> grid = [(1, 2, 3), (4, 5, 6)] >>> list(zip(*grid)) [(1, 4), (2, 5), (3, 6)] >>> grid = [(1, 2, 3), (4, 5, 6), (7, 8, 9)] >>> list(zip(*grid)) [(1, 4, 7), (2, 5, 8), (3, 6, 9)]
numpy makes this a breeze - numpy.transpose(matrix) - but you can also use the zip() built-in function, leveraging the fact that it iterates over several iterables in parallel, producing new tuples with an item from each one. So here feeding zip the tuples (1, 2, 3) and (4, 5, 6) it matches ("zips up"): - 1 and 4 -> (1, 4) - 2 and 5 -> (2, 5) - 3 and 6 -> (3, 6) = effectively transposing the grid.
bbelderbos python

turn strings into operations (without eval)

turn strings into operations (without eval)
download icon copy icon
>>> a = "1 + 2" >>> b = "3 * 4" >>> import operator >>> ops = {"+": operator.add, "*": operator.mul} >>> for line in (a, b): ... num1, op, num2 = line.split() ... num1 = int(num1) ... num2 = int(num2) ... result = ops[op](num1, num2) ... print(num1, num2, result) ... 1 2 3 3 4 12
The other day (in the AoC) I was given strings like "1 + 2" and "3 * 4". How do you perform these operations without using eval()? You can use the operator module in Python:
bbelderbos python

Least common multiple

Least common multiple
download icon copy icon
>>> import math >>> numbers = [2, 3, 4] >>> math.lcm(*numbers) 12 >>> for i in range(1, 14): ... print(i, " => ", end="") ... for n in numbers: ... print(f"{n}: {i % n == 0}", end=" ") ... print() ... 1 => 2: False 3: False 4: False 2 => 2: True 3: False 4: False 3 => 2: False 3: True 4: False 4 => 2: True 3: False 4: True 5 => 2: False 3: False 4: False 6 => 2: True 3: True 4: False 7 => 2: False 3: False 4: False 8 => 2: True 3: False 4: True 9 => 2: False 3: True 4: False 10 => 2: True 3: False 4: False 11 => 2: False 3: False 4: False 12 => 2: True 3: True 4: True # <= lcm 12 for 2,3,4 13 => 2: False 3: False 4: False
I learned about the "least common multiple" the other day which is the least possible number that is divisible by all the numbers in a list of numbers. For example, the LCM of 2, 3, and 4 == 12. Python has a function in the math module to conveniently calculate this for you:
bbelderbos python

namedtuple + type hints

namedtuple + type hints
download icon copy icon
from typing import NamedTuple class Karma(NamedTuple): giver: str receiver: str score: int # for this line: # Karma('bob', 'julian', '5') # mypy gives: # Argument 3 to "Karma" has incompatible type "str"; expected "int" karma = Karma('bob', 'julian', 5) # ok
Make a namedtuple with type hints 😍
bbelderbos python

Django form save commit kwarg

Django form save commit kwarg
download icon copy icon
if form.is_valid() # some Django ModelForm object bite = form.save(commit=False) bite.user = request.user # set the user foreign key field bite.save()
If you want to add extra attributes when saving a ModelForm, you can save the form with `commit=False`. I commonly use this to set a foreign key field for example.
bbelderbos python

Integer division

Integer division
download icon copy icon
>>> s = "this is a string" >>> len(s) 16 >>> len(s) / 2 8.0 >>> len(s) // 2 8
Python's / performs float division, you can use // for integer division.
bbelderbos python

set operations - multiple sets

set operations - multiple sets
download icon copy icon
>>> a = {1, 2, 3, 4} >>> b = {5, 6, 2} >>> c = {7, 2, 8} >>> d = {2, 9, 10} >>> a & b & c & d {2} >>> a.intersection(b, c, d) {2}
What is you want to find the common element(s) across more than two sets? set.intersection() supports this fine.
bbelderbos python

alphabet constants

alphabet constants
download icon copy icon
>>> import string >>> string.ascii_lowercase 'abcdefghijklmnopqrstuvwxyz' >>> string.ascii_uppercase 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
The string module makes it easy to get the ranges a-z + A-Z (and more) for use as constants.
bbelderbos python

from __future__ import annotations

from __future__ import annotations
download icon copy icon
# 3.7 REPL >>> def func() -> list[str]: ... pass ... Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'type' object is not subscriptable >>> def func() -> int|None: ... pass ... Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for |: 'type' and 'NoneType' >>> from __future__ import annotations >>> def func() -> list[str]: ... pass ... >>> def func() -> int|None: ... pass ...
If you need to maintain Python 3.7 and want to use modern typing syntax introduced in >= 3.8, you can import annotations from __future__:
bbelderbos python

testing datetimes

testing datetimes
download icon copy icon
import datetime from freezegun import freeze_time # function that returns tomorrow's date from tomorrow import tomorrow @freeze_time('2022-12-01') def test_tomorrow(): assert tomorrow() == datetime.date(2022, 12, 2)
Mocking out datetimes can be tough! Say you'd use @patch('datetime.date', your_datetime_mock_object) in your test, only to have it break when the import in the code changes from "import datetime" to "from datetime import date, timedelta" for example. Luckily there is a library called FreezeGun that works around this issue by thoroughly faking Python datetimes. All you have to do is use its @freeze_time decorator.
bbelderbos python

Read / write files the modern way

Read / write files the modern way
download icon copy icon
>>> from pathlib import Path >>> file = Path("notes") / "test_file" >>> file.write_text("hello\n") 6 # number of characters written >>> file.read_text() 'hello\n'
No more need for "with open(...", you can use pathlib now 🐍😍
bbelderbos python

look for multiple indices in a list

look for multiple indices in a list
download icon copy icon
>>> tip = """# Regex tip ... ... Description (usually single line, could be more) ... ... ... ~~~ ... >>> import re ... >>> words = ("how", "are", "you", "today") ... >>> for word in words: ... ... if re.match(r".*(o[uw])$", word): # 🤔 ... ... word ... ... ... 'how' ... 'you' ... ~~~ ... ... #regex""" >>> lines = tip.splitlines() >>> lines.index("~~~") 4 # assuming two ~~~ markers >>> start_index, end_index = [ ... i for i, line in enumerate(lines) ... if line.strip() == "~~~" ... ] >>> start_index, end_index (4, 13) >>> from pprint import pprint as pp >>> pp(lines[start_index + 1: end_index]) ['>>> import re', '>>> words = ("how", "are", "you", "today")', '>>> for word in words:', '... if re.match(r".*(o[uw])$", word): # 🤔', '... word', '...', "'how'", "'you'"]
You can use list.index() to find the first match of a string, for multiple matches you can use enumerate():
bbelderbos python

parametrizing fixtures

parametrizing fixtures
download icon copy icon
# note1 + note2 are fixtures, expected_tip1 and -2 are Tip objects @pytest.mark.parametrize( "file, expected", [ ("note1", expected_tip1), ("note2", expected_tip2), ], ) def test_parsing_note_files(request, file, expected): note_file = request.getfixturevalue(file) # from string to fixture! actual = parse_tip_file(note_file) assert actual == expected
You can use request.getfixturevalue() to lookup a fixture by string. This is handy when parametrizing them.
bbelderbos python

flatten a list of list

flatten a list of list
download icon copy icon
>>> nested_list = [['project'], ['introduction'], ['project'], ['project'], ['python', 'github'], ['python', 'testing'], ['python'], ['project', 'debug'], ['python']] >>> sum(nested_list, []) ['project', 'introduction', 'project', 'project', 'python', 'github', 'python', 'testing', 'python', 'project', 'debug', 'python'] >>> import itertools >>> itertools.chain.from_iterable(nested_list) <itertools.chain object at 0x1056fc2b0> >>> list(itertools.chain.from_iterable(nested_list)) ['project', 'introduction', 'project', 'project', 'python', 'github', 'python', 'testing', 'python', 'project', 'debug', 'python']
Two ways to flatten a list (iterable) or lists (iterables). The `sum()` way is quite obscure, I prefer `itertools.chain.from_iterable()` as it more clearly shows intent. For deeper nesting you will need recursion.
bbelderbos python

Alternate class constructors

Alternate class constructors
download icon copy icon
>>> from datetime import datetime, date >>> class MyDate: ... ... def __init__(self, year, month, day): ... self.date = date(year, month, day) ... ... @classmethod ... def from_str(cls, date_str): ... year, month, day = date_str.split("-") ... return cls(int(year), int(month), int(day)) ... >>> d1 = MyDate(2016, 12, 19) >>> d1.date datetime.date(2016, 12, 19) # an alternative way to instantiate a MyDate object: >>> d2 = MyDate.from_str("2016-12-19") >>> d2.date datetime.date(2016, 12, 19) >>> d1.date == d2.date True
Here we used a @classmethod to provide a second way to construct a MyDate object, namely by giving it a date string.
bbelderbos python

file exclusive creation mode

file exclusive creation mode
download icon copy icon
>>> with open("file", "w") as f: ... f.write("some text\n") ... 10 >>> with open("file", "w") as f: ... f.write("other text\n") ... 11 # oops >>> with open("file") as f: ... f.read() ... 'other text\n' # prevent overwriting existing file >>> with open("file", "x") as f: ... f.write("yet other text\n") ... Traceback (most recent call last): File "<stdin>", line 1, in <module> FileExistsError: [Errno 17] File exists: 'file'
You can open a file with 'x' (exclusive creation) to prevent overwriting it if it already exists.
bbelderbos python

Swap variables

Swap variables
download icon copy icon
>>> a = 1 >>> b = 2 >>> a, b = b, a >>> a 2 >>> b 1
Tuple unpacking 😍 - swapping variables does not require an intermediate variable :)
bbelderbos python

Only keyword arguments

Only keyword arguments
download icon copy icon
>>> def divide_numbers(*, numerator, denominator): ... try: ... return int(numerator)/int(denominator) ... except ZeroDivisionError: ... return 0 ... >>> divide_numbers(10, 2) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: divide_numbers() takes 0 positional arguments but 2 were given >>> divide_numbers(numerator=10, denominator=2) 5.0
You can enforce "keyword-only" arguments in Python by adding a * in the function arguments, succeeding arguments can only be supplied by keyword.
bbelderbos python

get type hints

get type hints
download icon copy icon
>>> def func(arg: str, another_arg: int) -> set[str]: ... pass ... >>> import typing >>> typing.get_type_hints(func) {'arg': <class 'str'>, 'another_arg': <class 'int'>, 'return': set[str]}
You can get the type hints for an object using get_type_hints() from the typing module.
bbelderbos python

str.replace()

str.replace()
download icon copy icon
>>> s = "hello world, hello again" >>> s.replace("hello", "bye") 'bye world, bye again' >>> s.replace("hello", "bye", 1) 'bye world, hello again'
The str.replace() method takes an optional count argument so you for example can only replace the first match.
bbelderbos python

test

test
download icon copy icon
test
string
pybob python

tuple unpacking

tuple unpacking
download icon copy icon
>>> pw_file_line = "bin:x:2:2:bin:/bin:/bin/sh" >>> pw_file_line.split(":") ['bin', 'x', '2', '2', 'bin', '/bin', '/bin/sh'] >>> username, *_, shell_path = pw_file_line.split(":") >>> username 'bin' >>> shell_path '/bin/sh'
I love Python's tuple unpacking.
bbelderbos python

timeit decorator

timeit decorator
download icon copy icon
>>> from functools import wraps >>> from time import time, sleep >>> def timing(f): ... """A simple timer decorator""" ... @wraps(f) ... def wrapper(*args, **kwargs): ... start = time() ... result = f(*args, **kwargs) ... end = time() ... print(f'Elapsed time {f.__name__}: {end - start}') ... return result ... return wrapper ... >>> @timing ... def func(): ... sleep(2) ... print('Done') ... >>> func() Done Elapsed time func: 2.0039281845092773
Here we use a decorator to time the duration of a function execution.
bbelderbos python

list comprehension

list comprehension
download icon copy icon
>>> languages = ['Python', 'Java', 'Perl', 'PHP', 'Python', 'JS', 'C++', 'JS', 'Python', 'Ruby'] >>> list(filter(lambda l: l.lower().startswith('p'), languages)) ['Python', 'Perl', 'PHP', 'Python', 'Python'] >>> [l for l in languages if l.lower().startswith('p')] ['Python', 'Perl', 'PHP', 'Python', 'Python']
A case of Beautiful is better than ugly?
bbelderbos python

deduplicate values

deduplicate values
download icon copy icon
>>> languages = 'Python Java Perl PHP Python JS C++ JS Python Ruby'.split() >>> set(languages) {'Perl', 'JS', 'Python', 'Ruby', 'Java', 'PHP', 'C++'}
#Python has a basic data type called set with a very useful feature: it does not hold duplicate elements.
bbelderbos python

venv prompt

venv prompt
download icon copy icon
√ tmpdir $ python3.10 -m venv venv && source venv/bin/activate (venv) √ tmpdir $ deactivate √ tmpdir $ rm -r venv # using a different prompt: √ tmpdir $ python3.10 -m venv venv --prompt "my 🐍 project" && source venv/bin/activate (my 🐍 project) √ tmpdir $
Tired of the venv prompt? You can use --prompt to name it something different.
bbelderbos python

collections.Counter

collections.Counter
download icon copy icon
>>> from collections import Counter >>> languages = 'Python Java Perl Python JS C++ JS Python'.split() >>> Counter(languages) Counter({'Python': 3, 'JS': 2, 'Java': 1, 'Perl': 1, 'C++': 1}) >>> Counter(languages).most_common(2) [('Python', 3), ('JS', 2)]
For counting in #Python look no further than collections.Counter(), one of my favorite 🐍 container datatypes 💖
bbelderbos python

numeric notations in Python f-strings

numeric notations in Python f-strings
download icon copy icon
number = 27 print(f"{number} in decimal is = {number:d}") # 27 in decimal is = 27 print(f"{number} in binary is = {number:b}") # 27 in binary is = 11011 print(f"{number} in octal is = {number:o}") # 27 in octal is = 33 print(f"{number} in hexadecimal is = {number:x}") # 27 in hexadecimal is = 1b print(f"{number} in scientific is = {number:e}") # 27 in scientific is = 2.700000e+01
The various numeric notations like hexadecimal, octal, scientific, etc. can be printed in Python f-strings using ‘{variable:notation}’
anthlis python

percentages in f-strings

percentages in f-strings
download icon copy icon
number = 0.27154 print(f"{number}") # 0.27154 print(f"{number:.0%}") # 27% print(f"{number:.1%}") # 27.2% print(f"{number:.2%}") # 27.15%
Convert a decimal number to a percentage with f-string:
anthlis python

propery setter

propery setter
download icon copy icon
>>> class Ninja: ... def __init__(self): ... self._score = 0 ... ... @property ... def score(self): ... return self._score ... ... @score.setter ... def score(self, new_score): ... if new_score < 0: ... raise ValueError('Score cannot be negative') ... self._score = new_score ... >>> n = Ninja() >>> n.score 0 >>> n.score = 2 >>> n.score = -1 ... ValueError: Score cannot be negative >>> n.score 2
The property decorator makes read-only attributes, but you can also add a setter to control under what conditions they can get set, pretty powerful.
bbelderbos python

if __name__ == "__main__"

if __name__ == "__main__"
download icon copy icon
$ more script.py def func(): print("Hello from function") if __name__ == "__main__": func() # main block gets invoked $ python script.py Hello from function $ python >>> import script # main block does not get invoked >>> script.func() Hello from function
What is this < if __name__ == "__main__" > in a #Python script? It's typically used at the end of a script to include code that ONLY runs if the module (script) is called directly and NOT upon importing it.
bbelderbos python

sort() vs sorted()

sort() vs sorted()
download icon copy icon
>>> numbers = [3, 1, 5, 4, 9] # sorted() returns numbers in sorted order >>> sorted(numbers) [1, 3, 4, 5, 9] # leaving the original numbers list unchanged >>> numbers [3, 1, 5, 4, 9] # sort() though sorts in-place >>> numbers.sort() # as it's in place the original list changed # and sort() returns None >>> numbers [1, 3, 4, 5, 9] >>> ret = numbers.sort() >>> ret is None True
sort() sorts in-place, sorted() returns a new sorted copy
bbelderbos python

monthcalendar

monthcalendar
download icon copy icon
>>> import calendar >>> from datetime import datetime >>> now = datetime.now() >>> now.year, now.month (2020, 10) >>> from pprint import pprint as pp >>> cal = calendar.monthcalendar(now.year, now.month) >>> pp(cal) [[0, 0, 0, 1, 2, 3, 4], [5, 6, 7, 8, 9, 10, 11], [12, 13, 14, 15, 16, 17, 18], [19, 20, 21, 22, 23, 24, 25], [26, 27, 28, 29, 30, 31, 0]] >>> cal = calendar.monthcalendar(now.year, now.month+1) >>> cal[0] [0, 0, 0, 0, 0, 0, 1]
Creating a month calendar in Python.
bbelderbos python

raise ... from None

raise ... from None
download icon copy icon
# https://docs.python.org/3/tutorial/errors.html#exception-chaining >>> def func(): ... raise ConnectionError ... >>> try: ... func() ... except ConnectionError as exc: ... raise RuntimeError('Failed to open database') from exc ... Traceback (most recent call last): File "<stdin>", line 2, in <module> File "<stdin>", line 2, in func ConnectionError The above exception was the direct cause of the following exception: Traceback (most recent call last): File "<stdin>", line 4, in <module> RuntimeError: Failed to open database # to only show the raised exception >>> try: ... func() ... except ConnectionError as exc: ... raise RuntimeError('Failed to open database') from None ... Traceback (most recent call last): File "<stdin>", line 4, in <module> RuntimeError: Failed to open database
Exception chaining happens automatically when an exception is raised inside an except or finally section. This can be disabled by using from None idiom:
bbelderbos python

dateutil.parser

dateutil.parser
download icon copy icon
>>> from datetime import datetime >>> from dateutil.parser import parse >>> logline = "INFO 2014-07-03T23:31:22 supybot Killing Driver objects." >>> date = logline.split()[1] >>> date '2014-07-03T23:31:22' >>> datetime.strptime(date, '%Y-%m-%dT%H:%M:%S') datetime.datetime(2014, 7, 3, 23, 31, 22) >>> parse(date) datetime.datetime(2014, 7, 3, 23, 31, 22)
The dateutil module has wonderful support for converting date strings into datetime objects. However it's an extra module to install, so with a bit more effort you can accomplish the same with datetime's strptime.
bbelderbos python

zip

zip
download icon copy icon
>>> names = 'bob julian tim sara'.split() >>> ages = '11 22 33 44'.split() >>> zip(names, ages) <zip object at 0x7fae75920d20> >>> list(zip(names, ages)) [('bob', '11'), ('julian', '22'), ('tim', '33'), ('sara', '44')] >>> dict(zip(names, ages)) {'bob': '11', 'julian': '22', 'tim': '33', 'sara': '44'}
In #Python you can create a dictionary from two iterables using the zip built-in function.
bbelderbos python

recursion

recursion
download icon copy icon
>>> numbers = [[1], [2, 3], [4, [5, 6, [7, 8, [9, [10, 11, 12]]]]]] >>> from itertools import chain >>> list(chain.from_iterable(numbers)) [1, 2, 3, 4, [5, 6, [7, 8, [9, [10, 11, 12]]]]] >>> def flatten(numbers): ... for num in numbers: ... if isinstance(num, int): ... yield num ... else: ... yield from flatten(num) ... >>> flatten(numbers) <generator object flatten at 0x7fae0b17b510> >>> list(flatten(numbers)) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] >>> sum(flatten(numbers)) 78
Common #Python interview question: How to flatten a list of lists (several nested levels)? Seems itertools.chain only goes one level deep, so let's use some recursion.
bbelderbos python

mutable default arguments

mutable default arguments
download icon copy icon
>>> def sum_(numbers, res=[]): ... res.extend(numbers) ... return sum(res) ... >>> sum_([1, 2, 3]) 6 >>> sum_([1, 2, 3]) 12 >>> sum_([1, 2, 3]) 18 >>> def sum_(numbers, res=None): ... res = res if res else [] ... res.extend(numbers) ... return sum(res) ... >>> sum_([1, 2, 3]) 6 >>> sum_([1, 2, 3]) 6 >>> sum_([1, 2, 3]) 6
Default argument values are evaluated once upon module load. Use None instead.
bbelderbos python

functools.partial

functools.partial
download icon copy icon
>>> from functools import partial >>> print_no_newline = partial(print, end=', ') >>> for _ in range(3): print('test') ... test test test >>> for _ in range(3): print_no_newline('test') ... test, test, test, >>>
The #Python functools module has some useful functions, one being "partial" that lets you “freeze” some portion of a function’s arguments and/or keywords. Here we make our own print defaulting the end keyword to a comma (overwriting print's default of adding a newline (\n) to the end).
bbelderbos python

enumerate

enumerate
download icon copy icon
>>> i = 0 >>> s = "abc" >>> for c in s: ... i, c ... i += 1 ... (0, 'a') (1, 'b') (2, 'c') >>> for i, c in enumerate(s): ... i, c ... (0, 'a') (1, 'b') (2, 'c') >>> for i, c in enumerate(s, start=1): ... i, c ... (1, 'a') (2, 'b') (3, 'c')
If you need the index alongside the loop variable, use the enumerate built-in.
bbelderbos python

__slots__

__slots__
download icon copy icon
class PersonWithoutSlots: def __init__(self, name, age): self.name = name self.age = age def __str__(self): return "This is a simple demo on a class without slots." tiago = PersonWithoutSlots(name='Tiago', age=27) print(tiago.__dict__) tiago.surname = 'Peres' print(tiago.__dict__) print("Now, let's try with __slots__\n") class PersonWithSlots: __slots__ = ['name', 'age'] def __init__(self, name, age): self.name = name self.age = age def __str__(self): return "This is a simple demo on a class with slots" tiago = PersonWithSlots(name='Tiago', age=27) # This will throw an error because surname is not declared upfront in __slots__ tiago.surname = 'Peres' # This will also throw an error because with slots you deny the __dict__ creation print(tiago.__dict__)
The special attribute __slots__ allows you to explicitly state which instance attributes you expect your object instances to have, with expected faster attribute access and space savings in memory.
Peres python

dict.fromkeys

dict.fromkeys
download icon copy icon
>>> from string import ascii_lowercase >>> ascii_lowercase[:5] 'abcde' >>> dict.fromkeys(ascii_lowercase[:5], 0) {'a': 0, 'b': 0, 'c': 0, 'd': 0, 'e': 0}
TIL: dict.fromkeys() -> "Create a new dictionary with keys from iterable and values set to value."
bbelderbos python

split

split
download icon copy icon
>>> "bob julian tim ramit".split() ['bob', 'julian', 'tim', 'ramit'] >>> "bob b,julian s,tim f,ramit s".split(",") ['bob b', 'julian s', 'tim f', 'ramit s'] >>> "name,some text with, commas".split(",", 1) ['name', 'some text with, commas']
You can use #Python's str.split() to split a string into a list. It takes an optional maxsplit of how many splits to do.
bbelderbos python

Singleton Python

Singleton Python
download icon copy icon
import sqlite3 class MetaSingleton(type): _instances = {} def __call__(cls, *args, **kwargs): if cls not in cls._instances: cls._instances[cls] = super(MetaSingleton, cls).__call__(*args, **kwargs) return cls._instances[cls] class Database(metaclass=MetaSingleton): connection = None def connect(self): if self.connection is None: self.connection = sqlite3.connect("db.sqlite3") self.cursor_obj = self.connection.cursor() return self.cursor_obj db1 = Database().connect() db2 = Database().connect() >>> db1 <sqlite3.Cursor object at 0x0000027AEE720110> >>> db2 <sqlite3.Cursor object at 0x0000027AEE720110>
Singleton pattern applied to a database connection
henrique python

operator.itemgetter

operator.itemgetter
download icon copy icon
>>> days = ['mon', 'tue', 'wed', 'thurs', 'fri', 'sat', 'sun'] # sure, you can use a list comprehension: >>> [day for day in days if day in ("thurs", "sun")] ['thurs', 'sun'] # but you can also use the operator module: >>> from operator import itemgetter >>> f = itemgetter(3, 6) >>> f(days) ('thurs', 'sun')
The itemgetter class lets you grab multiple items from a list, string, dict, etc
bbelderbos python

string methods

string methods
download icon copy icon
>>> import re >>> s = "1234" >>> re.match("^\d+$", s) <re.Match object; span=(0, 4), match='1234'> # rather: >>> s.isdigit() True # or: >>> s.isnumeric() True >>> s = "abcde" >>> re.match("^ab", s) <re.Match object; span=(0, 2), match='ab'> # there is a string method for this as well: >>> s.startswith("ab") True >>> re.match("^[a-z]+", s) <re.Match object; span=(0, 5), match='abcde'> # nope, str has you covered again >>> s.islower() True # for more info: >>> dir(s) ...
When not to use a regex? When #Python string methods do the obvious thing already :)
bbelderbos python

splitlines

splitlines
download icon copy icon
>>> "first line\r\nsecond line".split("\n") ['first line\r', 'second line'] # includes \r (Carriage Return) in the splitting >>> "first line\r\nsecond line".splitlines() ['first line', 'second line'] >>> "first line\rsecond line".splitlines() ['first line', 'second line'] >>> "first line\nsecond line".splitlines() ['first line', 'second line']
Although I don't use Windows it's nice if your #Python code can be OS compatible. str.splitlines() is a nice example of this.
bbelderbos python

TextBlob

TextBlob
download icon copy icon
>>> from textblob import TextBlob >>> tweets = ("I was happy with the book", "this is awful", "Python is object oriented", "Python is awesome") >>> for tw in tweets: ... tw, TextBlob(tw).sentiment ... ('I was happy with the book', Sentiment(polarity=0.8, subjectivity=1.0)) ('this is awful', Sentiment(polarity=-1.0, subjectivity=1.0)) ('Python is object oriented', Sentiment(polarity=0.0, subjectivity=0.0)) ('Python is awesome', Sentiment(polarity=1.0, subjectivity=1.0))
Quick and easy sentiment analysis in 4 lines of #Python code.
bbelderbos python

textwrap.wrap

textwrap.wrap
download icon copy icon
>>> from textwrap import wrap >>> text = ("Every great developer you know got there by solving " ... "problems they were unqualified to solve until they " ... "actually did it. - Patrick McKenzie") >>> for line in wrap(text, width=80): print(line) ... Every great developer you know got there by solving problems they were unqualified to solve until they actually did it. - Patrick McKenzie >>> for line in wrap(text, width=40): print(line) ... Every great developer you know got there by solving problems they were unqualified to solve until they actually did it. - Patrick McKenzie
You can use textwrap.wrap() to wrap text to columns.
bbelderbos python

textwrap.dedent

textwrap.dedent
download icon copy icon
from textwrap import dedent def test(): # end first line with \ to avoid the empty line! s = '''\ hello world ''' print(repr(s)) # prints ' hello\n world\n ' print(repr(dedent(s))) # prints 'hello\n world\n'
You can use textwrap.dedent() to remove any common leading whitespace from every line. This can be a useful technique when comparing actual vs expected outputs in your test functions.
bbelderbos python

sys.stdin

sys.stdin
download icon copy icon
$ echo "line1\nline2\nline3\n" > file $ cat file line1 line2 line3 $ cat script.py import sys for line in sys.stdin: print(line, end="") $ python script.py < file line1 line2 line3
You can use #Python's sys.stdin to read from standard input.
bbelderbos python

How to open a web browser using Python

How to open a web browser using Python
download icon copy icon
import webbrowser webbrowser.open("https://www.python.org")
Python webbrowser module code example
wilsonusman python

round

round
download icon copy icon
>>> for i in (0.5, 1.5, 2.5, 3.5): ... i, round(i) ... (0.5, 0) (1.5, 2) (2.5, 2) (3.5, 4)
Python's round built-in rounds even (aka bankers' rounding)
pybob python

Add rows one by one

Add rows one by one
download icon copy icon
import pandas as pd csv = pd.read_csv('data/models.csv') # Create an empty DataFrame columns = ['make', 'model', 'year'] df = pd.DataFrame(columns=columns) # Load each row one by one for n, i in enumerate(csv.itertuples()): df.loc[n] = [i[1]] + [i[2]] + [i[3]] # Result print(df) ''' make model year Audi A4 2008 BMW 328i 2006 Mercedes-Benz C300 2020 ''' +qq
wilsonusman python

str.isdigit

str.isdigit
download icon copy icon
>>> s = ("It's almost 3 years we launched our PyBites platform" ... ", which now hosts 300+ exercises, up to the next 100 Bites") >>> >>> [int(word) for word in s.split() if word.isdigit()] [3, 100]
You can use the str.isdigit method to see if a string is numeric. Note that isdigit requires all characters in the string to be digits, so 300+ is discarded here.
bbelderbos python

months dict

months dict
download icon copy icon
>>> import calendar >>> MONTH_NAMES_TO_NUMBERS = { ... v: k for k, v in enumerate(calendar.month_abbr[1:], start=1) ... } >>> MONTH_NAMES_TO_NUMBERS {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6, 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
Useful constants (and/or lazy typing things out) ... Using #Python's calendar module + the enumerate built-in to make a mapping of month names -> numbers
bbelderbos python

bisect

bisect
download icon copy icon
>>> from bisect import insort >>> items = [3, 5, 7] >>> insort(items, 6) >>> items [3, 5, 6, 7] >>> insort(items, 4) >>> items [3, 4, 5, 6, 7] >>> insort(items, 9) >>> items [3, 4, 5, 6, 7, 9] >>> insort(items, 20) >>> items [3, 4, 5, 6, 7, 9, 20] >>> insort(items, 15) >>> items [3, 4, 5, 6, 7, 9, 15, 20]
#Python's bisect module provides functions to support maintaining order upon insert.
bbelderbos python

dataclasses

dataclasses
download icon copy icon
>>> from dataclasses import dataclass >>> @dataclass ... class Bite: ... number: int ... title: str ... level: str = 'Beginner' ... ... def __post_init__(self): ... self.title = self.title.capitalize() ... >>> bite = Bite(1, "sum n numbers") >>> repr(bite) "Bite(number=1, title='Sum n numbers', level='Beginner')" >>> bite.level = 2 # dataclasses are mutable >>> bite Bite(number=1, title='Sum n numbers', level=2) # make an immutable dataclass >>> @dataclass(frozen=True) ... class Bite: ... # same but without __post_init__ (dataclasses.FrozenInstanceError) >>> bite = Bite(1, "sum n numbers") >>> bite.level = 2 ... dataclasses.FrozenInstanceError: cannot assign to field 'level'
#Python dataclasses help reduce boilerplate code when writing classes. You can make them immutable too.
bbelderbos python

fill a string

fill a string
download icon copy icon
>>> text = "Happy 2022!" >>> text.ljust(20) 'Happy 2022! ' >>> text.rjust(20) ' Happy 2022!' >>> text.center(20) ' Happy 2022! ' # with fill character >>> text.center(20, '-') '----Happy 2022!-----' # using f-strings >>> f"{text:<20}" 'Happy 2022! ' >>> f"{text:>20}" ' Happy 2022!' >>> f"{text:^20}" ' Happy 2022! ' >>> f"{text:-^20}" '----Happy 2022!-----'
String aligning and filling in #Python
bbelderbos python

itertools.cycle

itertools.cycle
download icon copy icon
>>> from itertools import cycle >>> from string import ascii_uppercase >>> >>> chars = cycle(ascii_uppercase[:5]) >>> for _ in range(10): next(chars) ... 'A' 'B' 'C' 'D' 'E' 'A' 'B' 'C' 'D' 'E'
itertools.cycle loops through an iterable ad infinitum.
bbelderbos python

Total PyBites Podcast hours

Total PyBites Podcast hours
download icon copy icon
>>> import feedparser >>> pybites_podcast_feed = "https://feeds.buzzsprout.com/1501156.rss" >>> episodes = feedparser.parse(pybites_podcast_feed)["entries"] >>> total_seconds = sum(int(episode.itunes_duration) for episode in episodes) >>> hours, seconds = divmod(total_seconds, 3600) >>> hours 22 >>> seconds / 60 53.6
How many hours of podcast content have we produced so far? #Python feedparser (PyPI) + the sum built-in make this easy.
bbelderbos python

feedparser

feedparser
download icon copy icon
>>> import feedparser # pip install >>> feed = feedparser.parse("https://www.nrc.nl/rss/") # Dutch newspaper >>> len(feed.entries) 153 >>> for item in feed.entries: ... if item.tags[0].term == "Wetenschap": ... print(f"{item.title}\n{item.link}\n") ... Waarom een komeet wel een groene kop heeft, maar geen groene staart https://www.nrc.nl/nieuws/2021/12/29/waarom-een-komeet-wel-een-groene-kop-heeft-maar-geen-groene-staart-a4073518 Uitbarsting La Palma is ten einde: 3.000 gebouwen verwoest en één dode https://www.nrc.nl/nieuws/2021/12/27/uitbarsting-vulkaan-la-palma-is-ten-einde-3000-gebouwen-verwoest-en-een-dode-a4072334 ... ... # TODO: send to Pocket?
The #Python feedparser package is still a staple for parsing RSS feeds.
bbelderbos python

Swapping variables in a line

Swapping variables in a line
download icon copy icon
a, b = b, a
Variables can be easily swapped by tuple unpacking. Here a and b values are swapped.
soumendra python

divmod

divmod
download icon copy icon
>>> from datetime import date >>> pybites_started = date(2016, 12, 19) >>> days_alive = (date.today() - pybites_started).days >>> days_alive 1835 >>> divmod.__doc__ 'Return the tuple (x//y, x%y). Invariant: div*y + mod == x.' >>> years, days = divmod(days_alive, 365) # slightly less if year = 365.25 >>> years 5 >>> days 10
Did you know #Python has a divmod built-in function that takes two (non complex) numbers as arguments and returns the quotient and remainder when using integer division? Here we use it to calculate PyBites' age in years / days.
bbelderbos python

__slots__

__slots__
download icon copy icon
>>> class WithoutSlots: ... def __init__(self, x, y, z): ... self.x = x ... self.y = y ... self.z = z ... >>> class WithSlots: ... __slots__ = 'x', 'y', 'z' ... def __init__(self, x, y, z): ... self.x = x ... self.y = y ... self.z = z ... >>> from pympler.asizeof import asizeof # pip install >>> w1 = WithoutSlots(1, 2, 3) >>> w2 = WithSlots(4, 5, 6) >>> asizeof(w1) 416 >>> asizeof(w2) 152 >>> w1.a = 1 >>> w2.a = 1 ... AttributeError: 'WithSlots' object has no attribute 'a'
If you are going to create a lot of instances of a class, you can reduce memory usage with __slots__ which saves space in objects. Instead of having a dynamic dict that allows adding attributes to objects at any time, there is a static structure which does not allow additions after creation.
bbelderbos python

Testing

Testing
download icon copy icon
from pprint import pprint as pp
carlosbarbosa python

screenshot

screenshot
download icon copy icon
from selenium import webdriver from selenium.webdriver.common.by import By driver = webdriver.Chrome() driver.get('https://codechalleng.es/bites/101/') driver.find_element(By.CLASS_NAME, 'biteDescription').screenshot("file.png") driver.close()
Taking a screenshot with #Python Selenium
bbelderbos python

re-run a generator

re-run a generator
download icon copy icon
>>> def gen(): ... yield from [1, 2, 3] ... >>> g = gen() >>> for i in g: print(i) ... 1 2 3 # generator exhausted: >>> for i in g: print(i) ... # reusable generator: >>> class Gen: ... def __iter__(self): ... yield from [1, 2, 3] ... >>> g = Gen() >>> for i in g: print(i) ... 1 2 3 # now you can iterate again: >>> for i in g: print(i) ... 1 2 3
TIL: by default #Python generators can be used only once, to re-run a generator you can use __iter__()
bbelderbos python

Greedy regexes

Greedy regexes
download icon copy icon
>>> import re # let's capture the first word in double quotes >>> s = 'Type "help", "copyright", "credits" or "license" for more information' # oops >>> re.sub(r'.*"(.*)".*', r'--\1--', s) '--license--' # <sub-pattern>? == non-greedy = match as little as possible # still greedy >>> re.sub(r'.*?"(.*)".*', r'--\1--', s) '--help", "copyright", "credits" or "license--' # works: >>> re.sub(r'.*?"(.*?)".*', r'--\1--', s) '--help--' # or be more explicit in your pattern >>> re.sub(r'[^"]+"([^"]+)".*', r'--\1--', s) '--help--'
Be careful when you want to match a nested pattern, regular expressions are greedy by default.
bbelderbos python

Precise String Trimming

Precise String Trimming
download icon copy icon
In [1]: animal = 'chocobo' In [2]: animal.lstrip('cho') Out[2]: 'bo' In [3]: animal.lstrip('ohc') Out[3]: 'bo' In [4]: animal.removeprefix('cho') Out[4]: 'cobo' In [5]: animal.removeprefix('ohc') Out[5]: 'chocobo'
The removeprefix/removesuffix string methods can trim edges of a string more precisely than their lstrip/rstrip predecessors.
ajkerrigan python

set operations

set operations
download icon copy icon
>>> names1 = {'Joyce', 'Julian', 'Tim', 'Frederik', 'Ana'} >>> names2 = {'Martin', 'Ana', 'Frederik', 'Marga', 'Henry'} # names in names1 but not in names2 # alternative = names1.difference(names2) >>> names1 - names2 {'Joyce', 'Julian', 'Tim'} # names in names2 but not in names1 # alternative = names2.difference(names1) >>> names2 - names1 {'Marga', 'Henry', 'Martin'} # names both in names1 and names2 # alternative = names1.intersection(names2) >>> names1 & names2 {'Ana', 'Frederik'} # names in names1 or in names2, but not in both # alternative = names1.symmetric_difference(names2) >>> names1 ^ names2 {'Joyce', 'Julian', 'Henry', 'Martin', 'Marga', 'Tim'}
Python set operations are powerful, concise and elegant.
bbelderbos python

NamedTuple

NamedTuple
download icon copy icon
from typing import NamedTuple class Karma(NamedTuple): giver: str receiver: str score: int # for this line: # Karma('bob', 'julian', '5') # mypy gives: # Argument 3 to "Karma" has incompatible type "str"; expected "int" karma = Karma('bob', 'julian', 5) # ok
Make a namedtuple with typing.
bbelderbos python

self.__class__.__name__

self.__class__.__name__
download icon copy icon
>>> class A: ... def __init__(self, name): ... self.name = name ... def __repr__(self): ... return f"A('{self.name}')" ... >>> class B(A): ... pass ... # oops >>> b = B("Katie") >>> repr(b) "A('Katie')" # use self.__class__.__name__ instead: >>> class A: ... def __init__(self, name): ... self.name = name ... def __repr__(self): ... return f"{self.__class__.__name__}('{self.name}')" ... >>> class B(A): ... pass ... >>> b = B("Katie") >>> repr(b) "B('Katie')"
Hardcoding class names in your #Python __repr__ can break in case of inheritance. Update: this works out of the box using dataclass!
bbelderbos python

calendar months

calendar months
download icon copy icon
>>> import calendar >>> MONTH_NAMES_TO_NUMBERS = { ... v: k for k, v in enumerate(calendar.month_abbr[1:], start=1) ... } >>> MONTH_NAMES_TO_NUMBERS {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6, 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
Useful constants (and/or lazy typing things out) ... Using #Python's calendar module + the enumerate built-in to make a mapping of month names -> numbers.
bbelderbos python

capwords

capwords
download icon copy icon
>>> s = "Here's food for thought, let's say title casing isn't easy ..." >>> s.title() "Here'S Food For Thought, Let'S Say Title Casing Isn'T Easy ..." >>> import string >>> string.capwords(s) "Here's Food For Thought, Let's Say Title Casing Isn't Easy ..."
An example when #Python string.capwords() is useful.
bbelderbos python

SequenceMatcher

SequenceMatcher
download icon copy icon
>>> from itertools import combinations >>> from difflib import SequenceMatcher >>> tags = 'python pythonista developer development'.split() >>> for pair in combinations(tags, 2): ... similarity = SequenceMatcher(None, *pair).ratio() ... print(pair, similarity) ... ('python', 'pythonista') 0.75 ('python', 'developer') 0.13333333333333333 ('python', 'development') 0.23529411764705882 ('pythonista', 'developer') 0.10526315789473684 ('pythonista', 'development') 0.19047619047619047 ('developer', 'development') 0.8
🚨Another #Python Standard Library gem 🐍 So you have an API and you don't want similar items to be submitted. Well, you can do a literal string comparison, a regex even, but ... for "almost equal" you can also use difflib's SequenceMatcher.
bbelderbos python