Skip to content

Commit

Permalink
Merge 2df4295 into ec1dca8
Browse files Browse the repository at this point in the history
  • Loading branch information
cpcloud committed Aug 2, 2018
2 parents ec1dca8 + 2df4295 commit 5520d29
Show file tree
Hide file tree
Showing 12 changed files with 546 additions and 43 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
*.pyc
*.egg-info/
.cache/
.pytest_cache/
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ python:
- "pypy3"

install:
- pip install --upgrade pip
- pip install coverage
- pip install --upgrade pytest pytest-benchmark

Expand Down
14 changes: 11 additions & 3 deletions bench/test_simple.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
from multipledispatch import dispatch

try:
range = xrange
except NameError:
pass


@dispatch(int)
def isint(x):
return True


@dispatch(object)
def isint(x):
return False


def test_simple():
for i in xrange(100000):
isint(5)
isint('a')
for i in range(100000):
assert isint(5)
assert not isint('a')
85 changes: 69 additions & 16 deletions multipledispatch/conflict.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,73 @@
from .utils import _toposort, groupby
from .variadic import isvariadic


class AmbiguityWarning(Warning):
pass


def supercedes(a, b):
""" A is consistent and strictly more specific than B """
return len(a) == len(b) and all(map(issubclass, a, b))
if len(a) < len(b):
# only case is if a is empty and b is variadic
return not a and len(b) == 1 and isvariadic(b[-1])
elif len(a) == len(b):
return all(map(issubclass, a, b))
else:
# len(a) > len(b)
p1 = 0
p2 = 0
while p1 < len(a) and p2 < len(b):
cur_a = a[p1]
cur_b = b[p2]
if not (isvariadic(cur_a) or isvariadic(cur_b)):
if not issubclass(cur_a, cur_b):
return False
p1 += 1
p2 += 1
elif isvariadic(cur_a):
assert p1 == len(a) - 1
return p2 == len(b) - 1 and issubclass(cur_a, cur_b)
elif isvariadic(cur_b):
assert p2 == len(b) - 1
if not issubclass(cur_a, cur_b):
return False
p1 += 1
return p2 == len(b) - 1 and p1 == len(a)


def consistent(a, b):
""" It is possible for an argument list to satisfy both A and B """
return (len(a) == len(b) and
all(issubclass(aa, bb) or issubclass(bb, aa)
for aa, bb in zip(a, b)))

# Need to check for empty args
if not a:
return not b or isvariadic(b[0])
if not b:
return not a or isvariadic(a[0])

# Non-empty args check for mutual subclasses
if len(a) == len(b):
return all(issubclass(aa, bb) or issubclass(bb, aa)
for aa, bb in zip(a, b))
else:
p1 = 0
p2 = 0
while p1 < len(a) and p2 < len(b):
cur_a = a[p1]
cur_b = b[p2]
if not issubclass(cur_b, cur_a) and not issubclass(cur_a, cur_b):
return False
if not (isvariadic(cur_a) or isvariadic(cur_b)):
p1 += 1
p2 += 1
elif isvariadic(cur_a):
p2 += 1
elif isvariadic(cur_b):
p1 += 1
# We only need to check for variadic ends
# Variadic types are guaranteed to be the last element
return (isvariadic(cur_a) and p2 == len(b) or
isvariadic(cur_b) and p1 == len(a))


def ambiguous(a, b):
Expand All @@ -24,11 +78,11 @@ def ambiguous(a, b):
def ambiguities(signatures):
""" All signature pairs such that A is ambiguous with B """
signatures = list(map(tuple, signatures))
return set([(a, b) for a in signatures for b in signatures
if hash(a) < hash(b)
and ambiguous(a, b)
and not any(supercedes(c, a) and supercedes(c, b)
for c in signatures)])
return set((a, b) for a in signatures for b in signatures
if hash(a) < hash(b)
and ambiguous(a, b)
and not any(supercedes(c, a) and supercedes(c, b)
for c in signatures))


def super_signature(signatures):
Expand All @@ -37,20 +91,19 @@ def super_signature(signatures):
assert all(len(s) == n for s in signatures)

return [max([type.mro(sig[i]) for sig in signatures], key=len)[0]
for i in range(n)]
for i in range(n)]


def edge(a, b, tie_breaker=hash):
""" A should be checked before B
Tie broken by tie_breaker, defaults to ``hash``
"""
if supercedes(a, b):
if supercedes(b, a):
return tie_breaker(a) > tie_breaker(b)
else:
return True
return False
# A either supercedes B and B does not supercede A or if B does then call
# tie_breaker
return supercedes(a, b) and (
not supercedes(b, a) or tie_breaker(a) > tie_breaker(b)
)


