Requirements
Can we write a function f which:
- takes a single argument as input.
- returns an integer
- has the property that
f(x) == f(y)if and only ifxis exactly the same asyorxis a copy ofy.
We say that "x is exactly the same as y or x is a copy of y" if at least one of the following conditions is met:
id(x) == id(y)returnsTruerepr(x) == repr(y)returnsTruex is yreturnsTruey is xreturnsTruex == copy.deepcopy(y)returnsTruey == copy.deepcopy(x)returnsTrue
I realize that our function f will never be fool-proof, but please assume that nobody is going to define a new class with a really weird __eq__ method.
Also assume that no adversary is going to define a new class with a truly bizarre __copy__ method or some sort of pathological __deepcopy__().
One Attempt at Defining f
My feeble attempt at defining a function f is shown below:
pi = lambda k_1, k_2: (1/2)*(k_1 + k_2)(k_1 + k_2 + 1)+k_2
def f(x:object) -> int:
"""
Based on something called "Cantor's pairing function"
from the field of mathematics.
"""
k_1 = hash(x)
k_2 = hash(type(x))
if k_1 < 0 or k_2 < 0:
raise NotImplementedError()
return pi(k_1, k_2)
Flaws in my solution
There are some issues:
- I worry that two different objects will have the same hash value. For example, maybe
hash([1, 2]) == hash([2, 3]). The two lists[1, 2]and[2, 3]are different, but might have different hash values. Feel free to correct me if I am wrong. - my solution only works when
hash()returns a non-negative integer.
id(x) == id(y) versus x == y
x == y is implemented as x.__eq__(y)
Note that the id of an object is generally not the same as the id of a copy of that object. Usually, an object and a copy of the object usually reside at different memory addresses.
The following code demonstrates the difference between __eq__() and id():
x = [1, 2, 3]
y = [1, 2, 3]
print("id(x)".ljust(15), id(x))
print("id(y)".ljust(15), id(y))
print("x is x".ljust(15), x is x)
print("x is y".ljust(15), x is y)
print("x == x".ljust(15), x == x)
print("x == y".ljust(15), x == y)
We have:
id(x) 2531180442688
id(y) 2531180443264
x is x True
x is y False
x == x True
x == y True
Worry a little bit, but not too much about custom-made __repr__ or __eq__
I realize that our function f will never be fool-proof, but please assume that nobody is going to define a new class with a really weird __eq__, __hash__ method, or __eq__ method.
For example, someone could (in theory) define the following:
import random
class NoNoNo:
def __init__(self, *args):
self.myVal = random.randint(1, 9)
def __eq__(self, other):
return False
def __repr__(self):
return "$"
Objects instantiated from the NoNoNo class are never equal to each-other.
import copy
x = NoNoNo()
y = copy.deepcopy(x)
print("x == y?", "Yes" if x == y else "No") # prints "x == y? No"
Also, repr(x) is always the same as repr(y)
import copy
x = NoNoNo()
y = NoNoNo() # `y` IS VERY DIFFERENT FROM `x`
print("repr(x) == repr(y)?", "Yes" if repr(x) == repr(y) else "No") # prints "repr(x) == repr(y)? Yes"