How does Python’s dict manage types as keys when __hash__ and __eq__ are not callable without arguments?

Python doc states that dict keys must be hashable, which implies having __hash__ and __eq__

I’m trying to understand how using types as keys in a dict is able to work when, by default at least, both these methods expect an instance as the first parameter.

Consider the code below and it’s output:

class Apple:
    pass

class Orange:
    pass

lookup = {Apple: lambda: print('Apple!'), Orange: lambda: print('Orange!')}

try:
    print(Apple.__hash__())
except Exception as e:
    print(e)
    
try:
    print(Apple.__eq__())
except Exception as e:
    print(e)

an_apple = Apple()
an_orange = Orange()

lookup[type(an_apple)]()
lookup[type(an_orange)]()
descriptor '__hash__' of 'object' object needs an argument
descriptor '__eq__' of 'object' object needs an argument
Apple!
Orange!

How is dict both getting the hash and checking equality of the types?

Answer

Python doesn’t look up magic methods on the instance. It looks them up on the class itself. In your case, the class of Apple is type.

>>> class Apple: pass
... 
>>> hash(Apple)
5929200437980
>>> type.__hash__(Apple)
5929200437980

The same thing happens for __eq__. This can also be seen if you try to attach magic methods to an instance rather than a class.

>>> class Apple:
...   def __hash__(self):
...     return 1
... 
>>> my_apple = Apple()
>>> my_apple.__hash__ = lambda: 2
>>> hash(my_apple) # 1, not 2
1
>>> my_apple.__hash__()
2
>>> Apple.__hash__(my_apple)
1

Note that Apple and my_apple both have __hash__ defined on them, but it’s the class’ method that gets called, not the one on the instance.