Skip to content

Instantly share code, notes, and snippets.

@hallazzang
Last active August 7, 2017 06:24
Show Gist options
  • Save hallazzang/2f92f0be42ce99ff321a887680bbae0b to your computer and use it in GitHub Desktop.
Save hallazzang/2f92f0be42ce99ff321a887680bbae0b to your computer and use it in GitHub Desktop.
How to implement method overloading using metaclass.
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