1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
|
"""
Routines for testing or asserting that a value is certain type, in a way that
works in both Python 2.7 and Python 3.4.
"""
from __future__ import print_function, division, absolute_import
# In Python 3, the "unicode" and "long" types went away.
string_types = (str,) # Python 2 and 3.
try:
string_types += (unicode,) # Python 2.
except NameError:
pass # Python 3.
integer_types = (int,) # Python 2 and 3.
try:
integer_types += (long,) # Python 2.
except NameError:
pass # Python 3.
def is_string(obj):
"""
Is the object a string?
"""
return isinstance(obj, string_types)
def is_integer(obj):
"""
Is the object an integer?
"""
return isinstance(obj, integer_types)
def require_string(obj, name=None, nonempty=False):
"""
Raise an exception if the obj is not of type str or unicode.
If name is provided it is used in the exception message.
If nonempty=True, then an exception is raised if the object is the empty
string.
"""
require_instance(obj, string_types, name, "string")
if nonempty and not obj:
raise ValueError(
(("%s: " % name) if name else "") +
"string must be nonempty.")
def require_integer(obj, name=None):
"""
Raise an exception if the obj is not of type int or long.
If name is provided it is used in the exception message.
If nonzero=True, then an exception is raised if the integer is 0.
If positive=True, then an exception is raised if the integer is < 1.
"""
require_instance(obj, integer_types, name, "integer")
def require_instance(obj, types=None, name=None, type_name=None, truncate_at=80):
"""
Raise an exception if obj is not an instance of one of the specified types.
Similarly to isinstance, 'types' may be either a single type or a tuple of
types.
If name or type_name is provided, it is used in the exception message.
The object's string representation is also included in the message,
truncated to 'truncate_at' number of characters.
"""
if not isinstance(obj, types):
obj_string = str(obj)
if len(obj_string) > truncate_at:
obj_string = obj_string[:truncate_at - 3] + "..."
if type_name is None:
try:
type_name = "one of " + ", ".join(str(t) for t in types)
except TypeError:
type_name = str(types)
name_string = ("%s: " % name) if name else ""
error_message = "%sexpected %s. Got: '%s' of type '%s'" % (
name_string, type_name, obj_string, type(obj))
raise TypeError(error_message)
def require_iterable_of(objs, types, name=None, type_name=None, truncate_at=80):
"""
Raise an exception if objs is not an iterable with each element an instance
of one of the specified types.
See `require_instance` for descriptions of the other parameters.
"""
# Fast pass for common case where all types are correct.
# This avoids the more expensive loop below. A typical speedup from this
# optimization is 6.6 sec -> 1.7 sec, for testing a list of size 10,000,000.
try:
if all(isinstance(obj, types) for obj in objs):
return
except TypeError:
# We don't require that objs is a list in this function, just that it's
# iterable. We specify 'list' below as a convenient way to throw the
# desired error.
require_instance(objs, list, name, "iterable", truncate_at)
# Some type isn't correct. We reuse the require_instance function to raise
# the exception.
prefix = ("%s: " % name) if name else ""
for (i, obj) in enumerate(objs):
element_name = prefix + ("element at index %d" % i)
require_instance(obj, types, element_name, type_name, truncate_at)
assert False, "Shouldn't reach here."
|