mathspp.com feed Stay up-to-date with the articles on mathematics and programming that get published to mathspp.com. 2022-09-06T01:12:28+02:00 Rodrigo Girão Serrão https://mathspp.com/blog/twitter-threads 4 useful Python command line switches https://mathspp.com/blog/twitter-threads/4-useful-python-command-line-switches 2022-09-06T01:12:28+02:00 2022-09-05T00:00:00+02:00 This short article teaches you 4 common switches to use in the command line with Python.

The Python 🐍 command has many different switches.

Here are the 4 switches I use the most:

1. `-c cmd`: program passed in as string;
2. `-m mod`: run library module as a script;
3. `-i`: inspect interactively after running script; and
4. `-q`: don't print version and copyright messages on interactive startup.

## `-c`

The switch `-c` runs code directly from the command line. It doesn't open the REPL, and it is convenient for short, one-off expressions. The result isn't printed by default, so don't forget your `print`!

``````# What is the factorial of 15?
λ python -c "import math; print(math.factorial(15))"
1307674368000

# What is 2 + 2?
λ python -c "print(2 + 2)"
4``````

## `-m`

The switch `-m` runs a module as a script. This will run an installed module's section that is inside `if __name__ == "__main__":`. The one I use the most is the module `timeit` to measure execution time:

``````λ python -m timeit -s "import math" "math.factorial(15)"
2000000 loops, best of 5: 167 nsec per loop

λ python -m timeit -s "import math" "math.factorial(150)"
200000 loops, best of 5: 1.52 usec per loop``````

## `-i`

The switch `-i` stands for Inspect Interactively. By running your code with `-i`, after the script is done, you get a REPL session with the variables and functions from that script. Useful to play around with functions you just defined.

Suppose this is your file `example.py`:

``````x = 3
y = 5

return x + y``````

If you run it with `-i`, you get to play around with the variables `x` and `y` and with the function `add`:

``````λ python -i example.py
>>> x
3
>>> y
5
13``````

## `-q`

The switch `-q` opens the REPL Quietly. What this means is that it opens the REPL without displaying all the version/platform information. I use it when recording videos and demoing things.

``````λ python -q
>>> # This is a standard REPL``````
]]>
3 different ways to create a dictionary https://mathspp.com/blog/twitter-threads/3-different-ways-to-create-a-dictionary 2022-09-05T08:30:53+02:00 2022-08-26T00:00:00+02:00 This short article teaches you 3 ways of creating a Python dictionary.

Here are 3 ways in which you can create a Python 🐍 dictionary: (Of the three, the last one is the least commonly used.)

``````>>> dict([(1, "one"), (2, "two")])
{1: 'one', 2: 'two'}

{'name': 'Rodrigo', 'twitter': 'mathsppblog'}

>>> dict.fromkeys(["likes", "retweets"], 0)
{'likes': 0, 'retweets': 0}``````

## An iterable of key, value pairs

The built-in `dict` can take an iterable with key, value pairs. Useful, for example, when you have a bunch of keys and a bunch of values that you put together with `zip`:

``````>>> dict([(1, "one"), (2, "two")])
{1: 'one', 2: 'two'}

>>> keys = range(1, 4)
>>> values = "one two three".split()
>>> dict(zip(keys, values))
{1: 'one', 2: 'two', 3: 'three'}``````

In this particular case, you could also get creating with `enumerate` and make use of the keyword argument `start`:

``````>>> dict(enumerate(values, start=1))
{1: 'one', 2: 'two', 3: 'three'}``````

## Keyword arguments

You can use keyword arguments in `dict` to define key, value pairs in your dictionary! However, this only works if your keys are valid variable names:

``````# Values don't have to be strings:
>>> dict(one=1, two=2)
{'one': 1, 'two': 2}

# But the keys have to:
>>> dict(1="one", 2="two")
File "<stdin>", line 1
dict(1="one", 2="two")
^
SyntaxError: expression cannot contain assignment, perhaps you meant "=="?

# Keys have to be valid variable names:
{'name': 'Rodrigo', 'twitter': 'mathsppblog'}``````

## Class method `dict.fromkeys`

The class method `dict.fromkeys` accepts an iterable and a value, and produces a dictionary where all keys have that value. By default, that value is `None`:

``````# Default value is None:
>>> dict.fromkeys("abc")
{'a': None, 'b': None, 'c': None}
>>> dict.fromkeys(range(5))
{0: None, 1: None, 2: None, 3: None, 4: None}

