mathspp.com feed Stay up-to-date with the articles on mathematics and programming that get published to mathspp.com. 2021-10-12T15:00:09+02:00 Rodrigo Girão Serrão https://mathspp.com/blog/til TIL #012 – At operator for matrix multiplication https://mathspp.com/blog/til/012 2021-10-12T15:00:09+02:00 2021-10-12T00:00:00+02:00

Today I learned that Python 3.5+ supports the operator @ for matrix multiplication. # At operator @

Since Python 3.5, Python has the infix operator @. This operator was introduced with PEP 465 to be used in matrix multiplication.

You can try to use it with just vanilla Python, but no vanilla Python types define their behaviour with @:

>>> 3 @ 5
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for @: 'int' and 'int'

However, just looking at the error above, you see that the error is in @ not knowing what to do with integers. The error is not the fact that @ is an invalid operator! So cool!

# Matrix multiplication in numpy with @

If you have numpy at hand, you can check @ works, because numpy arrays added support to be used with @:

>>> import numpy as np
>>> np.random.rand(3, 3) @ np.random.rand(3, 3)
array([[0.89431673, 0.57949659, 0.59470797],
[0.47364302, 0.29837518, 0.33552972],
[1.12634752, 0.75218169, 0.78876082]])
>>> _ @ np.eye(3)   # The identity (eye) matrix leaves the other matrix unchanged.
array([[0.89431673, 0.57949659, 0.59470797],
[0.47364302, 0.29837518, 0.33552972],
[1.12634752, 0.75218169, 0.78876082]])

# Using @ with custom classes/types

If you want your own objects to add support for @, all you have to do is implement the dunder methods __matmul__ and __rmatmul__:

>>> class Dummy:
...     def __matmul__(self, other):
...         print("Works!")
...         return 42
...     def __rmatmul__(self, other):
...         print("Also works!")
...         return 73
...
>>> d = Dummy()
>>> d @ 1
Works!
42
>>> 1 @ d
Also works!
73

There's also the __imatmul__ method for in-place matrix multiplication:

>>> class Dummy:
...     def __imatmul__(self, other):
...         print("In-place!")
...
>>> d = Dummy()
>>> d @= 1
In-place!

Of course, this silly example above doesn't show you the proper semantics of the __matmul__, __rmatmul__, and __imatmul__ methods. It just shows you they exist and they interact with the operator @!

By the way, for reference, here is the tweet that showed me this:

That's it for now! Stay tuned and I'll see you around!

]]>
TIL #011 – emojis in Python with pythonji https://mathspp.com/blog/til/011 2021-10-12T15:00:09+02:00 2021-10-11T00:00:00+02:00

Today I learned that you can use emojis as variable names in Python if you use pythonji. # Can you use emojis in Python?

No! At the time of writing, emojis are not valid Python identifiers. This means that this code fails:

>>> 🍔 = "hamburguer"   # SyntaxError

However, if you install the package pythonji , you will be able to run code like that!

Installing pythonji is as easy as python -m pip install pythonji!

With pythonji installed, we can run programs that make use of emojis!

Here's a little program I wrote:

import enum

class 🍽(enum.Enum):
🍔 = "hamburguer"
🍕 = "pizza"
🍅 = "tomato"
🥕 = "carrot"

class 🧍:
def __init__(🤳, 😋👍):
🤳.😋👍 = 😋👍

def 🍽(🤳, 😋):
if 😋 in 🤳.😋👍:
return "Yummi!"
else:
return "Ok, I'll eat that."

👨 = 🧍([🍽.🍕, 🍽.🥕])
print(👨.🍽(🍽.🍕))
print(👨.🍽(🍽.🍅))

Save it to the file foo.🐍 (yes, the extension really is 🐍!).

Now, run it with pythonji foo.🐍 and this is the output:

 > pythonji foo.🐍
Yummi!
Ok, I'll eat that.

Amazing, right? 😆

That's it for now! Stay tuned and I'll see you around!

]]>
TIL #010 – generator return https://mathspp.com/blog/til/010 2021-10-10T01:56:57+02:00 2021-10-09T00:00:00+02:00

Today I learned that Python generators can return a value. # Generators

Generators are interesting Python objects that produce a series of values, but one by one. In a way, they can be thought of as stateful functions. (That is, functions with state.)

What I learned is that generators can also return something. Here is the tweet that prompted this discovery:

# Returning from a generator

When you use a return inside a generator, the generator will have that returned information in its StopIteration exception when it's done:

>>> def f():
...     yield 10
...     return 20
...
>>> gen = f()
>>> next(gen)
10
>>> next(gen)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration: 20

If you want to get access to that value, you just need to catch the exception:

>>> gen = f(); next(gen);
10
>>> try:
...     next(gen)
... except StopIteration as e:
...     val = e.value
...
>>> val
20

