Videos
No, this is not possible in Python (or most/any other languages). For any function that does slightly complicated branching or even common math operations, it is completely impossible to undo. One very simple example:
def foo(a, b):
return a + b
foo.undo(6) # made-up example syntax
What should this return? 0 and 6, or maybe -13 and 19? A reversible function needs to have an unambiguous mapping from both input to output and output to input. Anything in the function that would cause two different inputs to create the same output will break this mapping.
Especially in your example, you are utilizing randomness. How could the program even know what to undo if it is randomized? The mapping isn't even consistent for input to output, much less the other way around.
If you want to do something like this, you could use simple substitutions only (such as rot13), which provides a direct mapping between characters. You could also keep track of previous values in a dict with the function results being the keys, which would work because it is creating the mapping as it goes. This obviously would not work on its own across multiple runs of the program though, as it would not preserve the mapping.
Whichever method you choose, you will definitely need to write your own undo function.
Many answers have pointed out how what you're describing (reversing the actions of a generic function) is mathematically impossible. I'll show you here a way you could accomplish this under some very specific circumstances, though I will hasten to point out that they are correct -- this will not work in the general case.
However, if you're frequently round-tripping these results, it might be helpful to memoize the results of your hash function and do a reverse lookup.
# This code assumes that the memo dictionary need not be bounded in size.
# A real implementation will likely include a method to cull old results
# once the memo reaches a certain size. See `functools.lru_cache` for a
# specific example of this made for speeding up repeated function calls.
_memo_dict = dict()
def hash(p):
# produces strlist as above, then...
_key = tuple(strlist)
_memo_dict[_key] = p
def unhash(hashed_p: list[int]) -> str:
cache_hit = _memo_dict.get(tuple(hashed_p))
if cache_hit is not None:
# we've previously hashed a string to get this
# value so we can skip the calculations to reverse
# the process and just hand back the result
return cache_hit
# otherwise, you should have some way to reverse it
# manually here.