3 Lesser Known Python Features

Maxwell Conradt
3 min readNov 22, 2021

--

This post describes some nice functionality of Python’s list , set , and tuple data types. I recently learned about these, found them interesting, and wanted to share them with you, my fellow programmer.

Photo by Universal Eye on Unsplash

Sets

This is the most interesting part. We’ll learn about what the operators <, <=, >, >= do with set operands. It’s somewhat obvious what the == operator does with sets, but there’s interesting behavior when we venture into ordering sets. If you haven’t thought about this before, try guessing how the comparison operators work before reading further.

{'bread'} <= {'bread', 'butter'} 
# True
{'bread', 'jam'} <= {'bread', 'butter'}
# False
{'bread'} <= {'bread'}
# True

That’s right! <= is overloaded with issubset on set. There’s a slightly different behavior defined on the < operator:

{'bread'} < {'bread', 'butter'}
# True
{'bread'} < {'bread'}
# False

The difference is < defines whether the left operand is a strict subset of the right operand. This means the left operand is a subset of, but not equal to, the right operand. This can be a useful distinction, especially since there are not isstrictsubset or isstrictsuperset methods on set .

I wont go into the behavior of > and >= in quite as much detail, because you can more or less replace subset with superset and do the algebraic equivalent of multiplying by -1 in the above section. Let’s do just that:

{'bread', 'butter'} >= {'bread'}
# True
{'bread', 'jam'} >= {'bread', 'butter'}
# False
{'bread'} >= {'bread'}
# True
{'bread', 'butter'} > {'bread'}
# True
{'bread'} > {'bread'}
# False

Now you can chain comparisons on sets:

{'bread'} <= {'bread', 'butter'} <= {'bread', 'butter', 'eggs'}
# True

Here I’ve gratuitously added the equivalent code using issubset to show how far we’ve come:

{'bread'}.issubset({'bread', 'butter'}) and {'bread', 'butter'}.issubset({'bread', 'butter', 'eggs'})

Bonus

The humble ^ operator, which is overloaded with the symmetric difference. What does it mean? It’s the set of elements that appear in one, but not both of the left and right operands:

def symmetric_difference(a: set, b: set) -> set:    
return {elt for elt in a | b if (elt in a) ^ (elt in b)}
symmetric_difference({'bread', 'butter'}, {'bread', 'jam'})
# {'butter', 'jam'}
{'bread', 'butter'} ^ {'bread', 'jam'}
# {'butter', 'jam'}

Note the ^ in the set comprehension. It’s XOR all the way down!

Lists, Tuples, Strings, oh my!

Comparison operators on lists, tuples and strings work the same way. The operators <, <=, >, >= define a lexicographic order, which is a way of ordering sequences of objects based on an ordering of their elements. It’s the concept of alphabetical ordering, generalized to sequences containing any type of object with < and > operators defined. This is where things get weird!

Let’s start with strings:

'01' == '01'
# True
'01' < '10'
# True
'11' < '2'
# True
'2' > '10' > '1'
# True

Note this works the same way with lists:

[0, 1] == [0, 1]
# True
[0, 1] < [1, 0]
# True
[1, 1] < [2]
# True
[2] > [1, 0] > [1]
# True

And tuples:

(0, 1) == (0, 1)
# True
(0, 1) < (1, 0)
# True
(1, 1) < (2,)
# True
(2,) > (1, 0) > (1,)
# True

Bonus

You can even compare lists of heterogenous types:

[2, 'a'] < [2, 'b']
# True
[2, [1, 0]] < [2, [2, 1]] # (:
# True

Just make sure the types match element-wise:

[3, 4] < [3, 'a']
# TypeError: '<' not supported between instances of 'int' and 'str'

Conclusion

I hope this exploration got you thinking about what your programming language can and should do, as opposed to reusing snippets you’ve already seen. Next time you think of something and wonder if it works, try it out!

If you know of any quirky behavior of operators in Python or any other programming language, please share in the comments!

Thanks to Spencer Finkel and Cole Willenbring for reading drafts of this.

--

--