65 lines
1.5 KiB
Python
65 lines
1.5 KiB
Python
"""
|
|
Variation of ``tombola.Tombola`` implementing ``__subclasshook__``.
|
|
|
|
Tests with simple classes::
|
|
|
|
>>> Tombola.__subclasshook__(object)
|
|
NotImplemented
|
|
>>> class Complete:
|
|
... def __init__(): pass
|
|
... def load(): pass
|
|
... def pick(): pass
|
|
... def loaded(): pass
|
|
...
|
|
>>> Tombola.__subclasshook__(Complete)
|
|
True
|
|
>>> issubclass(Complete, Tombola)
|
|
|
|
"""
|
|
|
|
|
|
from abc import ABC, abstractmethod
|
|
from inspect import getmembers, isfunction
|
|
|
|
|
|
class Tombola(ABC): # <1>
|
|
|
|
@abstractmethod
|
|
def __init__(self, iterable): # <2>
|
|
"""New instance is loaded from an iterable."""
|
|
|
|
@abstractmethod
|
|
def load(self, iterable):
|
|
"""Add items from an iterable."""
|
|
|
|
@abstractmethod
|
|
def pick(self): # <3>
|
|
"""Remove item at random, returning it.
|
|
|
|
This method should raise `LookupError` when the instance is empty.
|
|
"""
|
|
|
|
def loaded(self): # <4>
|
|
try:
|
|
item = self.pick()
|
|
except LookupError:
|
|
return False
|
|
else:
|
|
self.load([item]) # put it back
|
|
return True
|
|
|
|
@classmethod
|
|
def __subclasshook__(cls, other_cls):
|
|
if cls is Tombola:
|
|
interface_names = function_names(cls)
|
|
found_names = set()
|
|
for a_cls in other_cls.__mro__:
|
|
found_names |= function_names(a_cls)
|
|
if found_names >= interface_names:
|
|
return True
|
|
return NotImplemented
|
|
|
|
|
|
def function_names(obj):
|
|
return {name for name, _ in getmembers(obj, isfunction)}
|