# Different default values:
>>> dict.fromkeys(range(5), ";)")
{0: ';)', 1: ';)', 2: ';)', 3: ';)', 4: ';)'}
>>> dict.fromkeys(["likes", "retweets"], 0)
{'likes': 0, 'retweets': 0}``````

The class method `.fromkeys` has a gotcha associated with it, though. Be careful when using mutable values, because the value isn't copied to each key. It's exactly the same object used over and over:

``````>>> d = dict.fromkeys(range(3), [])
>>> d
{0: [], 1: [], 2: []}

>>> d.append("zero")
>>> d.append("one")

>>> d  # Shouldn't d be empty?!
['zero', 'one']
>>> d
{0: ['zero', 'one'], 1: ['zero', 'one'], 2: ['zero', 'one']}  # 🤯``````

These are just 3 ways of creating a Python dictionary. Soon, I'll send out a Mathspp Insider article talking about all the ways in which you can create dictionaries in Python 🐍 Join to keep learning: https://insider.mathspp.com

## Summary

Here's a quick recap:

1. `dict` accepts an iterable that contains key, value pairs;
2. use keyword arguments in `dict` if you want string keys; and
3. `.fromkeys` gives the same value (default is `None`) to a bunch of keys.

Comment other ways in which you can create a Python 🐍 dict!

]]>
3 ways to reverse a Python list https://mathspp.com/blog/twitter-threads/3-ways-to-reverse-a-python-list 2022-08-26T10:03:51+02:00 2022-08-25T00:00:00+02:00 This short article teaches you 3 ways of reversing a Python list.

Here are 3 simple ways in which you can reverse a Python 🐍 list:

``````>>> lst = [42, 73, 0]
>>> rev1 = reversed(lst)
>>> rev2 = lst[::-1]
>>> lst.reverse()``````

Let's see how they are different.

## Built-in `reversed`

The built-in `reversed` accepts a sequence and returns an object that knows how to iterate over that sequence in reverse order. Hence, `reversed`. Notice it doesn't return a list:

``````>>> reversed(lst)
<list_reverseiterator object at 0x00000241D77BD520>``````

The `list_reverseiterator` object that is returned is “linked” to the original list... So, if you change the original list, the reverse iterator will notice:

``````>>> lst = [42, 73, 0]
>>> rev = reversed(lst)
>>> lst = 999999  # Change something in the original list.

>>> for n in rev:  # Go over the reverse iterator.
...     print(n)
...
0
999999  # This was changed as well.
42``````

## Slicing with `[::-1]`

The slicing syntax with brackets `[]` and colons `:` accepts a “step” that can be negative. If the “start” and “stop” are omitted and the “step” is -1, we get a copy in the reverse order:

``````>>> lst = [42, 73, 0]
>>> lst[::-1]
[0, 73, 42]``````

Slices in Python 🐍 are regular objects, so you can also name them. Thus, you could go as far as creating a named slice to reverse lists, and then use it:

``````>>> lst
[42, 73, 0]

>>> reverse = slice(None, None, -1)
>>> lst[reverse]
[0, 73, 42]``````

Notice that slices are not “linked” to the original list. That's because slicing creates a copy of the list. So, if you change the elements in a given index, the reversed list will not notice:

``````>>> lst = [42, 73, 0]  # Original list.
>>> rev = lst[::-1]    # Reverse copy.
>>> lst = 999999    # Change something in the original.
>>> rev                # The copy didn't notice.
[0, 73, 42]``````

Slicing is very powerful and useful, and that is why I wrote a whole chapter of my free book “Pydon'ts” on the subject.

## List method `.reverse`

Lists have a method `.reverse` that reverses the list in place. What this means is that you do not get a return value with the reversed list... Instead, the list itself gets flipped around 🙃

``````>>> lst = [42, 73, 0]
>>> lst.reverse()
>>> lst
[0, 73, 42]``````

## Summary

Here is a quick summary:

Reverse a Python list with:

1. the built-in `reversed` that will notice changes to the original list;
2. slicing `[::-1]` that creates a copy of the original list; and
3. the method `.reverse` that reverses a list in place.
]]>
Deciphering David Beazley's monstrosity https://mathspp.com/blog/twitter-threads/deciphering-david-beazleys-monstrosity 2022-08-23T02:16:30+02:00 2022-08-22T00:00:00+02:00 This short article explains a monstrous piece of code that David Beazley wrote.

I just found another Python 🐍 monstrosity:

``````sub = lambda x,y,c: c(x-y)
mul = lambda x,y,c: c(x*y)
eqv = lambda x,y,c: c(x==y)
bnz = lambda t,c,a: (a,c)[t]()

