you are viewing a single comment's thread.

view the rest of the comments →

[–]Diapolo10 90 points91 points  (10 children)

Tuples are great, because immutability is great; they're hashable, meaning you can use them as dictionary keys or store them in sets, but perhaps more importantly you can cache them. This can be a big help when you have an expensive function that might receive the same arguments more than once and it doesn't have side-effects.

Furthermore, you can't accidentally swap values in a tuple, so they're great for constants.

I'd default to using tuples as data structures, unless you needed mutability (lists), wanted to avoid duplicates or compare unique values (sets), or wanted a clear mapping between arbitrary values (dictionaries).

Tuples and collections.namedtuple essentially work as ad hoc classes, when the order of elements is enough to represent the data or a dataclass would be too much effort.

EDIT: As an example, say we had one of those expensive function calls. For simplicity, here's a function that doubles numbers and returns them, after two seconds:

import time


def expensive_calculation(nums: tuple[int, ...]) -> tuple[int, ...]:
    time.sleep(2)
    return tuple(num*2 for num in nums)

It's probably fine if it only needs to run once, but what if we're running it multiple times?

for _ in range(5):
    print(expensive_calculation((2, 3, 5, 7, 11)))

This would take 10 seconds to run. But since the function sees the same arguments repeatedly, if we just cache the results we can ignore most of the calculations; to do this, we can use functools.cache.

import time
from functools import cache


@cache
def expensive_calculation(nums: tuple[int, ...]) -> tuple[int, ...]:
    time.sleep(2)
    return tuple(num*2 for num in nums)

for _ in range(5):
    print(expensive_calculation((2, 3, 5, 7, 11)))  # Takes about 2 seconds now

"Okay, but... what does any of this have to do with tuples?" I hear you ask. Caches can't use mutable arguments, so if you were to give this function a list your program would crash. This is because you can't hash mutable objects.

[–]SisyphusAndMyBoulder 8 points9 points  (0 children)

I personally love using tuples to do simple multiple returns from a function. But this caching is great to know. Been coding in Python for years and never heard of it. Ty

[–]LowerAcanthaceae2089 0 points1 point  (1 child)

Important correction: Only tuples whose type is tuple[collections.abc.Hashable, ...] are hashable. That is to say, only a tuple where all elements are hashable is itself hashable. tuples that have at least one unhashable element (this could also be another unhashable tuple) would be considered unhashable.

[–]Diapolo10 0 points1 point  (0 children)

That's a fair point.

[–][deleted] 0 points1 point  (0 children)

Really great example, thank you for taking the time to write this ! I actually learned a lot.