from collections import defaultdict
from functools import wraps

def affinity(args):
    def keyfunc(function):
        return sum(arg == annotation for arg, annotation in zip(args[1:], function.__annotations__.values())) - len(function.__annotations__)
    return keyfunc


def best_match(args, functions):
    return max(functions, key=affinity(args))


def make_dispatcher(alternatives):
    @wraps(alternatives[0])
    def dispatch(*args, **kwargs):
        return best_match(args, alternatives)(*args, **kwargs)
    return dispatch


class MultiNamespaces(dict):
    def __getitem__(self, key):
        return super().__getitem__(key)[-1]

    def __setitem__(self, key, value):
        self.setdefault(key, []).append(value)

    def to_dict(self):
        frozen = {}
        for key, values in self.items():
            if callable(values[-1]):
                frozen[key] = make_dispatcher(values)
            else:
                frozen[key] = values[-1]
        return frozen



class Overloader(type):
    @classmethod
    def __prepare__(cls, name, bases, **kwds):
        return MultiNamespaces()

    def __new__(cls, name, bases, ns, **kwargs):
        return super().__new__(cls, name, bases, ns.to_dict(), **kwargs)


class Overloading(metaclass=Overloader):
    ...


class Fib(Overloading):
    def fib(self, n: 0):
        return 1

    def fib(self, n: 1):
        return 1

    def fib(self, n):
        return self.fib(n - 1) + self.fib(n - 2)


print(Fib().fib(10))