def fact(n, c):
(loop:= lambda n, r0: (
eqv(n, 1, lambda r1:
bnz(r1, lambda: c(r0), lambda:
mul(n, r0, lambda r2:
sub(n, 1, lambda r3:
loop(r3, r2)))))))\
(n, 1)

fact(5, print)``````

This one was brought to you by the infamous David Beazley. But what does it do?

Let us start by looking at the four lambdas at the top.

The first three are quite similar to each other:

``````sub = lambda x,y,c: c(x-y)
mul = lambda x,y,c: c(x*y)
eqv = lambda x,y,c: c(x==y)
bnz = lambda t,c,a: (a,c)[t]()

sub(5, 3, print)  # Prints 2
mul(5, 3, print)  # Prints 15
eqv(5, 3, print)  # Prints False``````

Take the first two arguments, operate on them, and recursively call `c` with the result.

So, David is going to chain operations by doing these crazy recursive calls.

The fourth lambda is a bit more interesting but also crazier: it implements conditionals. (I have no idea what “bnz” means, though 🤷) The first argument is the Boolean value, then:

• if the Boolean is `True`, we call `c`;
• if it is `False`, we call the alternative `a`.
``````bnz = lambda t,c,a: (a,c)[t]()

yes = lambda: print("heya")
no = lambda: print(":(")

bnz(True, yes, no)  # Prints heya
bnz(False, yes, no)  # Prints :(``````

Alright, we are halfway there! Now, we just need to understand how David put everything together to implement the factorial function.

The function `fact` defines `loop` in its body, which is a tail recursive factorial. Remember that the 3rd arguments of `sub`, `mul`, and `eqv`, can be seen as what happens after that operation.

``````def fact(n, c):
(loop:= lambda n, r0: (  # Start computing with n and r0.
eqv(n, 1, lambda r1:  # Is n equal to 1?
bnz(r1, lambda: c(r0), lambda:  # If it is, call c(r0) and end, if not...
mul(n, r0, lambda r2:  # multiply n with r0 and call it r2, then
sub(n, 1, lambda r3:  # compute n - 1 and call it r3, and
loop(r3, r2)))))))\   # go to the top, with the new values for n and r0.
(n, 1)``````

In the lambda `loop`, `n` is the value for which we still have to compute the factorial, and it decreases with each call. `r0` is the accumulated factorial that grows as we reduce the value of `n`.

Does this make sense?

]]>
Chunking iterables https://mathspp.com/blog/twitter-threads/chunking-iterables 2022-08-17T11:45:54+02:00 2022-08-17T00:00:00+02:00 This short article shows how to chunk iterables into pieces of a fixed length.

If you need to chunk a list or another iterable into groups of `n` items, you can do that with the built-in `zip`:

``````>>> def chunk(iterable, chunk_size):
...     return list(zip(*[iter(iterable)] * chunk_size))
...
>>> chunk(range(10), 5)
[(0, 1, 2, 3, 4), (5, 6, 7, 8, 9)]``````

You can also add a third argument to process each chunk after it is created:

``````>>> chunk("Hello, world", 3)
[('H', 'e', 'l'), ('l', 'o', ','), (' ', 'w', 'o'), ('r', 'l', 'd')]

# Redefine `chunk`:
>>> def chunk(iterable, chunk_size, on_chunk=None):
...     on_chunk = on_chunk or (lambda x: x)
...     return [on_chunk(chunk) for chunk in zip(*[iter(iterable)] * chunk_size)]
...
>>> chunk("Hello, world", 3, "".join)
['Hel', 'lo,', ' wo', 'rld']``````

The code above has a limitation, though: it ignores the last elements if the chunk size doesn't divide evenly into the length of the iterable. Sometimes this is ok... Sometimes, it is not.

If you use `itertools.zip_longest`, you get the opposite behaviour:

``````>>> chunk(range(8), 5)
[(0, 1, 2, 3, 4)]  # where are 5, 6, and 7??

>>> from itertools import zip_longest
>>> def chunk_longest(iterable, chunk_size):
...     return list(zip_longest(*[iter(iterable)] * chunk_size))
...

>>> chunk_longest(range(8), 5)
[(0, 1, 2, 3, 4), (5, 6, 7, None, None)]``````

By using `zip_longest`, you get padding with the value `None`. You could also customise this padding.

Finally, if you want your chunks to always have the same size, you can use `strict=True` in `zip`! This will make your `chunk` function error if the chunk size doesn't divide evenly! However, this only works in Python 3.10+ because that is when `zip(..., strict=True)` was added.

