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