Skip to content

Instantly share code, notes, and snippets.

@ambv
Last active February 28, 2016 00:05
Show Gist options
  • Save ambv/5682351 to your computer and use it in GitHub Desktop.
Save ambv/5682351 to your computer and use it in GitHub Desktop.
Stateful methods with PEP 443 single-dispatch generic functions.
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