That's it for now! Stay tuned and I'll see you around!

]]>
TIL #009 – vars https://mathspp.com/blog/til/009 2021-10-07T23:30:33+02:00 2021-10-07T00:00:00+02:00

Today I learned about the built-in function vars in Python. # vars

Python has a bunch of built-in functions. Like, a lot! It's very difficult to keep track of all of them, remember them, and use them correctly.

A built-in function I just learned about is vars. I heard about it from Reuven Lerner (a Python trainer) in his newsletter “Better Developers”. (Disclaimer: that's a referral link, but I am a subscriber and avid reader of the newsletter, so it is a very honest recommendation!)

Looking at the Python documentation, we can see what vars does:

“Return the __dict__ attribute for a module, class, instance, or any other object with a __dict__ attribute.”

The documentation also goes on to say that “without an argument, vars() acts like locals().”. So that's not useful because we can always use locals().

When vars really shines is when you give it an argument, like a module or a class instance!

>>> class Person:
...     def __init__(self, name):
...         self.name = name
...
>>> p = Person("me")
>>> vars(p)
{'name': 'me'}

So, we can see that vars is a very handy way of inspecting an instance of a class you defined. Quite cool, right?

For things like built-in classes, or modules, vars is similar to dir. Recall that dir lists the names of all the attributes of an object, but vars will give you a mapping with the names of the attributes and the corresponding values:

>>> import math
>>> dir(math)
['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'comb', 'copysign', 'cos', 'cosh', 'degrees', 'dist', 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', 'isqrt', 'lcm', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan', 'nextafter', 'perm', 'pi', 'pow', 'prod', 'radians', 'remainder', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'tau', 'trunc', 'ulp']
>>> vars(math)
{'__name__': 'math', '__doc__': 'This module provides access to the mathematical functions\ndefined by the C standard.', ...}

That's it for now! Stay tuned and I'll see you around!

]]>
TIL #008 – two pass join https://mathspp.com/blog/til/008 2021-10-07T23:30:33+02:00 2021-09-30T00:00:00+02:00

Today I learned that the .join method in Python is a two-pass algorithm, and that's why joining a list comprehension is faster than a generator expression. # Context

In my talk, I walk you through successive refactors of a piece of “bad” Python code, until you reach a “better” piece of Python code. The talk follows closely the “bite-sized refactoring” Pydont'.

In the talk, I started with this code:

def myfunc(a):
empty=[]
for i in range(len(a)):
if i%2==0:
empty.append(a[i].upper())
else:
empty.append(a[i].lower())

return "".join(empty)

# ---
>>> myfunc("Hello, world!")
'HeLlO, wOrLd!'
>>> myfunc("Spongebob.")
'SpOnGeBoB.'

And refactored it up until this point:

def alternate_casing(text):
return "".join([
char.lower() if idx % 2 else char.upper()
for idx, char in enumerate(text)
])

# .join

In a feedback tweet, someone said that I could've removed the [] of the list comprehension. That would, instead, define a generator expression, that should even be faster.

Thankfully, I was prepared for that: before the talk, I checked the performance of both alternatives, and found out that the generator expression was not faster.

That surprised me, because I was under the impression that generator expressions tend to be faster that list comprehensions, but the data in front of me didn't lie... And so, I didn't change the list comprehension into a generator expression in the talk...

But why is the generator expression slower?

As it turns out, the .join method is a two-pass algorithm!

At least, according to a tweet that was quoted to me:

Sadly, I haven't been able to verify this yet.

However, it does make some sense: if .join really is a two-pass algorithm, then generator expressions hurt us, because generators can only be traversed once. Therefore, we need to do something before .join can actually start its work.

Peeking at the source code for .join will probably reveal this...

I'm a bit sleepy, but I'll interrupt typing right now to take a quick peek at the source, see if I can find this code.

[Me browsing through Python's source code.]

Ok, I think I found it 🎉🎉!

In Python's GitHub repo, there's an Objects folder that contains a stringlib that has a join.h file. Opening it, you can see that there are two for loops over the data.

In the exact commit I looked at, at the beginning of the 30th of September of 2021, those two for loops start...

]]>
TIL #007 – math.nextafter https://mathspp.com/blog/til/007 2021-10-07T23:30:33+02:00 2021-09-29T00:00:00+02:00

Today I learned about the math.nextafter method. # math.nextafter

0 is a neat number, isn't it?

Perhaps one of the greatest discoveries of mankind.

But what's the number that comes after 0? That would be the smallest number in the set $$]0, +\inf[$$, if you're familiar with the mathematical notation for sets.

In short, $$[a, b]$$ is the contiguous set of numbers $$x$$ that satisfy the restriction $$a \leq x \leq b$$. Notice how $$a$$ and $$b$$ are included inside $$[a, b]$$ because the brackets are closed. If the brackets are open, then that number is not included.

For the intervals below, $$x$$ belongs to it if...

• $$[a, b]$$$$a \leq x \leq b$$;
• $$[a, b[$$$$a \leq x < b$$;
• $$]a, b]$$$$a < x \leq b$$; and
• $$]a, b[$$$$a < x < b$$.

So, in $$]0, +\infty[$$, nor 0, nor $$+\infty$$ are included. Thus, what's the minimum element of this interval? Well, there isn't any!

Mathematically speaking, there is no minimum in the interval $$]0, +\infty[$$. Why not? Whatever you pick as a potential minimum $$m$$, $$m/2$$ will be smaller than $$m$$ and still be greater than $$0$$, that is, $$0 < m/2 < m < +\infty$$, and so $$m/2$$ is in $$]0, +\infty[$$.

That's interesting, right?

But this is a whole other story if we go into the programming real! Because of how floats are represented, Python has a number that comes immediately after 0. So, what is it?

Here it is:

>>> import math
>>> math.nextafter(0, 1)
5e-324

That's $$5 \times 10^{-324}$$, it's freakishly small!

So, what's the role of the math.nextafter method?

>>> help(math.nextafter)
Help on built-in function nextafter in module math:

nextafter(x, y, /)
Return the next floating-point value after x towards y.

Hence, nextafter looks at x and then checks what's the float that's immediately next to x, if you walk in the direction of y. If I set x to zero and y to one, I get the smallest float that Python can represent on my machine.

So, what's the next float that Python can represent after 1?

Give it some thought.

Here it is:

>>> math.nextafter(1, 999)
1.0000000000000002

I'll be honest, for a second I thought it should've been 1 + 5e-324, but it makes sense it wasn't that. Floating point numbers have limited precision, right? And one thing that's limited is the size of the mantissa: the stuff that comes after the decimal point.

Above, we can see that the mantissa has 16 digits, and that's the size of the mantissa in Python.

So, what's the next number after 10?

Give it some thought.

Here it is:

>>> math.nextafter(10, 999)
10.000000000000002

If you count, now there's only 15 digits to the right of the decimal point... But...

]]>
TIL #006 – file unpacking https://mathspp.com/blog/til/006 2021-10-07T23:30:33+02:00 2021-09-24T21:00:00+02:00

Today I learned that files can also be unpacked in Python. # File unpacking

After learning that strings can be unpacked in Python, I shared the short article on Twitter.

As a reply, @dabeaz suggested I tried doing it with a file:

After seeing how strings can be unpacked, unpacking a file didn't look so weird, but it was still a pleasant surprise!

But it “does make sense”, after all you can iterate directly over a file, which essentially iterates over the lines of the file.

Grab a CSV file "my_data.csv", for example with this data:

Name, Surnames
John, Doe
Mary, Smith

Then, in your Python REPL, you can get this to work:

>>> header, *data = open("my_data.csv")
'Name, Surnames\n'
>>> data
['John, Doe\n', 'Mary, Smith\n']

It is not as useful as using the [csv][csv] module to read the CSV data in and process it, but it is still a nifty trick.

Come to think of it, if there is a place when this will be useful, probably won't be with CSV files...

I'll let you know if I put this little trick to good use!

That's it for now! Stay tuned and I'll see you around!

]]>
TIL #005 – string unpacking https://mathspp.com/blog/til/005 2021-10-07T23:30:33+02:00 2021-09-22T00:00:00+02:00

Today I learned strings can also be unpacked in Python. # String unpacking

I've written a couple of Pydon't articles about unpacking before, namely one about unpacking with starred assignments, and another one about deep (structural) unpacking.

Having said that, I have no idea why I was so surprised, earlier today, when I found out that strings can be unpacked in Python:

>>> a, b = "Hi"
>>> a
'H'
>>> b
'i'

In hindsight, I already possessed all the knowledge to arrive at this conclusion...

And yet, when I saw it in my face, it baffled me!

Now, whether or not this is a helpful thing... That's a whole different discussion!

But there you have it, something interesting about Python.

(Pssst, no one else is looking, check out this horror:

>>> first, *middle, last = "Hello, world!"
>>> first
'H'
>>> middle
['e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd']
>>> last
'!'

Would you get fired if you wrote things like this in production?)

Here's the tweet from where I learnt this:

That's it for now! Stay tuned and I'll see you around!

]]>
TIL #004 – C3 linearisation https://mathspp.com/blog/til/004 2021-10-07T23:30:33+02:00 2021-09-20T00:00:00+02:00

Today I learned about an algorithm that Python uses to sort out inheritance. From the Wiki article on C3 linearisation, by H2power, shared under the CC BY-SA 4.0 license.

# C3 linearisation algorithm

Take the following piece of code, which defines a series of classes where hierarchical dependencies are represented in the diagram above, where arrows indicate which classes inherit from which classes.

