Last active
August 7, 2017 06:24
-
-
Save hallazzang/2f92f0be42ce99ff321a887680bbae0b to your computer and use it in GitHub Desktop.
How to implement method overloading using metaclass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import inspect | |
from itertools import islice | |
class MethodList(list): | |
pass | |
class OverloadingClassDict(dict): | |
def __setitem__(self, name, value): | |
if callable(value) and name in self: | |
old_value = self[name] | |
if callable(old_value): | |
new_value = MethodList([old_value]) | |
elif isinstance(old_value, MethodList): | |
new_value = old_value | |
else: | |
raise ValueError('Name duplicated: %s' % name) | |
new_value.append(value) | |
else: | |
new_value = value | |
super().__setitem__(name, new_value) | |
class OverloadingClassMeta(type): | |
@classmethod | |
def __prepare__(cls, clsname, bases): | |
return OverloadingClassDict() | |
def __new__(cls, clsname, bases, clsdict): | |
clsobj = super().__new__(cls, clsname, bases, clsdict) | |
for name, value in clsdict.items(): | |
if not isinstance(value, MethodList): | |
continue | |
sigs = [inspect.signature(method) for method in value] | |
methods = list(zip(value, sigs)) | |
doc_lines = [] | |
for method, sig in methods: | |
doc_lines.append('%s%s\n %s' % ( | |
method.__name__, sig, method.__doc__ or 'undocumented' | |
)) | |
def _patched_func(self, *args, **kwargs): | |
for method, sig in methods: | |
try: | |
bound = sig.bind(self, *args, **kwargs) | |
except TypeError: | |
continue | |
for param, arg in islice(zip(sig.parameters.values(), | |
bound.arguments.values()), 1, None): | |
if param.annotation is not param.empty: | |
if not isinstance(arg, param.annotation): | |
break | |
else: | |
return method(self, *args, **kwargs) | |
raise TypeError('No matching arguments type') | |
_patched_func.__name__ = value[0].__name__ | |
_patched_func.__qualname__ = value[0].__qualname__ | |
_patched_func.__doc__ = '\n'.join(doc_lines) | |
setattr(clsobj, name, _patched_func) | |
return clsobj | |
class Base(metaclass=OverloadingClassMeta): | |
pass | |
class Foo(Base): | |
def bar(self): | |
print('bar with no argument') | |
def bar(self, x: int): | |
print('bar with integer argument: %d' % x) | |
def bar(self, x: str): | |
print('bar with string argument: %s' % x) | |
if __name__ == '__main__': | |
foo = Foo() | |
foo.bar() # bar with no argument | |
foo.bar(12) # bar with integer argument: 12 | |
foo.bar('lorem') # bar with string argument: lorem | |
foo.bar(3.14) # TypeError: No matching arguments type |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment