abc
module.
contextlib.AbstractContextManager
It’s easy to test if an object (in this case,
li
)
is
iterable:
simply test if the object has an
__iter__
method.
li = [10, 20, 30] if hasattr(li, "__iter__") and callable(getattr(li, "__iter__")): print("li is iterable.") else: print("li is not iterable.")
li is iterable.
It’s a bit harder to test if an object (in this case,
it
)
is an
iterator.
We have to test if the object has two methods.
li = [10, 20, 30] it = iter(li) if hasattr(it, "__next__") and callable(getattr(it, "__next__")) and \ hasattr(it, "__iter__") and callable(getattr(it, "__iter__")): print("it is an iterator.") else: print("it is not an iterator.")
it is an iterator.
Similarly, to test if an object is a
context
manager,
we have to test if the object has the methods
__enter__
and
__exit__
.
To test for other interfaces,
we might have to test if the object has three or more methods.
Duck-typing
means checking whether an object suits your purposes
by checking whether the object has the attributes (including the methods)
you need.
Instead of the above duck-typing,
take advantage of the facts that class
list
is derived from the base class
collections.abc.Iterable
,
and class
list_iterator
is derived from the base class
collections.abc.Iterator
,
import collections.abc #abstract base classes for data types that are collections li = [10, 20, 30] it = iter(li) if isinstance(li, collections.abc.Iterable): print("li is iterable.") else: print("li is not iterable.") if isinstance(it, collections.abc.Iterator): print("it is an iterator.") else: print("it is not iterator.")
li is iterable. it is an iterator.
import contextlib infile = open("file.txt") if isinstance(infile, contextlib.AbstractContextManager): print("infile is a context manager.") else: print("infile is not a context manager.")
infile is a context manager.
import collections.abc def myprint(x): "Print a variable of any data type." if isinstance(x, str) or not isinstance(x, collections.abc.Iterable): print(x) else: for item in x: #Arrive here if x is any type of iterable except str print(item)
We saw this module in Iterator. The advantages of inheriting from an abstract base class are:
if
isinstance
.
__iter__
method to your class
range
,
or if you forgot to give a
__next__
method to your class
iterator
.
In the latter case, the error message would be
TypeError:
Can't instantiate abstract class iterator with abstract methods
__next__
”.
__iter__
method for your class
iterator
.
Your class
iterator
now inherits the
__iter__
method from class
collections.abc.Iterator
.
""" This module is float.py. """ import collections.abc class range(collections.abc.Iterable): "A range of n+1 equally spaced floats, from start to end inclusive." def __init__(self, start, end, n): if not isinstance(start, int) and not isinstance(start, float): raise TypeError(f"start must be int or float, not {type(start)}") if not isinstance(end, int) and not isinstance(end, float): raise TypeError(f"end must be int or float, not {type(end)}") if end <= start: raise ValueError("start must be > end") if not isinstance(n, int): raise TypeError(f"n must be int, not {type(n)}") if n <= 0: raise ValueError(f"n must be posiive, not {n}") self.start = start self.end = end self.n = n def __iter__(self): return iterator(self.start, self.end, self.n) class iterator(collections.abc.Iterator): def __init__(self, start, end, n): self.start = start self.end = end self.n = n self.i = 0 def __next__(self): if self.i >= self.n + 1: raise StopIteration result = self.start + (self.end - self.start) * self.i / self.n self.i += 1 return result if __name__ == "__main__": import sys for f in range(0.0, 1.0, 10): #the range we just defined here in float.py print(f) sys.exit(0)
Test out the module before you try to import it.
python3 -m float
import sys import float for i in range(10): #the range in the Python Standard Library print(i) print() for f in float.range(0.0, 1.0, 10): #the range we defined in float.py print(f) sys.exit(0)
0 1 2 3 4 5 6 7 8 9 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0