|
| 1 | +""" |
| 2 | +Variation of ``tombola.Tombola`` implementing ``__subclasshook__``. |
| 3 | +
|
| 4 | +Tests with simple classes:: |
| 5 | +
|
| 6 | + >>> Tombola.__subclasshook__(object) |
| 7 | + NotImplemented |
| 8 | + >>> class Complete: |
| 9 | + ... def __init__(): pass |
| 10 | + ... def load(): pass |
| 11 | + ... def pick(): pass |
| 12 | + ... def loaded(): pass |
| 13 | + ... |
| 14 | + >>> Tombola.__subclasshook__(Complete) |
| 15 | + True |
| 16 | + >>> issubclass(Complete, Tombola) |
| 17 | +
|
| 18 | +""" |
| 19 | + |
| 20 | + |
| 21 | +from abc import ABC, abstractmethod |
| 22 | +from inspect import getmembers, isfunction |
| 23 | + |
| 24 | + |
| 25 | +class Tombola(ABC): # <1> |
| 26 | + |
| 27 | + @abstractmethod |
| 28 | + def __init__(self, iterable): # <2> |
| 29 | + """New instance is loaded from an iterable.""" |
| 30 | + |
| 31 | + @abstractmethod |
| 32 | + def load(self, iterable): |
| 33 | + """Add items from an iterable.""" |
| 34 | + |
| 35 | + @abstractmethod |
| 36 | + def pick(self): # <3> |
| 37 | + """Remove item at random, returning it. |
| 38 | +
|
| 39 | + This method should raise `LookupError` when the instance is empty. |
| 40 | + """ |
| 41 | + |
| 42 | + def loaded(self): # <4> |
| 43 | + try: |
| 44 | + item = self.pick() |
| 45 | + except LookupError: |
| 46 | + return False |
| 47 | + else: |
| 48 | + self.load([item]) # put it back |
| 49 | + return True |
| 50 | + |
| 51 | + @classmethod |
| 52 | + def __subclasshook__(cls, other_cls): |
| 53 | + if cls is Tombola: |
| 54 | + interface_names = function_names(cls) |
| 55 | + found_names = set() |
| 56 | + for a_cls in other_cls.__mro__: |
| 57 | + found_names |= function_names(a_cls) |
| 58 | + if found_names >= interface_names: |
| 59 | + return True |
| 60 | + return NotImplemented |
| 61 | + |
| 62 | + |
| 63 | +def function_names(obj): |
| 64 | + return {name for name, _ in getmembers(obj, isfunction)} |
0 commit comments