``````>>> def chunk_strict(iterable, chunk_size):
...     return list(zip(*[iter(iterable)] * chunk_size, strict=True))
...

# 2 divides into 6
>>> chunk_strict(range(6), 2)
[(0, 1), (2, 3), (4, 5)]

# 5 does NOT divide into 8
>>> chunk_strict(range(8), 5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in chunk_strict
ValueError: zip() argument 4 is shorter than arguments 1-3``````
]]>
bisect module https://mathspp.com/blog/twitter-threads/bisect-module 2022-08-10T16:20:19+02:00 2022-08-10T00:00:00+02:00 This short article shows how to use the module `bisect` from the Python standard library.

Are you familiar with the module `bisect` from the Python 🐍 standard library?

It is a small module to help you work with sorted lists. If you have a sorted list and a new value, you can find the index where the new value would go by using the method `bisect`:

``````>>> value = 22
>>> ordered_values = [1, 5, 19, 23]
# `value` would go at index 3 -^

>>> import bisect
>>> bisect.bisect(ordered_values, value)
3  # the index where `value` would go

>>> ordered_values.insert(3, value)
>>> ordered_values
[1, 5, 19, 22, 23]``````

The function `bisect` tells you where a value would go... But if you want to insert it, `bisect` can also do that for you! Just use the method `insort`:

``````>>> ordered_values
[1, 5, 19, 22, 23]

# Let's add 21 in there, preserving the order:
>>> bisect.insort(ordered_values, 21)
>>> ordered_values
[1, 5, 19, 21, 22, 23]``````

The module `bisect` has four more methods: `bisect_left`, `bisect_right`, `insort_left`, and `insort_right`... The `_left` and `_right` tell you where to place ties (elements that are equal).

The methods `bisect` and `insort`, shown above, match their `_right` variants.

]]>
Simplifying equality comparisons with or https://mathspp.com/blog/twitter-threads/simplifying-equality-comparisons-with-or 2022-08-05T14:16:42+02:00 2022-08-05T00:00:00+02:00 In this short article I describe how to simplify equality comparisons chained with the operator `or`.

Do you want to check if a Python 🐍 variable matches one of several possible values? Instead of writing a big chain of `or` and equalities `==`, use the `in` operator!

``````>>> x = 43
>>> if x == 42 or x == 43 or x == 44:
...     print("Nice!")
...
Nice!
>>> if x in (42, 43, 44):
...     print("Nice!")
...
Nice!``````

Using `in` is a great tip but it isn't always a suitable alternative! The operator `or` short-circuits, which means it stops comparing as soon as it finds a `True`. This isn't the case if you use `in`:

``````>>> def f():
...     return 42
...
>>> def g():
...     return 43
...
>>> def h():
...     return 1 / 0
...
>>> if x == f() or x == g() or x == h():
...     print("Nice!")
...
Nice!
>>> if x in (f(), g(), h()):
...     print("Nice!")
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in h
ZeroDivisionError: division by zero``````

You can read more about short-circuiting in this Pydon't article.

For a bonus tip, see @guilatrova's tweet on a similar tip:

]]>
Conditional expressions in list comprehensions https://mathspp.com/blog/twitter-threads/conditional-expressions-in-list-comprehensions 2022-07-01T00:26:29+02:00 2022-06-30T00:00:00+02:00 In this short article I describe how to use conditional expressions in list comprehensions.

Conditional expressions in list comprehensions can be quite powerful because they let us write more flexible comprehensions.

List comprehensions start with an expression that modifies values. Typically, you always modify values in the same way, with the same function or formula. However, sometimes you may want to adapt the modification to the value itself.

Here's an example using the Collatz sequence:

``````# Google for the Collatz conjecture;
# Take a number n, and do a step:
# odd numbers go n -> 3 * n + 1
# even numbers go n -> n // 2

# Compute 1 step for some initial numbers:
steps = []
for n in range(100):
if n % 2:
steps.append(3 * n + 1)
else:
steps.append(n // 2)``````

Notice how, in the example above, we always want to append to the final list. It's just that we want to append one of two different things, depending on `n % 2`. This is where a conditional expression comes in:

``````steps = []
for n in range(100):
# Compute next value depending on n % 2
step = 3 * n + 1 if n % 2 else n // 2
steps.append(step)``````

At this point, the classic list comprehension pattern arises and you can rewrite the loop above as a list comprehension. To give you a hand, I did it for you below. When using a conditional expression, I like to have the conditional expression by itself:

``````steps = [
3 * n + 1 if n % 2 else n // 2  # How to modify n
for n in range(100)             # Where to get n from
]``````

To conclude, beware of a source of confusion:

• conditional expressions in the beginning; vs
• using `if` after a `for` to filter values.

They do different things, and

• the former always looks like `... if ... else ...`; while
• the latter cannot have an `else`.
]]>
Nested list comprehensions https://mathspp.com/blog/twitter-threads/nested-list-comprehensions 2022-06-21T09:05:04+02:00 2022-06-20T00:00:00+02:00 In this short article I show you how to create nested list comprehensions.

