Last active
February 28, 2016 00:05
-
-
Save ambv/5682351 to your computer and use it in GitHub Desktop.
Stateful methods with PEP 443 single-dispatch generic functions.
This file contains 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
from decimal import Decimal | |
from functools import singledispatch | |
# | |
# @singledispatch has to be moved inside __init__ to | |
# make registration work per instance. | |
# | |
class State: | |
def __init__(self): | |
self.add = singledispatch(self.add) | |
self.add.register(int, self.add_int) | |
self.add.register(float, self.add_float) | |
self.add.register(complex, self.add_complex) | |
self.sum = 0 | |
def add(self, arg): | |
raise TypeError("Not supported.") | |
def add_int(self, arg): | |
self.sum += arg | |
def add_float(self, arg): | |
self.sum += int(round(arg)) | |
def add_complex(self, arg): | |
self.sum += int(round(arg.real)) | |
if __name__ == '__main__': | |
state = State() | |
state2 = State() | |
state.add(1) | |
state.add(2.51) | |
state.add(3.7+4j) | |
assert state.sum == 8, state.sum | |
state.add(2.50) | |
assert state.sum == 10 | |
try: | |
state.add("string") | |
assert False, "TypeError not raised." | |
except TypeError: | |
pass # properly refused to add a string | |
# | |
# This way you can register new implementations on | |
# a single instance. This requires manually binding | |
# the implementation as a method to make "self" work | |
# as expected. | |
# | |
def add_decimal(self, arg): | |
self.sum += int(arg.to_integral_value()) | |
bound = add_decimal.__get__(state2, State) | |
state2.add.register(Decimal, bound) | |
state2.add(50) | |
state2.add(Decimal("48.76")) | |
assert state2.sum == 99 | |
# The first instance is untouched. | |
assert state.sum == 10 | |
try: | |
state.add(Decimal("1")) | |
assert False, "TypeError not raised." | |
except TypeError: | |
pass # properly refused to add a decimal | |
# | |
# To enable class-wide registration of new | |
# implementations, the class needs to maintain a second | |
# registry where users assign templates. The class binds | |
# those templates in __init__ and registers them on the | |
# instance-level singledispatch method. | |
# | |
# We can implement this as a second layer of | |
# singledispatch, which is nice because the user does | |
# not see that there is anything special going on. | |
# | |
class ExtendedState: | |
def __init__(self): | |
add_registry = self.add.registry | |
self.add = singledispatch(add_registry[object]) | |
self.add.register(int, self.add_int) | |
self.add.register(float, self.add_float) | |
self.add.register(complex, self.add_complex) | |
for typ, impl in add_registry.items(): | |
impl = impl.__get__(self, ExtendedState) | |
self.add.register(typ, impl) | |
self.sum = 0 | |
@singledispatch | |
def add(self, arg): | |
raise TypeError("Not supported.") | |
def add_int(self, arg): | |
self.sum += arg | |
def add_float(self, arg): | |
self.sum += int(round(arg)) | |
def add_complex(self, arg): | |
self.sum += int(round(arg.real)) | |
if __name__ == '__main__': | |
@ExtendedState.add.register(str) | |
def add_str(self, arg): | |
self.sum += int(arg) | |
state1 = ExtendedState() | |
state2 = ExtendedState() | |
state1.add(10) | |
state2.add(20.4) | |
state1.add("30") | |
state2.add("40") | |
assert state1.sum == 40 | |
assert state2.sum == 60 | |
# | |
# Instance-level registration is still possible, | |
# requires manual binding like before. | |
# | |
def add_decimal(self, arg): | |
self.sum += int(arg.to_integral_value()) | |
bound = add_decimal.__get__(state2, State) | |
state2.add.register(Decimal, bound) | |
state2.add(Decimal("39.99")) | |
assert state2.sum == 100 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment