Initial commit
This commit is contained in:
68
function_overloading/overload.py
Normal file
68
function_overloading/overload.py
Normal file
@ -0,0 +1,68 @@
|
||||
# https://www.codewars.com/kata/5f24315eff32c4002efcfc6a
|
||||
|
||||
import inspect
|
||||
|
||||
# https://docs.python.org/3.8/reference/datamodel.html#customizing-class-creation
|
||||
# https://snarky.ca/unravelling-pythons-classes/
|
||||
# https://gist.github.com/tmr232/c7214d62607ba1fc0e0a2e82d7e041cd
|
||||
class DispatchProxy:
|
||||
def __init__(self, name):
|
||||
self.registry = {}
|
||||
self.name = name
|
||||
|
||||
def register(self, f):
|
||||
print(f"register {inspect.signature(f)}")
|
||||
argcount = f.__code__.co_argcount
|
||||
self.registry[argcount] = f
|
||||
"""
|
||||
def decorator(func):
|
||||
argcount = f.__code__.co_argcount
|
||||
self.registry[argcount] = f
|
||||
return func
|
||||
return decorator
|
||||
"""
|
||||
|
||||
def __call__(self, *args):
|
||||
argcount = len(args) + 1 # add 1 because the first argument of a method is "self"
|
||||
if argcount in self.registry:
|
||||
resolved = self.registry[argcount]
|
||||
print(f"resolved: {resolved.__name__}{inspect.signature(resolved)}")
|
||||
return resolved(self, *args)
|
||||
else:
|
||||
raise AttributeError(f"No matching function for args: {args}")
|
||||
|
||||
|
||||
class MyNamespace(dict):
|
||||
def __getitem__(self, key):
|
||||
#(f"get {key}")
|
||||
return super().__getitem__(key)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
#sign = inspect.signature(value) if callable(value) else value
|
||||
#print(f"set {key} {value}")
|
||||
|
||||
# already exists => overload
|
||||
is_method = callable(value) and not value.__name__.startswith("__")
|
||||
|
||||
if is_method and key in self:
|
||||
m = self[key]
|
||||
if not isinstance(m, DispatchProxy):
|
||||
m = DispatchProxy(self)
|
||||
m.register(self[key])
|
||||
m.register(value)
|
||||
super().__setitem__(key, m)
|
||||
else:
|
||||
super().__setitem__(key, value)
|
||||
|
||||
|
||||
class Meta(type):
|
||||
@classmethod
|
||||
def __prepare__(cls, name, bases):
|
||||
return MyNamespace()
|
||||
|
||||
def __new__(cls, name, bases, dct):
|
||||
x = super().__new__(cls, name, bases, dct)
|
||||
return x
|
||||
|
||||
def __init__(self, name, bases, attrs):
|
||||
super(Meta, self).__init__(name, bases, attrs)
|
160
function_overloading/tests.py
Normal file
160
function_overloading/tests.py
Normal file
@ -0,0 +1,160 @@
|
||||
import codewars_test as test
|
||||
from overload import Meta
|
||||
|
||||
cng_msg = 'Dont change the properties of class.'
|
||||
sld_work = 'Function overloading should work.'
|
||||
|
||||
|
||||
@test.describe('Python Recipes #1 : Function Overloading')
|
||||
def _():
|
||||
|
||||
# implement meta class -----
|
||||
|
||||
def build(): # returning class to regenerate accordingly.
|
||||
|
||||
class Overload(metaclass=Meta):
|
||||
|
||||
CLS_VAR = 42
|
||||
|
||||
def __init__(self):
|
||||
self.a = 1
|
||||
self.no = 'This is "No parameter" function.'
|
||||
self.single = 'This is "Single parameter" function'
|
||||
self.two = 'This is "Two parameter" function'
|
||||
self.three = 'This is "Three parameter" function'
|
||||
|
||||
def foo(self):
|
||||
return self.no
|
||||
|
||||
def foo(self, x):
|
||||
return self.single + ':' + str(x)
|
||||
|
||||
def foo(self, x, y):
|
||||
return self.two + ':' + str(x) + ',' + str(y)
|
||||
|
||||
def foo(self, x, y, z):
|
||||
return self.three + ':' + str(x) + ',' + str(y) + ',' + str(z)
|
||||
|
||||
def extra(self):
|
||||
return 'This is extra method.'
|
||||
|
||||
return Overload
|
||||
|
||||
@test.describe('Sample tests')
|
||||
def _():
|
||||
|
||||
overload = build()
|
||||
obj = overload()
|
||||
|
||||
|
||||
@test.it('Verifying access')
|
||||
def _():
|
||||
test.assert_equals(obj.a, 1, cng_msg)
|
||||
test.assert_equals(obj.no, 'This is "No parameter" function.', cng_msg)
|
||||
test.assert_equals(obj.single, 'This is "Single parameter" function', cng_msg)
|
||||
test.assert_equals(obj.two, 'This is "Two parameter" function', cng_msg)
|
||||
test.assert_equals(obj.three, 'This is "Three parameter" function', cng_msg)
|
||||
test.assert_equals(obj.extra(), 'This is extra method.', cng_msg)
|
||||
test.assert_equals(obj.CLS_VAR, 42, cng_msg)
|
||||
|
||||
@test.it('Verifying overloading feature')
|
||||
def _():
|
||||
test.assert_equals(obj.foo(), 'This is "No parameter" function.', sld_work)
|
||||
test.assert_equals(obj.foo(1), 'This is "Single parameter" function:1', sld_work)
|
||||
test.assert_equals(obj.foo(1,2), 'This is "Two parameter" function:1,2', sld_work)
|
||||
test.assert_equals(obj.foo(1,2,3), 'This is "Three parameter" function:1,2,3', sld_work)
|
||||
|
||||
@test.it('Verifying overloading feature after adding new methods :1')
|
||||
def _():
|
||||
|
||||
def foo(self, a, b, c, d):
|
||||
return self.a + a + b + c + d
|
||||
|
||||
def foo1(self, a, b):
|
||||
return 'Overwritten'
|
||||
|
||||
overload.foo = foo
|
||||
|
||||
|
||||
test.assert_equals(obj.foo(), 'This is "No parameter" function.', sld_work)
|
||||
test.assert_equals(obj.foo(1), 'This is "Single parameter" function:1', sld_work)
|
||||
test.assert_equals(obj.foo(1,2), 'This is "Two parameter" function:1,2', sld_work)
|
||||
test.assert_equals(obj.foo(1,2,3), 'This is "Three parameter" function:1,2,3', sld_work)
|
||||
|
||||
test.assert_equals(obj.foo(1,2,3,4), 11, sld_work)
|
||||
|
||||
overload.foo = foo1
|
||||
test.assert_equals(obj.foo(1,2), 'Overwritten', sld_work)
|
||||
|
||||
|
||||
@test.it('Verifying overloading feature after adding new methods :2')
|
||||
def _():
|
||||
|
||||
def boo1(self):
|
||||
return self.a
|
||||
|
||||
def boo2(self, a):
|
||||
return self.a + a
|
||||
|
||||
def unique(self, a, b):
|
||||
return a + b
|
||||
|
||||
overload.boo = boo1
|
||||
overload.boo = boo2
|
||||
obj.nothing = '123'
|
||||
overload.unique = unique
|
||||
|
||||
test.assert_equals(obj.boo(), 1, sld_work)
|
||||
test.assert_equals(obj.boo(1), 2, sld_work)
|
||||
test.assert_equals(obj.unique(1, 2), 3, sld_work)
|
||||
test.assert_equals(obj.nothing, '123', sld_work)
|
||||
|
||||
|
||||
|
||||
@test.describe('Verify the absence of unwanted sharing of methods between different classes')
|
||||
def _():
|
||||
class Unshare(metaclass=Meta):
|
||||
def foo(self,a,b): return 'unshared!'
|
||||
|
||||
unsh=Unshare()
|
||||
@test.it('The new foo function in Unshare class should be used')
|
||||
def _():
|
||||
test.assert_equals(unsh.foo(1,2), 'unshared!')
|
||||
|
||||
@test.it('Previous implementations of "foo" in other classes shouldn\'t be visible from a freshly built class')
|
||||
def _():
|
||||
test.expect_error("Your function didn't raise any exception", lambda: unsh.foo(1), Exception)
|
||||
test.expect_error("Your function should raise AttributeError", lambda: unsh.foo(1), AttributeError)
|
||||
|
||||
@test.it('Previous method with the same signature in other classes shouldn\'t be affected')
|
||||
def _():
|
||||
test.assert_equals(obj.foo(1,2), 'Overwritten')
|
||||
|
||||
|
||||
@test.it('Class level variables are still working as expected')
|
||||
def _():
|
||||
def over(): obj.CLS_VAR=-42
|
||||
def write(): obj.HAHA=msg
|
||||
|
||||
test.expect_no_error("Class level variables can be overriden", over)
|
||||
test.assert_equals(obj.CLS_VAR, -42, "Class variable Overload.CLS_VAR hasn't been overriden")
|
||||
|
||||
msg = 'This is another class variable'
|
||||
test.expect_no_error("New class level variables can be defined", write)
|
||||
test.assert_equals(obj.HAHA, msg, "New class level variables can be defined and accessed")
|
||||
|
||||
|
||||
@test.it('Undefined methods or variables should raise AttributeError')
|
||||
def _():
|
||||
test.expect_error('Undefined attributes/mehtods should raise AttributeError',
|
||||
lambda: overload.NEW_CLS_VAR, AttributeError)
|
||||
test.expect_error('Undefined attributes/mehtods should raise AttributeError',
|
||||
lambda: obj.ABCD, AttributeError)
|
||||
test.expect_error('Undefined attributes/mehtods should raise AttributeError',
|
||||
lambda: obj.randomFuncName(), AttributeError)
|
||||
|
||||
|
||||
@test.it('undefined overloaded methods (wrong number of arguments should raise AttributeError)')
|
||||
def _():
|
||||
test.expect_error('Undefined attributes/mehtods should raise AttributeError',
|
||||
lambda: overload.foo(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1), AttributeError)
|
Reference in New Issue
Block a user