Did you know that list comprehensions can be nested? It's not as bad as it sounds, I'll show you.

List comprehensions are expressions: it's code that evaluates to a result. And list comprehensions can contain arbitrary expressions on the left... So, list comprehensions can be nested!

To nest list comprehensions, focus on one list comprehension at a time! Here is an example:

``````# Want to produce this pattern:
# [[0, 1, 4], [9, 16, 25], [36, 49, 64]]

# Double loop:
result = []
for i in range(3):
inner = []                          # Convert the
for j in range(3):                  # inner loop
inner.append((3 * i + j) ** 2)  # first.
result.append(inner)``````

To convert the double loop into a list comprehension, we start by focusing solely on the inner loop:

``````# Outer loop, inner list comprehension:
result = []
for i in range(3):
inner = [(3 * i + j) ** 2 for j in range(3)]
result.append(inner)

# Without intermediate `inner` variable:
result = []
for i in range(3):
result.append([(3 * i + j) ** 2 for j in range(3)])
#                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^``````

Now that we converted the inner loop, we have the core loop pattern that hints at a list comprehension:

1. initialisation of an empty list;
2. a `for` loop; and
3. appending to the same list over and over again.

Thus, this can be converted to a list comprehension, as long as whatever is inside the call to `.append` goes in the beginning of the final list comprehension.

``````# Whatever was inside the `.append` goes in the beginning.
result = [[(3 * i + j) ** 2 for j in range(3)] for i in range(3)]
#         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^``````

Nested list comprehensions are at the frontier of what are acceptable list comprehensions. They are great when all the list comprehensions involved are short and straightforward.

By the way, when nesting list comprehensions, you may want to split them across multiple lines:

``````# This list comp...
result = [[(3 * i + j) ** 2 for j in range(3)] for i in range(3)]

# ... is the same as this one:
result = [
[(3 * i + j) ** 2 for j in range(3)]
for i in range(3)
]``````
]]>
List comprehensions in functional programming https://mathspp.com/blog/twitter-threads/list-comprehensions-in-functional-programming 2022-06-21T09:14:20+02:00 2022-06-18T00:00:00+02:00 In this short article I explain the relationship between list comprehensions and functional programming.

Did you know that list comprehensions are actually a great tool for functional programming?

The first thing that you need to understand is what the core of functional programming is. And while a bad CS class might make you think functional programming is all about `map` and `filter` and things like that... That's not what FP is about! FP is about purity...

Purity of what? Purity of results and behaviour. In functional programming you are not supposed to produce side-effects.

Proper discussion of this would make for a huge article, so let's stick with this:

In functional programming, we (typically) don't want side-effects.

List comprehensions provide just that: List comprehensions provide a way to transform iterables into new lists without having a single side-effect!

This is not true for the equivalent `for`-loop. Check the two examples below. The `for` loop produces a variable as a side-effect:

``````squares = []
for num in range(10):
squares.append(num ** 2)

# The loop is over and `num` still exists:
print(num)``````

But the list comprehension doesn't:

``````squares = [num ** 2 for num in range(10)]

# The list comp is over and `num` does NOT exist:
print(num)  # NameError: 'num' is not defined.``````

So, list comprehensions produce no side-effects by themselves... And that's also why you are typically advised against doing work in list comprehensions that produces side-effects.

For example, many people have sent me list comprehension solutions that look something like this:

``````squares = []
[squares.append(num ** 2) for num in range(10)]``````

What's the issue here? The issue is that the list comprehension is producing side-effects: As a side-effect to the list comprehension running, the list called `squares` is growing!

That's a big no no!

On top of that, you are defeating the purpose of list comprehensions... Because you used a list comprehension to build a list that you just ignored! So, for now, remember: list comprehensions should produce no side-effects.

And let us end with a quiz: What's the output of the code below? That should help clarify why it doesn't make much sense to use `.append` inside a list comprehension.

``````squares = []
list_comp = [squares.append(num ** 2) for num in range(10)]
print(list_comp)``````
]]>