Historical Values in Python

Often in programming we have some "constant" value that we define as a variable in Python. But the only try constant is change.

Consider for example you have a program that needs to process a file with a certain filename. So you might have something like:

FILENAME = "foo.txt"

data = Path(FILENAME).read_text(encoding="UTF-8")

This program is installed and runs for a while, but then at some point it is decided that instead of "foo.txt" we want the file name to be "bar.txt". It's easy to change our FILENAME variable to "bar.txt", but what about those existing systems that ran the program and have the filename foo.txt? You might see something like this:

FILENAME = "bar.txt"
OLD_FILENAME = "foo.txt"

if (path := Path(OLD_FILENAME)).exists():
    path = path.rename(FILENAME)
else:
    path = Path(FILENAME)

I was thinking wouldn't it be nice if FILENAME knew its old value? Wouldn't it even be nicer if FILENAME knew about all it's "historic values"? That's when I came up with something like this:

class History:
    def __new__(cls, history):
        hist = [*history]
        value = hist[-1]
        base = type(value)

        return base.__new__(type("History", (History, base), {"_history": hist}), value)

    @property
    def previous(self):
        return type(self)(self._history[:-1])

    @property
    def first(self):
        return type(self)([self._history[0]])

    def __getattr__(self, name):
        if not name.startswith("v"):
            raise AttributeError(name)

        version = int(name[1:]) if name[1:].isdigit() else 0
        if 0 < version <= len(self._history):
            return type(self)(self._history[:version])

        raise ValueError(f"Invalid version: {name}")

Instances of the History class look like plain values but have a history, for example:

>>> FILENAME = History(["foo.txt", "bar.txt", "baz.txt"])
>>> FILENAME
'baz.txt'
>>> FILENAME.previous
'bar.txt'
>>> FILENAME.previous.previous
'foo.txt'
>>> FILENAME.v1
'foo.txt'

So now let's rewrite the previous code example:

FILENAME = History(["foo.txt", "bar.txt"])

if (path := Path(FILENAME.previous)).exists():
    path = path.rename(FILENAME)
else:
    path = Path(FILENAME)

I probably wouldn't do anything like this in production code, but I do think it's pretty interesting and demonstrates the flexibility of Python for creating "Pythonic" interfaces.