class O:
def __repr__(self):
return "O"

class A(O):
def __repr__(self):
return "A"

class B(O):
def __repr__(self):
return "B"

class C(O):
def __repr__(self):
return "C"

class D(O):
def __repr__(self):
return "D"

class E(O):
def __repr__(self):
return "E"

class K1(A, B, C):
def __repr__(self):
return "K1"

class K2(D, B, E):
def __repr__(self):
return "K2"

class K3(D, A):
def __repr__(self):
return "K3"

class Z(K1, K2, K3):
def __repr__(self):
return "Z"

z = Z()
print(z)

What's the output of the code above? If you guessed "Z", you got it right!

Now, modify the class Z and remove its __repr__ method, like so:

class Z(K1, K2, K3): pass

z = Z()
print(z)

What's the output now? When we print z, we try to look for a __str__ or __repr__ implementation (read this article to understand why), and the class Z provides none. So, in order to print z, we need to go up the hierarchy and look for a way to print z. Next up in the inheritance hierarchy is K1, so the output is K1.

Modify K1 as well, to remove its __repr__, like so:

class K1(A, B, C): pass

class Z(K1, K2, K3): pass

z = Z()
print(z)

What's the output now?

It should be K2.

And we can keep going over this process of removing one __repr__ at a time, and this shows the hierarchy that Python builds for us, when we have multiple inheritance at stake.

Of course, in order to check the full hierarchy, we could keep doing this by hand, or we could just ask Python for the linearised hierarchy:

>>> Z.__mro__
(<class '__main__.Z'>, <class '__main__.K1'>, <class '__main__.K2'>, <class '__main__.K3'>, <class '__main__.D'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.E'>, <class '__main__.O'>, <class 'object'>)

This shows that we first look things up on Z, then on K1, then on K2, ...

This linearised hierarchy is built by a straightforward algorithm called the “C3 linearisation algorithm”, that you can find on Wikipedia.

The algorithm isn't that complicated (the description is in the link above) and I might actually implement it in Python in the near future, just for fun.

This came to my attention as I was reading this article that was shared with me on Twitter, in a discussion related to yesterday's TIL article, about the ICPO rule for attribute lookup.

That's it for now! Stay tuned and I'll see you around!

]]>
TIL #003 – ICPO rule for attribute lookup https://mathspp.com/blog/til/003 2021-10-07T23:30:33+02:00 2021-09-19T00:00:00+02:00

Today I learned about the ICPO rule for attribute lookup in Python. # ICPO rule for attribute lookup

How does Python do attribute lookup when dealing with objects?

It uses the ICPO rule: it checks the instance, then its class, then the parent(s), and finally the object object.

I was attending Reuven Lerner's Advanced Python objects class today, and Reuven really nailed down the explanation for something that I had an intuitive understanding, but that I had never seen laid out so cleanly.

This ICPO rule tells us where Python looks whenever we try to access an attribute or a method of an object.

The I, standing for Instance, means that any object instance may have things that are its own. Those things, that are unique to that instance, take precedence over everything else.

After that, comes the C, that stands for Class. These are the things that are defined for the class – and this is what caught me off guard: methods are generally looked up at this level!

Here's an example:

class Person:
def __init__(self, name):
self.name = name

def greet(self):
return f"Hello, {self.name}."

p = Person("Rodrigo")
print(p.greet())

When Python finds the p.greet() piece of code, it realises it has to look up greet in p. First, it checks the instance for something called greet, and it finds nothing.

Then, it looks on p's class, which is Person. Person defines a greet, so that's what we run. And this is what was wrong in my mind! I thought the greet function that is being called belonged to the instance, but it doesn't.

To show you it doesn't, I'll add a greet method that does belong to the instance:

class Person:
def __init__(self, name):
self.name = name

def greet(self):
return f"Hello, {self.name}."

p = Person("Rodrigo")
p.greet = lambda: "Go away!"

print(p.greet())    # Go away!
del p.greet         # Deletes the instance's greet.
print(p.greet())    # Hello, Rodrigo.

As you can see, the p.greet = lambda: ... shadowed the class's greet!

Then the rule goes on to tell you that, after checking the class, we check the parent classes: this is useful if there's inheritance going on.

Finally, we check the object object, from which all classes inherit. (So, in a way, I guess object could be included in the “P” of the rule! But I think it's nice to have it explicit here.)

In the Person example, the fact that we can go all the way up to object when doing attribute lookup is useful when, for example, we print our Person objects:

print(p)    # <__main__.Person object at 0x0000012A5C9E7520>

Even though I did not define the dunder methods __str__ or __repr__, the print statement worked because object defines __repr__ for me. That's where Python went to get the representation of my Person instance.

I learned much more in Reuven's class, but most of it is still being processed by my tired brain... So, stay tuned and I'll see you around!

]]>