def ordering(signatures):
Expand Down
2 changes: 1 addition & 1 deletion multipledispatch/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def ismethod(func):
"""
if hasattr(inspect, "signature"):
signature = inspect.signature(func)
return signature.parameters.get('self', None) != None
return signature.parameters.get('self', None) is not None
else:
spec = inspect.getargspec(func)
return spec and spec.args and spec.args[0] == 'self'
87 changes: 82 additions & 5 deletions multipledispatch/dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
import inspect
from .conflict import ordering, ambiguities, super_signature, AmbiguityWarning
from .utils import expand_tuples
from .variadic import Variadic, isvariadic
import itertools as itl



class MDNotImplementedError(NotImplementedError):
""" A NotImplementedError for multiple dispatch """

Expand Down Expand Up @@ -46,6 +46,59 @@ def restart_ordering(on_ambiguity=ambiguity_warn):
DeprecationWarning,
)


def variadic_signature_matches_iter(types, full_signature):
"""Check if a set of input types matches a variadic signature.
Notes
-----
The algorithm is as follows:
Initialize the current signature to the first in the sequence
For each type in `types`:
If the current signature is variadic
If the type matches the signature
yield True
Else
Try to get the next signature
If no signatures are left we can't possibly have a match
so yield False
Else
yield True if the type matches the current signature
Get the next signature
"""
sigiter = iter(full_signature)
sig = next(sigiter)
for typ in types:
matches = issubclass(typ, sig)
if isvariadic(sig):
# if the current type matches the current variadic signature yield
# True, else yield False
yield matches
else:
# we're not matching a variadic argument, so move to the next
# element in the signature
yield matches
sig = next(sigiter)
else:
try:
sig = next(sigiter)
except StopIteration:
assert isvariadic(sig)
yield True
else:
# We have signature items left over, so all of our arguments
# haven't matched
yield False


def variadic_signature_matches(types, full_signature):
# No arguments always matches a variadic signature
assert full_signature
return all(variadic_signature_matches_iter(types, full_signature))


class Dispatcher(object):
""" Dispatch methods based on type signature
Expand Down Expand Up @@ -164,16 +217,35 @@ def add(self, signature, func):
self.add(typs, func)
return

for typ in signature:
if not isinstance(typ, type):
new_signature = []

for index, typ in enumerate(signature, start=1):
if not isinstance(typ, (type, list)):
str_sig = ', '.join(c.__name__ if isinstance(c, type)
else str(c) for c in signature)
raise TypeError("Tried to dispatch on non-type: %s\n"
"In signature: <%s>\n"
"In function: %s" %
(typ, str_sig, self.name))

self.funcs[signature] = func
# handle variadic signatures
if isinstance(typ, list):
if index != len(signature):
raise TypeError(
'Variadic signature must be the last element'
)

if len(typ) != 1:
raise TypeError(
'Variadic signature must contain exactly one element. '
'To use a variadic union type place the desired types '
'inside of a tuple, e.g., [(int, str)]'
)
new_signature.append(Variadic[typ[0]])
else:
new_signature.append(typ)

self.funcs[tuple(new_signature)] = func
self._cache.clear()

try:
Expand Down Expand Up @@ -248,7 +320,7 @@ def dispatch(self, *types):
None
See Also:
``multipledispatch.conflict`` - module to determine resolution order
``multipledispatch.conflict`` - module to determine resolution order
"""

if types in self.funcs:
Expand All @@ -260,11 +332,16 @@ def dispatch(self, *types):
return None

def dispatch_iter(self, *types):

n = len(types)
for signature in self.ordering:
if len(signature) == n and all(map(issubclass, types, signature)):
result = self.funcs[signature]
yield result
elif len(signature) and isvariadic(signature[-1]):
if variadic_signature_matches(types, signature):
result = self.funcs[signature]
yield result

def resolve(self, types):
""" Deterimine appropriate implementation for this type signature
Expand Down
47 changes: 43 additions & 4 deletions multipledispatch/tests/test_conflict.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
from multipledispatch.conflict import (supercedes, ordering, ambiguities,
ambiguous, super_signature, consistent)
from multipledispatch.dispatcher import Variadic


class A(object): pass
class B(A): pass
class C(object): pass

def _test_supercedes(a, b):
assert supercedes(a, b)
assert not supercedes(b, a)


def test_supercedes():
assert supercedes([B], [A])
assert supercedes([B, A], [A, A])
assert not supercedes([B, A], [A, B])
assert not supercedes([A], [B])
_test_supercedes([B], [A])
_test_supercedes([B, A], [A, A])


def test_consistent():
Expand Down Expand Up @@ -60,3 +63,39 @@ def test_ordering():

def test_type_mro():
assert super_signature([[object], [type]]) == [type]


def test_supercedes_variadic():
_test_supercedes((Variadic[B],), (Variadic[A],))
_test_supercedes((B, Variadic[A]), (Variadic[A],))
_test_supercedes((Variadic[A],), (Variadic[(A, C)],))
_test_supercedes((A, B, Variadic[C]), (Variadic[object],))
_test_supercedes((A, Variadic[B]), (Variadic[A],))
_test_supercedes(tuple([]), (Variadic[A],))
_test_supercedes((A, A, A), (A, Variadic[A]))


def test_consistent_variadic():
# basic check
assert consistent((Variadic[A],), (Variadic[A],))
assert consistent((Variadic[B],), (Variadic[B],))
assert not consistent((Variadic[C],), (Variadic[A],))

# union types
assert consistent((Variadic[(A, C)],), (Variadic[A],))
assert consistent((Variadic[(A, C)],), (Variadic[(C, A)],))
assert consistent((Variadic[(A, B, C)],), (Variadic[(C, B, A)],))
assert consistent((A, B, C), (Variadic[(A, B, C)],))
assert consistent((A, B, C), (A, Variadic[(B, C)]))

# more complex examples
assert consistent(tuple([]), (Variadic[object],))
assert consistent((A, A, B), (A, A, Variadic[B]))
assert consistent((A, A, B), (A, A, Variadic[A]))
assert consistent((A, B, Variadic[C]), (B, A, Variadic[C]))
assert consistent((A, B, Variadic[C]), (B, A, Variadic[(C, B)]))

# not consistent
assert not consistent((C,), (Variadic[A],))
assert not consistent((A, A, Variadic[C]), (A, Variadic[C]))
assert not consistent((A, B, Variadic[C]), (C, B, Variadic[C]))

0 comments on commit 5520d29

Please sign in to comment.