Closed. This question is opinion-based. It is not currently accepting answers.
Closed 5 years ago.
Locked. This question and its answers are locked because the question is off-topic but has historical significance. It is not currently accepting new answers or interactions.
I'm looking for an efficient way to check variables of a Python function. For example, I'd like to check arguments type and value. Is there a module for this? Or should I use something like decorators, or any specific idiom?
def my_function(a, b, c):
"""An example function I'd like to check the arguments of."""
# check that a is an int
# check that 0 < b < 10
# check that c is not an empty string
In this elongated answer, we implement a Python 3.x-specific type checking decorator based on PEP 484-style type hints in less than 275 lines of pure-Python (most of which is explanatory docstrings and comments) – heavily optimized for industrial-strength real-world use complete with a py.test-driven test suite exercising all possible edge cases.
Feast on the unexpected awesome of bear typing:
>>> #beartype
... def spirit_bear(kermode: str, gitgaata: (str, int)) -> tuple:
... return (kermode, gitgaata, "Moksgm'ol", 'Ursus americanus kermodei')
>>> spirit_bear(0xdeadbeef, 'People of the Cane')
AssertionError: parameter kermode=0xdeadbeef not of <class "str">
As this example suggests, bear typing explicitly supports type checking of parameters and return values annotated as either simple types or tuples of such types. Golly!
O.K., that's actually unimpressive. #beartype resembles every other Python 3.x-specific type checking decorator based on PEP 484-style type hints in less than 275 lines of pure-Python. So what's the rub, bub?
Pure Bruteforce Hardcore Efficiency
Bear typing is dramatically more efficient in both space and time than all existing implementations of type checking in Python to the best of my limited domain knowledge. (More on that later.)
Efficiency usually doesn't matter in Python, however. If it did, you wouldn't be using Python. Does type checking actually deviate from the well-established norm of avoiding premature optimization in Python? Yes. Yes, it does.
Consider profiling, which adds unavoidable overhead to each profiled metric of interest (e.g., function calls, lines). To ensure accurate results, this overhead is mitigated by leveraging optimized C extensions (e.g., the _lsprof C extension leveraged by the cProfile module) rather than unoptimized pure-Python (e.g., the profile module). Efficiency really does matter when profiling.
Type checking is no different. Type checking adds overhead to each function call type checked by your application – ideally, all of them. To prevent well-meaning (but sadly small-minded) coworkers from removing the type checking you silently added after last Friday's caffeine-addled allnighter to your geriatric legacy Django web app, type checking must be fast. So fast that no one notices it's there when you add it without telling anyone. I do this all the time! Stop reading this if you are a coworker.
If even ludicrous speed isn't enough for your gluttonous application, however, bear typing may be globally disabled by enabling Python optimizations (e.g., by passing the -O option to the Python interpreter):
$ python3 -O
# This succeeds only when type checking is optimized away. See above!
>>> spirit_bear(0xdeadbeef, 'People of the Cane')
(0xdeadbeef, 'People of the Cane', "Moksgm'ol", 'Ursus americanus kermodei')
Just because. Welcome to bear typing.
What The...? Why "bear"? You're a Neckbeard, Right?
Bear typing is bare-metal type checking – that is, type checking as close to the manual approach of type checking in Python as feasible. Bear typing is intended to impose no performance penalties, compatibility constraints, or third-party dependencies (over and above that imposed by the manual approach, anyway). Bear typing may be seamlessly integrated into existing codebases and test suites without modification.
Everyone's probably familiar with the manual approach. You manually assert each parameter passed to and/or return value returned from every function in your codebase. What boilerplate could be simpler or more banal? We've all seen it a hundred times a googleplex times, and vomited a little in our mouths everytime we did. Repetition gets old fast. DRY, yo.
Get your vomit bags ready. For brevity, let's assume a simplified easy_spirit_bear() function accepting only a single str parameter. Here's what the manual approach looks like:
def easy_spirit_bear(kermode: str) -> str:
assert isinstance(kermode, str), 'easy_spirit_bear() parameter kermode={} not of <class "str">'.format(kermode)
return_value = (kermode, "Moksgm'ol", 'Ursus americanus kermodei')
assert isinstance(return_value, str), 'easy_spirit_bear() return value {} not of <class "str">'.format(return_value)
return return_value
Python 101, right? Many of us passed that class.
Bear typing extracts the type checking manually performed by the above approach into a dynamically defined wrapper function automatically performing the same checks – with the added benefit of raising granular TypeError rather than ambiguous AssertionError exceptions. Here's what the automated approach looks like:
def easy_spirit_bear_wrapper(*args, __beartype_func=easy_spirit_bear, **kwargs):
if not (
isinstance(args[0], __beartype_func.__annotations__['kermode'])
if 0 < len(args) else
isinstance(kwargs['kermode'], __beartype_func.__annotations__['kermode'])
if 'kermode' in kwargs else True):
raise TypeError(
'easy_spirit_bear() parameter kermode={} not of {!r}'.format(
args[0] if 0 < len(args) else kwargs['kermode'],
__beartype_func.__annotations__['kermode']))
return_value = __beartype_func(*args, **kwargs)
if not isinstance(return_value, __beartype_func.__annotations__['return']):
raise TypeError(
'easy_spirit_bear() return value {} not of {!r}'.format(
return_value, __beartype_func.__annotations__['return']))
return return_value
It's long-winded. But it's also basically* as fast as the manual approach. * Squinting suggested.
Note the complete lack of function inspection or iteration in the wrapper function, which contains a similar number of tests as the original function – albeit with the additional (maybe negligible) costs of testing whether and how the parameters to be type checked are passed to the current function call. You can't win every battle.
Can such wrapper functions actually be reliably generated to type check arbitrary functions in less than 275 lines of pure Python? Snake Plisskin says, "True story. Got a smoke?"
And, yes. I may have a neckbeard.
No, Srsly. Why "bear"?
Bear beats duck. Duck may fly, but bear may throw salmon at duck. In Canada, nature can surprise you.
Next question.
What's So Hot about Bears, Anyway?
Existing solutions do not perform bare-metal type checking – at least, none I've grepped across. They all iteratively reinspect the signature of the type-checked function on each function call. While negligible for a single call, reinspection overhead is usually non-negligible when aggregated over all calls. Really, really non-negligible.
It's not simply efficiency concerns, however. Existing solutions also often fail to account for common edge cases. This includes most if not all toy decorators provided as stackoverflow answers here and elsewhere. Classic failures include:
Failing to type check keyword arguments and/or return values (e.g., sweeneyrod's #checkargs decorator).
Failing to support tuples (i.e., unions) of types accepted by the isinstance() builtin.
Failing to propagate the name, docstring, and other identifying metadata from the original function onto the wrapper function.
Failing to supply at least a semblance of unit tests. (Kind of critical.)
Raising generic AssertionError exceptions rather than specific TypeError exceptions on failed type checks. For granularity and sanity, type checking should never raise generic exceptions.
Bear typing succeeds where non-bears fail. All one, all bear!
Bear Typing Unbared
Bear typing shifts the space and time costs of inspecting function signatures from function call time to function definition time – that is, from the wrapper function returned by the #beartype decorator into the decorator itself. Since the decorator is only called once per function definition, this optimization yields glee for all.
Bear typing is an attempt to have your type checking cake and eat it, too. To do so, #beartype:
Inspects the signature and annotations of the original function.
Dynamically constructs the body of the wrapper function type checking the original function. Thaaat's right. Python code generating Python code.
Dynamically declares this wrapper function via the exec() builtin.
Returns this wrapper function.
Shall we? Let's dive into the deep end.
# If the active Python interpreter is *NOT* optimized (e.g., option "-O" was
# *NOT* passed to this interpreter), enable type checking.
if __debug__:
import inspect
from functools import wraps
from inspect import Parameter, Signature
def beartype(func: callable) -> callable:
'''
Decorate the passed **callable** (e.g., function, method) to validate
both all annotated parameters passed to this callable _and_ the
annotated value returned by this callable if any.
This decorator performs rudimentary type checking based on Python 3.x
function annotations, as officially documented by PEP 484 ("Type
Hints"). While PEP 484 supports arbitrarily complex type composition,
this decorator requires _all_ parameter and return value annotations to
be either:
* Classes (e.g., `int`, `OrderedDict`).
* Tuples of classes (e.g., `(int, OrderedDict)`).
If optimizations are enabled by the active Python interpreter (e.g., due
to option `-O` passed to this interpreter), this decorator is a noop.
Raises
----------
NameError
If any parameter has the reserved name `__beartype_func`.
TypeError
If either:
* Any parameter or return value annotation is neither:
* A type.
* A tuple of types.
* The kind of any parameter is unrecognized. This should _never_
happen, assuming no significant changes to Python semantics.
'''
# Raw string of Python statements comprising the body of this wrapper,
# including (in order):
#
# * A "#wraps" decorator propagating the name, docstring, and other
# identifying metadata of the original function to this wrapper.
# * A private "__beartype_func" parameter initialized to this function.
# In theory, the "func" parameter passed to this decorator should be
# accessible as a closure-style local in this wrapper. For unknown
# reasons (presumably, a subtle bug in the exec() builtin), this is
# not the case. Instead, a closure-style local must be simulated by
# passing the "func" parameter to this function at function
# definition time as the default value of an arbitrary parameter. To
# ensure this default is *NOT* overwritten by a function accepting a
# parameter of the same name, this edge case is tested for below.
# * Assert statements type checking parameters passed to this callable.
# * A call to this callable.
# * An assert statement type checking the value returned by this
# callable.
#
# While there exist numerous alternatives (e.g., appending to a list or
# bytearray before joining the elements of that iterable into a string),
# these alternatives are either slower (as in the case of a list, due to
# the high up-front cost of list construction) or substantially more
# cumbersome (as in the case of a bytearray). Since string concatenation
# is heavily optimized by the official CPython interpreter, the simplest
# approach is (curiously) the most ideal.
func_body = '''
#wraps(__beartype_func)
def func_beartyped(*args, __beartype_func=__beartype_func, **kwargs):
'''
# "inspect.Signature" instance encapsulating this callable's signature.
func_sig = inspect.signature(func)
# Human-readable name of this function for use in exceptions.
func_name = func.__name__ + '()'
# For the name of each parameter passed to this callable and the
# "inspect.Parameter" instance encapsulating this parameter (in the
# passed order)...
for func_arg_index, func_arg in enumerate(func_sig.parameters.values()):
# If this callable redefines a parameter initialized to a default
# value by this wrapper, raise an exception. Permitting this
# unlikely edge case would permit unsuspecting users to
# "accidentally" override these defaults.
if func_arg.name == '__beartype_func':
raise NameError(
'Parameter {} reserved for use by #beartype.'.format(
func_arg.name))
# If this parameter is both annotated and non-ignorable for purposes
# of type checking, type check this parameter.
if (func_arg.annotation is not Parameter.empty and
func_arg.kind not in _PARAMETER_KIND_IGNORED):
# Validate this annotation.
_check_type_annotation(
annotation=func_arg.annotation,
label='{} parameter {} type'.format(
func_name, func_arg.name))
# String evaluating to this parameter's annotated type.
func_arg_type_expr = (
'__beartype_func.__annotations__[{!r}]'.format(
func_arg.name))
# String evaluating to this parameter's current value when
# passed as a keyword.
func_arg_value_key_expr = 'kwargs[{!r}]'.format(func_arg.name)
# If this parameter is keyword-only, type check this parameter
# only by lookup in the variadic "**kwargs" dictionary.
if func_arg.kind is Parameter.KEYWORD_ONLY:
func_body += '''
if {arg_name!r} in kwargs and not isinstance(
{arg_value_key_expr}, {arg_type_expr}):
raise TypeError(
'{func_name} keyword-only parameter '
'{arg_name}={{}} not a {{!r}}'.format(
{arg_value_key_expr}, {arg_type_expr}))
'''.format(
func_name=func_name,
arg_name=func_arg.name,
arg_type_expr=func_arg_type_expr,
arg_value_key_expr=func_arg_value_key_expr,
)
# Else, this parameter may be passed either positionally or as
# a keyword. Type check this parameter both by lookup in the
# variadic "**kwargs" dictionary *AND* by index into the
# variadic "*args" tuple.
else:
# String evaluating to this parameter's current value when
# passed positionally.
func_arg_value_pos_expr = 'args[{!r}]'.format(
func_arg_index)
func_body += '''
if not (
isinstance({arg_value_pos_expr}, {arg_type_expr})
if {arg_index} < len(args) else
isinstance({arg_value_key_expr}, {arg_type_expr})
if {arg_name!r} in kwargs else True):
raise TypeError(
'{func_name} parameter {arg_name}={{}} not of {{!r}}'.format(
{arg_value_pos_expr} if {arg_index} < len(args) else {arg_value_key_expr},
{arg_type_expr}))
'''.format(
func_name=func_name,
arg_name=func_arg.name,
arg_index=func_arg_index,
arg_type_expr=func_arg_type_expr,
arg_value_key_expr=func_arg_value_key_expr,
arg_value_pos_expr=func_arg_value_pos_expr,
)
# If this callable's return value is both annotated and non-ignorable
# for purposes of type checking, type check this value.
if func_sig.return_annotation not in _RETURN_ANNOTATION_IGNORED:
# Validate this annotation.
_check_type_annotation(
annotation=func_sig.return_annotation,
label='{} return type'.format(func_name))
# Strings evaluating to this parameter's annotated type and
# currently passed value, as above.
func_return_type_expr = (
"__beartype_func.__annotations__['return']")
# Call this callable, type check the returned value, and return this
# value from this wrapper.
func_body += '''
return_value = __beartype_func(*args, **kwargs)
if not isinstance(return_value, {return_type}):
raise TypeError(
'{func_name} return value {{}} not of {{!r}}'.format(
return_value, {return_type}))
return return_value
'''.format(func_name=func_name, return_type=func_return_type_expr)
# Else, call this callable and return this value from this wrapper.
else:
func_body += '''
return __beartype_func(*args, **kwargs)
'''
# Dictionary mapping from local attribute name to value. For efficiency,
# only those local attributes explicitly required in the body of this
# wrapper are copied from the current namespace. (See below.)
local_attrs = {'__beartype_func': func}
# Dynamically define this wrapper as a closure of this decorator. For
# obscure and presumably uninteresting reasons, Python fails to locally
# declare this closure when the locals() dictionary is passed; to
# capture this closure, a local dictionary must be passed instead.
exec(func_body, globals(), local_attrs)
# Return this wrapper.
return local_attrs['func_beartyped']
_PARAMETER_KIND_IGNORED = {
Parameter.POSITIONAL_ONLY, Parameter.VAR_POSITIONAL, Parameter.VAR_KEYWORD,
}
'''
Set of all `inspect.Parameter.kind` constants to be ignored during
annotation- based type checking in the `#beartype` decorator.
This includes:
* Constants specific to variadic parameters (e.g., `*args`, `**kwargs`).
Variadic parameters cannot be annotated and hence cannot be type checked.
* Constants specific to positional-only parameters, which apply to non-pure-
Python callables (e.g., defined by C extensions). The `#beartype`
decorator applies _only_ to pure-Python callables, which provide no
syntactic means of specifying positional-only parameters.
'''
_RETURN_ANNOTATION_IGNORED = {Signature.empty, None}
'''
Set of all annotations for return values to be ignored during annotation-
based type checking in the `#beartype` decorator.
This includes:
* `Signature.empty`, signifying a callable whose return value is _not_
annotated.
* `None`, signifying a callable returning no value. By convention, callables
returning no value are typically annotated to return `None`. Technically,
callables whose return values are annotated as `None` _could_ be
explicitly checked to return `None` rather than a none-`None` value. Since
return values are safely ignorable by callers, however, there appears to
be little real-world utility in enforcing this constraint.
'''
def _check_type_annotation(annotation: object, label: str) -> None:
'''
Validate the passed annotation to be a valid type supported by the
`#beartype` decorator.
Parameters
----------
annotation : object
Annotation to be validated.
label : str
Human-readable label describing this annotation, interpolated into
exceptions raised by this function.
Raises
----------
TypeError
If this annotation is neither a new-style class nor a tuple of
new-style classes.
'''
# If this annotation is a tuple, raise an exception if any member of
# this tuple is not a new-style class. Note that the "__name__"
# attribute tested below is not defined by old-style classes and hence
# serves as a helpful means of identifying new-style classes.
if isinstance(annotation, tuple):
for member in annotation:
if not (
isinstance(member, type) and hasattr(member, '__name__')):
raise TypeError(
'{} tuple member {} not a new-style class'.format(
label, member))
# Else if this annotation is not a new-style class, raise an exception.
elif not (
isinstance(annotation, type) and hasattr(annotation, '__name__')):
raise TypeError(
'{} {} neither a new-style class nor '
'tuple of such classes'.format(label, annotation))
# Else, the active Python interpreter is optimized. In this case, disable type
# checking by reducing this decorator to the identity decorator.
else:
def beartype(func: callable) -> callable:
return func
And leycec said, Let the #beartype bring forth type checking fastly: and it was so.
Caveats, Curses, and Empty Promises
Nothing is perfect. Even bear typing.
Caveat I: Default Values Unchecked
Bear typing does not type check unpassed parameters assigned default values. In theory, it could. But not in 275 lines or less and certainly not as a stackoverflow answer.
The safe (...probably totally unsafe) assumption is that function implementers claim they knew what they were doing when they defined default values. Since default values are typically constants (...they'd better be!), rechecking the types of constants that never change on each function call assigned one or more default values would contravene the fundamental tenet of bear typing: "Don't repeat yourself over and oooover and oooo-oooover again."
Show me wrong and I will shower you with upvotes.
Caveat II: No PEP 484
PEP 484 ("Type Hints") formalized the use of function annotations first introduced by PEP 3107 ("Function Annotations"). Python 3.5 superficially supports this formalization with a new top-level typing module, a standard API for composing arbitrarily complex types from simpler types (e.g., Callable[[Arg1Type, Arg2Type], ReturnType], a type describing a function accepting two arguments of type Arg1Type and Arg2Type and returning a value of type ReturnType).
Bear typing supports none of them. In theory, it could. But not in 275 lines or less and certainly not as a stackoverflow answer.
Bear typing does, however, support unions of types in the same way that the isinstance() builtin supports unions of types: as tuples. This superficially corresponds to the typing.Union type – with the obvious caveat that typing.Union supports arbitrarily complex types, while tuples accepted by #beartype support only simple classes. In my defense, 275 lines.
Tests or It Didn't Happen
Here's the gist of it. Get it, gist? I'll stop now.
As with the #beartype decorator itself, these py.test tests may be seamlessly integrated into existing test suites without modification. Precious, isn't it?
Now the mandatory neckbeard rant nobody asked for.
A History of API Violence
Python 3.5 provides no actual support for using PEP 484 types. wat?
It's true: no type checking, no type inference, no type nuthin'. Instead, developers are expected to routinely run their entire codebases through heavyweight third-party CPython interpreter wrappers implementing a facsimile of such support (e.g., mypy). Of course, these wrappers impose:
A compatibility penalty. As the official mypy FAQ admits in response to the frequently asked question "Can I use mypy to type check my existing Python code?": "It depends. Compatibility is pretty good, but some Python features are not yet implemented or fully supported." A subsequent FAQ response clarifies this incompatibility by stating that:
"...your code must make attributes explicit and use a explicit protocol representation." Grammar police see your "a explicit" and raise you an implicit frown.
"Mypy will support modular, efficient type checking, and this seems to rule out type checking some language features, such as arbitrary runtime addition of methods. However, it is likely that many of these features will be supported in a restricted form (for example, runtime modification is only supported for classes or methods registered as dynamic or ‘patchable’)."
For a full list of syntactic incompatibilities, see "Dealing with common issues". It's not pretty. You just wanted type checking and now you refactored your entire codebase and broke everyone's build two days from the candidate release and the comely HR midget in casual business attire slips a pink slip through the crack in your cubicle-cum-mancave. Thanks alot, mypy.
A performance penalty, despite interpreting statically typed code. Fourty years of hard-boiled computer science tells us that (...all else being equal) interpreting statically typed code should be faster, not slower, than interpreting dynamically typed code. In Python, up is the new down.
Additional non-trivial dependencies, increasing:
The bug-laden fragility of project deployment, especially cross-platform.
The maintenance burden of project development.
Possible attack surface.
I ask Guido: "Why? Why bother inventing an abstract API if you weren't willing to pony up a concrete API actually doing something with that abstraction?" Why leave the fate of a million Pythonistas to the arthritic hand of the free open-source marketplace? Why create yet another techno-problem that could have been trivially solved with a 275-line decorator in the official Python stdlib?
I have no Python and I must scream.
The most Pythonic idiom is to clearly document what the function expects and then just try to use whatever gets passed to your function and either let exceptions propagate or just catch attribute errors and raise a TypeError instead. Type-checking should be avoided as much as possible as it goes against duck-typing. Value testing can be OK – depending on the context.
The only place where validation really makes sense is at system or subsystem entry point, such as web forms, command line arguments, etc. Everywhere else, as long as your functions are properly documented, it's the caller's responsibility to pass appropriate arguments.
Edit: as of 2019 there is more support for using type annotations and static checking in Python; check out the typing module and mypy. The 2013 answer follows:
Type checking is generally not Pythonic. In Python, it is more usual to use duck typing. Example:
In you code, assume that the argument (in your example a) walks like an int and quacks like an int. For instance:
def my_function(a):
return a + 7
This means that not only does your function work with integers, it also works with floats and any user defined class with the __add__ method defined, so less (sometimes nothing) has to be done if you, or someone else, want to extend your function to work with something else. However, in some cases you might need an int, so then you could do something like this:
def my_function(a):
b = int(a) + 7
c = (5, 6, 3, 123541)[b]
return c
and the function still works for any a that defines the __int__ method.
In answer to your other questions, I think it is best (as other answers have said to either do this:
def my_function(a, b, c):
assert 0 < b < 10
assert c # A non-empty string has the Boolean value True
or
def my_function(a, b, c):
if 0 < b < 10:
# Do stuff with b
else:
raise ValueError
if c:
# Do stuff with c
else:
raise ValueError
Some type checking decorators I made:
import inspect
def checkargs(function):
def _f(*arguments):
for index, argument in enumerate(inspect.getfullargspec(function)[0]):
if not isinstance(arguments[index], function.__annotations__[argument]):
raise TypeError("{} is not of type {}".format(arguments[index], function.__annotations__[argument]))
return function(*arguments)
_f.__doc__ = function.__doc__
return _f
def coerceargs(function):
def _f(*arguments):
new_arguments = []
for index, argument in enumerate(inspect.getfullargspec(function)[0]):
new_arguments.append(function.__annotations__[argument](arguments[index]))
return function(*new_arguments)
_f.__doc__ = function.__doc__
return _f
if __name__ == "__main__":
#checkargs
def f(x: int, y: int):
"""
A doc string!
"""
return x, y
#coerceargs
def g(a: int, b: int):
"""
Another doc string!
"""
return a + b
print(f(1, 2))
try:
print(f(3, 4.0))
except TypeError as e:
print(e)
print(g(1, 2))
print(g(3, 4.0))
One way is to use assert:
def myFunction(a,b,c):
"This is an example function I'd like to check arguments of"
assert isinstance(a, int), 'a should be an int'
# or if you want to allow whole number floats: assert int(a) == a
assert b > 0 and b < 10, 'b should be betwen 0 and 10'
assert isinstance(c, str) and c, 'c should be a non-empty string'
You can use Type Enforcement accept/returns decorators from
PythonDecoratorLibrary
It's very easy and readable:
#accepts(int, int, float)
def myfunc(i1, i2, i3):
pass
There are different ways to check what a variable is in Python. So, to list a few:
isinstance(obj, type) function takes your variable, obj and gives you True is it is the same type of the type you listed.
issubclass(obj, class) function that takes in a variable obj, and gives you True if obj is a subclass of class. So for example issubclass(Rabbit, Animal) would give you a True value
hasattr is another example, demonstrated by this function, super_len:
def super_len(o):
if hasattr(o, '__len__'):
return len(o)
if hasattr(o, 'len'):
return o.len
if hasattr(o, 'fileno'):
try:
fileno = o.fileno()
except io.UnsupportedOperation:
pass
else:
return os.fstat(fileno).st_size
if hasattr(o, 'getvalue'):
# e.g. BytesIO, cStringIO.StringI
return len(o.getvalue())
hasattr leans more towards duck-typing, and something that is usually more pythonic but that term is up opinionated.
Just as a note, assert statements are usually used in testing, otherwise, just use if/else statements.
I did quite a bit of investigation on that topic recently since I was not satisfied with the many libraries I found out there.
I ended up developing a library to address this, it is named valid8. As explained in the documentation, it is for value validation mostly (although it comes bundled with simple type validation functions too), and you might wish to associate it with a PEP484-based type checker such as enforce or pytypes.
This is how you would perform validation with valid8 alone (and mini_lambda actually, to define the validation logic - but it is not mandatory) in your case:
# for type validation
from numbers import Integral
from valid8 import instance_of
# for value validation
from valid8 import validate_arg
from mini_lambda import x, s, Len
#validate_arg('a', instance_of(Integral))
#validate_arg('b', (0 < x) & (x < 10))
#validate_arg('c', instance_of(str), Len(s) > 0)
def my_function(a: Integral, b, c: str):
"""an example function I'd like to check the arguments of."""
# check that a is an int
# check that 0 < b < 10
# check that c is not an empty string
# check that it works
my_function(0.2, 1, 'r') # InputValidationError for 'a' HasWrongType: Value should be an instance of <class 'numbers.Integral'>. Wrong value: [0.2].
my_function(0, 0, 'r') # InputValidationError for 'b' [(x > 0) & (x < 10)] returned [False]
my_function(0, 1, 0) # InputValidationError for 'c' Successes: [] / Failures: {"instance_of_<class 'str'>": "HasWrongType: Value should be an instance of <class 'str'>. Wrong value: [0]", 'len(s) > 0': "TypeError: object of type 'int' has no len()"}.
my_function(0, 1, '') # InputValidationError for 'c' Successes: ["instance_of_<class 'str'>"] / Failures: {'len(s) > 0': 'False'}
And this is the same example leveraging PEP484 type hints and delegating type checking to enforce:
# for type validation
from numbers import Integral
from enforce import runtime_validation, config
config(dict(mode='covariant')) # type validation will accept subclasses too
# for value validation
from valid8 import validate_arg
from mini_lambda import x, s, Len
#runtime_validation
#validate_arg('b', (0 < x) & (x < 10))
#validate_arg('c', Len(s) > 0)
def my_function(a: Integral, b, c: str):
"""an example function I'd like to check the arguments of."""
# check that a is an int
# check that 0 < b < 10
# check that c is not an empty string
# check that it works
my_function(0.2, 1, 'r') # RuntimeTypeError 'a' was not of type <class 'numbers.Integral'>
my_function(0, 0, 'r') # InputValidationError for 'b' [(x > 0) & (x < 10)] returned [False]
my_function(0, 1, 0) # RuntimeTypeError 'c' was not of type <class 'str'>
my_function(0, 1, '') # InputValidationError for 'c' [len(s) > 0] returned [False].
This checks the type of input arguments upon calling the function:
def func(inp1:int=0,inp2:str="*"):
for item in func.__annotations__.keys():
assert isinstance(locals()[item],func.__annotations__[item])
return (something)
first=7
second="$"
print(func(first,second))
Also check with second=9 (it must give assertion error)
Normally, you do something like this:
def myFunction(a,b,c):
if not isinstance(a, int):
raise TypeError("Expected int, got %s" % (type(a),))
if b <= 0 or b >= 10:
raise ValueError("Value %d out of range" % (b,))
if not c:
raise ValueError("String was empty")
# Rest of function
def someFunc(a, b, c):
params = locals()
for _item in params:
print type(params[_item]), _item, params[_item]
Demo:
>> someFunc(1, 'asd', 1.0)
>> <type 'int'> a 1
>> <type 'float'> c 1.0
>> <type 'str'> b asd
more about locals()
If you want to check **kwargs, *args as well as normal arguments in one go, you can use the locals() function as the first statement in your function definition to get a dictionary of the arguments.
Then use type() to examine the arguments, for example whilst iterating over the dict.
def myfunc(my, args, to, this, function, **kwargs):
d = locals()
assert(type(d.get('x')) == str)
for x in d:
if x != 'x':
assert(type(d[x]) == x
for x in ['a','b','c']:
assert(x in d)
whatever more...
If you want to do the validation for several functions you can add the logic inside a decorator like this:
def deco(func):
def wrapper(a,b,c):
if not isinstance(a, int)\
or not isinstance(b, int)\
or not isinstance(c, str):
raise TypeError
if not 0 < b < 10:
raise ValueError
if c == '':
raise ValueError
return func(a,b,c)
return wrapper
and use it:
#deco
def foo(a,b,c):
print 'ok!'
Hope this helps!
This is not the solution to you, but if you want to restrict the function calls to some specific parameter types then you must use the PROATOR { The Python Function prototype validator }. you can refer the following link. https://github.com/mohit-thakur-721/proator
def myFunction(a,b,c):
"This is an example function I'd like to check arguments of"
if type( a ) == int:
#dostuff
if 0 < b < 10:
#dostuff
if type( C ) == str and c != "":
#dostuff
Related
I have a function that has to take two arguments, like:
def f(first_arg: int, unused_arg) -> int:
first_arg += 1
return first_arg
I want to type my function, what should be the type of unused_arg ? From this question I guess None could be used.
For context, I'm using lax.scan from jax which needs a function with two arguments even when the second one is unused.
You can use Any?
from typing import Any
def f(first_arg: int, unused_arg: Any) -> int:
...
The doc (https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.scan.html) gives the type of f as:
Callable[[~Carry, ~X], Tuple[~Carry, ~Y]]
so I'd maybe do it with a TypeVar named X if I didn't have a more specific type:
from typing import TypeVar
X = TypeVar("X")
def f(first_arg: int, _second_arg: X) -> int:
...
Using Any can be problematic since it masks typechecking -- it doesn't matter for a variable you aren't ever using, but if your function implementation ever changes you might not realize that the formerly-unused arg isn't being typechecked, whereas if you use a TypeVar you'll get errors if you make any assumptions that aren't backed up by a bound on that variable. I try to avoid even importing Any just to make sure it's not creeping in anyplace without me noticing.
Note that per the doc it seems like your f is expected to return a tuple of two items; I'm not sure if that matters for your use case.
The standard way (at least as enforced by linters I've used) of indicating an unused parameter is to prefix it with an underscore, so I'd do that instead of naming it unused_arg).
In Python, I would like to check the type of the arguments passed to a function.
I wrote two implementations:
class FooFloat(float):
pass
# Solution 1
def foo(foo_instance):
if type(foo_instance) is FooFloat:
raise TypeError, 'foo only accept FooFloat input'
# Solution 2
def foo(foo_instance):
assert type(foo_instance) is FooFloat, 'foo only accept FooFloat input'
In my opinion the latter is easier to read and less boilerplate. However it will throw an AssertionError which is not the type of error I would like to raise.
Is there a better third solution in this case more common?
I was thinking about a decorator:
#argtype('foo_instance', FooFloat)
def foo(foo_instance):
pass
I like this idea and thinking of using it in future. I implement the third solution as following, please have a try.
def argtype(arg_name, arg_type):
def wrap_func(func):
def wrap_args(*args, **kwargs):
if not isinstance(kwargs.get(arg_name), arg_type):
raise TypeError, '%s\'s argument %s should be %s type' % (func.__name__, arg_name, arg_type.__name__)
return func(*args, **kwargs)
return wrap_args
return wrap_func
#argtype('bar', int)
#argtype('foo', int)
def work(foo, bar):
print 'hello word'
work(foo='a', bar=1)
Besides, I think use isinstance is more suitable if there is inheritance.
isinstance() does this. It accepts the type and subtypes.
if not isinstance(arg,<required type>):
raise TypeError("arg: expected `%s', got `%s'"%(<required type>,type(arg))
After eliminating all duplication (DRY principle), this becomes:
(n,t)=('arg',<required_type>);o=locals()[n]
if not isinstance(o,t):
raise TypeError("%(n)s: expected `%(t)s', got `%(rt)s'"
%dict(locals(),rt=type(o)) # fine in this particular case.
# See http://stackoverflow.com/a/26853961/648265
# for other ways and limitations
del n,t,o
Personally, I would use assert instead unless I care about which exceptions it throws (which I typically don't - an invalid argument is a fatal error, so I'm only interested in the fact one was thrown):
assert isinstance(arg,<type>),"expected `%s',got `%s'"%(<type>,type(arg))
#arg name would be seen in the source in stacktrace
Also consider duck typing instead of explicit type checks (this includes checking for special members, e.g. __iter__ for iterables). Full "duck typing vs type checks" discussion is beyond the scope of the current topic, but it looks like explicit checks are more fit for highly-specialized and/or complex interfaces as opposed to simple and generic ones.
Perhaps as a remnant of my days with a strongly-typed language (Java), I often find myself writing functions and then forcing type checks. For example:
def orSearch(d, query):
assert (type(d) == dict)
assert (type(query) == list)
Should I keep doing this? what are the advantages to doing/not doing this?
Stop doing that.
The point of using a "dynamic" language (that is strongly typed as to values*, untyped as to variables, and late bound) is that your functions can be properly polymorphic, in that they will cope with any object which supports the interface your function relies on ("duck typing").
Python defines a number of common protocols (e.g. iterable) which different types of object may implement without being related to each other. Protocols are not per se a language feature (unlike a java interface).
The practical upshot of this is that in general, as long as you understand the types in your language, and you comment appropriately (including with docstrings, so other people also understand the types in your programme), you can generally write less code, because you don't have to code around your type system. You won't end up writing the same code for different types, just with different type declarations (even if the classes are in disjoint hierarchies), and you won't have to figure out which casts are safe and which are not, if you want to try to write just the one piece of code.
There are other languages that theoretically offer the same thing: type inferred languages. The most popular are C++ (using templates) and Haskell. In theory (and probably in practice), you can end up writing even less code, because types are resolved statically, so you won't have to write exception handlers to deal with being passed the wrong type. I find that they still require you to programme to the type system, rather than to the actual types in your programme (their type systems are theorem provers, and to be tractable, they don't analyse your whole programme). If that sounds great to you, consider using one of those languages instead of python (or ruby, smalltalk, or any variant of lisp).
Instead of type testing, in python (or any similar dynamic language) you'll want to use exceptions to catch when an object does not support a particular method. In that case, either let it go up the stack, or catch it, and raise your exception about an improper type. This type of "better to ask forgiveness than permission" coding is idiomatic python, and greatly contributes to simpler code.
* In practice. Class changes are possible in Python and Smalltalk, but rare. It's also not the same as casting in a low level language.
Update: You can use mypy to statically check your python outside of production. Annotating your code so they can check that their code is consistent lets them do that if they want; or yolo it if they want.
In most of the cases it would interfere with duck typing and with inheritance.
Inheritance: You certainly intended to write something with the effect of
assert isinstance(d, dict)
to make sure that your code also works correctly with subclasses of dict. This is similar to the usage in Java, I think. But Python has something that Java has not, namely
Duck typing: most built-in functions do not require that an object belongs to a specific class, only that it has certain member functions that behave in the right way. The for loop, e.g., does only require that the loop variable is an iterable, which means that it has the member functions __iter__() and next(), and they behave correctly.
Therefore, if you do not want to close the door to the full power of Python, do not check for specific types in your production code. (It might be useful for debugging, nevertheless.)
If you insist on adding type checking to your code, you may want to look into annotations and how they might simplify what you have to write. One of the questions on StackOverflow introduced a small, obfuscated type-checker taking advantage of annotations. Here is an example based on your question:
>>> def statictypes(a):
def b(a, b, c):
if b in a and not isinstance(c, a[b]): raise TypeError('{} should be {}, not {}'.format(b, a[b], type(c)))
return c
return __import__('functools').wraps(a)(lambda *c: b(a.__annotations__, 'return', a(*[b(a.__annotations__, *d) for d in zip(a.__code__.co_varnames, c)])))
>>> #statictypes
def orSearch(d: dict, query: dict) -> type(None):
pass
>>> orSearch({}, {})
>>> orSearch([], {})
Traceback (most recent call last):
File "<pyshell#162>", line 1, in <module>
orSearch([], {})
File "<pyshell#155>", line 5, in <lambda>
return __import__('functools').wraps(a)(lambda *c: b(a.__annotations__, 'return', a(*[b(a.__annotations__, *d) for d in zip(a.__code__.co_varnames, c)])))
File "<pyshell#155>", line 5, in <listcomp>
return __import__('functools').wraps(a)(lambda *c: b(a.__annotations__, 'return', a(*[b(a.__annotations__, *d) for d in zip(a.__code__.co_varnames, c)])))
File "<pyshell#155>", line 3, in b
if b in a and not isinstance(c, a[b]): raise TypeError('{} should be {}, not {}'.format(b, a[b], type(c)))
TypeError: d should be <class 'dict'>, not <class 'list'>
>>> orSearch({}, [])
Traceback (most recent call last):
File "<pyshell#163>", line 1, in <module>
orSearch({}, [])
File "<pyshell#155>", line 5, in <lambda>
return __import__('functools').wraps(a)(lambda *c: b(a.__annotations__, 'return', a(*[b(a.__annotations__, *d) for d in zip(a.__code__.co_varnames, c)])))
File "<pyshell#155>", line 5, in <listcomp>
return __import__('functools').wraps(a)(lambda *c: b(a.__annotations__, 'return', a(*[b(a.__annotations__, *d) for d in zip(a.__code__.co_varnames, c)])))
File "<pyshell#155>", line 3, in b
if b in a and not isinstance(c, a[b]): raise TypeError('{} should be {}, not {}'.format(b, a[b], type(c)))
TypeError: query should be <class 'dict'>, not <class 'list'>
>>>
You might look at the type-checker and wonder, "What on earth is that doing?" I decided to find out for myself and turned it into readable code. The second draft eliminated the b function (you could call it verify). The third and final draft made a few improvements and is shown down below for your use:
import functools
def statictypes(func):
template = '{} should be {}, not {}'
#functools.wraps(func)
def wrapper(*args):
for name, arg in zip(func.__code__.co_varnames, args):
klass = func.__annotations__.get(name, object)
if not isinstance(arg, klass):
raise TypeError(template.format(name, klass, type(arg)))
result = func(*args)
klass = func.__annotations__.get('return', object)
if not isinstance(result, klass):
raise TypeError(template.format('return', klass, type(result)))
return result
return wrapper
Edit:
It has been over four years since this answer was written, and a lot has changed in Python since that time. As a result of those changes and personal growth in the language, it seems beneficial to revisit the type-checking code and rewrite it to take advantage of new features and improved coding technique. Therefore, the following revision is provided that makes a few marginal improvements to the statictypes (now renamed static_types) function decorator.
#! /usr/bin/env python3
import functools
import inspect
def static_types(wrapped):
def replace(obj, old, new):
return new if obj is old else obj
signature = inspect.signature(wrapped)
parameter_values = signature.parameters.values()
parameter_names = tuple(parameter.name for parameter in parameter_values)
parameter_types = tuple(
replace(parameter.annotation, parameter.empty, object)
for parameter in parameter_values
)
return_type = replace(signature.return_annotation, signature.empty, object)
#functools.wraps(wrapped)
def wrapper(*arguments):
for argument, parameter_type, parameter_name in zip(
arguments, parameter_types, parameter_names
):
if not isinstance(argument, parameter_type):
raise TypeError(f'{parameter_name} should be of type '
f'{parameter_type.__name__}, not '
f'{type(argument).__name__}')
result = wrapped(*arguments)
if not isinstance(result, return_type):
raise TypeError(f'return should be of type '
f'{return_type.__name__}, not '
f'{type(result).__name__}')
return result
return wrapper
This is a non-idiomatic way of doing things. Typically in Python you would use try/except tests.
def orSearch(d, query):
try:
d.get(something)
except TypeError:
print("oops")
try:
foo = query[:2]
except TypeError:
print("durn")
Personally I have an aversion to asserts it seems that the programmer could see trouble coming but couldn't be bothered to think about how to handle them, the other problem is that your example will assert if either parameter is a class derived from the ones you are expecting even though such classes should work! - in your example above I would go for something like:
def orSearch(d, query):
""" Description of what your function does INCLUDING parameter types and descriptions """
result = None
if not isinstance(d, dict) or not isinstance(query, list):
print "An Error Message"
return result
...
Note type only matches if the type is exactly as expected, isinstance works for derived classes as well. e.g.:
>>> class dd(dict):
... def __init__(self):
... pass
...
>>> d1 = dict()
>>> d2 = dd()
>>> type(d1)
<type 'dict'>
>>> type(d2)
<class '__main__.dd'>
>>> type (d1) == dict
True
>>> type (d2) == dict
False
>>> isinstance(d1, dict)
True
>>> isinstance(d2, dict)
True
>>>
You could consider throwing a custom exception rather than an assert. You could even generalise even more by checking that the parameters have the methods that you need.
BTW It may be finicky of me but I always try to avoid assert in C/C++ on the grounds that if it stays in the code then someone in a few years time will make a change that ought to be caught by it, not test it well enough in debug for that to happen, (or even not test it at all), compile as deliverable, release mode, - which removes all asserts i.e. all the error checking that was done that way and now we have unreliable code and a major headache to find the problems.
I agree with Steve's approach when you need to do type checking. I don't often find the need to do type checking in Python, but there is at least one situation where I do. That is where not checking the type could return an incorrect answer that will cause an error later in computation. These kinds of errors can be difficult to track down, and I've experienced them a number of times in Python. Like you, I learned Java first, and didn't have to deal with them often.
Let's say you had a simple function that expects an array and returns the first element.
def func(arr): return arr[0]
if you call it with an array, you will get the first element of the array.
>>> func([1,2,3])
1
You will also get a response if you call it with a string or an object of any class that implements the getitem magic method.
>>> func("123")
'1'
This would give you a response, but in this case it's of the wrong type. This can happen with objects that have the same method signature. You may not discover the error until much later in computation. If you do experience this in your own code, it usually means that there was an error in prior computation, but having the check there would catch it earlier. However, if you're writing a python package for others, it's probably something you should consider regardless.
You should not incur a large performance penalty for the check, but it will make your code more difficult to read, which is a big thing in the Python world.
Two things.
First, if you're willing to spend ~$200, you can get a pretty good python IDE. I use PyCharm and have been really impressed. (It's by the same people who make ReSharper for C#.) It will analyze your code as you write it, and look for places where variables are of the wrong type (among a pile of other things).
Second:
Before I used PyCharm, I ran in to the same problem--namely, I'd forget about the specific signatures of functions I wrote. I may have found this somewhere, but maybe I wrote it (I can't remember now). But anyway it's a decorator that you can use around your function definitions that does the type checking for you.
Call it like this
#require_type('paramA', str)
#require_type('paramB', list)
#require_type('paramC', collections.Counter)
def my_func(paramA, paramB, paramC):
paramB.append(paramC[paramA].most_common())
return paramB
Anyway, here's the code of the decorator.
def require_type(my_arg, *valid_types):
'''
A simple decorator that performs type checking.
#param my_arg: string indicating argument name
#param valid_types: *list of valid types
'''
def make_wrapper(func):
if hasattr(func, 'wrapped_args'):
wrapped = getattr(func, 'wrapped_args')
else:
body = func.func_code
wrapped = list(body.co_varnames[:body.co_argcount])
try:
idx = wrapped.index(my_arg)
except ValueError:
raise(NameError, my_arg)
def wrapper(*args, **kwargs):
def fail():
all_types = ', '.join(str(typ) for typ in valid_types)
raise(TypeError, '\'%s\' was type %s, expected to be in following list: %s' % (my_arg, all_types, type(arg)))
if len(args) > idx:
arg = args[idx]
if not isinstance(arg, valid_types):
fail()
else:
if my_arg in kwargs:
arg = kwargs[my_arg]
if not isinstance(arg, valid_types):
fail()
return func(*args, **kwargs)
wrapper.wrapped_args = wrapped
return wrapper
return make_wrapper
This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Tools for static type checking in Python
For most part I love the fact that you can pick up any type and drop it where you like in Python and just let the Duck typing take over. But how would one stop the darn thing from passing Compile time. Is there a way to enforce some sort of sanity checks when required at compile time, without resorting to Unit Tesing.
Use a separate tool like Pychecker to warn you about things like usages of non-existent methods or properties. This isn't part of compilation, but you could enforce it as part of your own process, such as a pre-commit hook in your VCS.
No. The Python compiler doesn't (and, generally, can't) even know if you spelled variable names correctly, much less what types might be pup in each variable, object attribute, collection slot, etc. And that's not just because the people writing it have other priorities, it is very hard to impossible with most code. For some very simple cases, static analyzers may be able to attempt something like this. But practically, it's impossible.
The compiler doesn't have type information in Python; however, the possibility of adding optional annotations to the language that would give the compiler that information has been discussed, here for instance.
In the meantime, I recommend looking into PyChecker, which may do some of what you want.
Python doesn't really have a well-defined "compile-time" that other, static, languages have.
You can use isinstance() and type() to verify that your object is an instance of a class you're expecting however.
You can just make the first line of everything resemble:
if not all(isinstance(a, b) for a, b in zip(((x, int), (y, str), (z, float))))
You could use a package like http://code.enthought.com/projects/traits/ that allows you to explicitly declare types.
You could write your code in http://cython.org/.
Python doesn't have anything like that, because compile is initialization time. You can use assert statements to enforce that specific types are being passed to your functions, i.e. assert type(foo) == list, but that's somewhat unpythonic, as it defeats the point of duck typing in the first place. What is pythonic is checking to make sure the object you're getting has the method you need. For example, if you need to iterate over the object, try this:
assert '__iter__' in dir(obj)
You can abuse decorators, to add warnings for unusual types in debug mode:
import warnings
import functools
import numbers
debug = True
class TypeWarning(Warning):
pass
def warn_types(*pos_types):
def decorator(func):
if not debug:
return func
#functools.wraps(func)
def wrapper(*args):
for i, (x, t) in enumerate(zip(args, pos_types)):
if not isinstance(x, t):
warnings.warn("Expected %s got %s for argument %d of %s"
% (t.__name__, x.__class__.__name__,
i, func.__name__),
category=TypeWarning, stacklevel=2)
return func(*args)
return wrapper
return decorator
#warn_types(numbers.Number, numbers.Number)
def add(x, y):
return x + y
This produces warnings for the programmer without breaking the functionality, and they can be turned off by turning off the debug mode. They can be also removed by a simple search-replace after you've finished coding your project.
>>> print add(3, 4)
7
>>> print add("a", "b")
__main__:1: TypeWarning: Expected Number got str for argument 0 of add
__main__:1: TypeWarning: Expected Number got str for argument 1 of add
ab
Extending this for keyword arguments is non-trivial in the general case, unless you're on Python 3 and can take advantage of annotations, in case it can become very simple.
I think you just want a quick type check, right?
I'll answer with a quick demo:
>>> m = 7
>>> m.__class__
<type 'int'>
>>> n = 6
>>> o = 6.6
>>> m.__class__ == n.__class__
True
>>> m.__class__ == o.__class__
False
>>> isinstance(o, int)
False
>>> isinstance(m, int)
True
>>>
Hope that makes sense.
This question already has answers here:
What is the best (idiomatic) way to check the type of a Python variable? [duplicate]
(10 answers)
Closed 5 years ago.
Sometimes checking of arguments in Python is necessary. e.g. I have a function which accepts either the address of other node in the network as the raw string address or class Node which encapsulates the other node's information.
I use type() function as in:
if type(n) == type(Node):
do this
elif type(n) == type(str)
do this
Is this a good way to do this?
Update 1: Python 3 has annotation for function parameters. These can be used for type checks using tool: http://mypy-lang.org/
Use isinstance(). Sample:
if isinstance(n, unicode):
# do this
elif isinstance(n, Node):
# do that
...
>>> isinstance('a', str)
True
>>> isinstance(n, Node)
True
Sounds like you're after a "generic function" - one which behaves differently based on the arguments given. It's a bit like how you'll get a different function when you call a method on a different object, but rather than just using the first argument (the object/self) to lookup the function you instead use all of the arguments.
Turbogears uses something like this for deciding how to convert objects to JSON - if I recall correctly.
There's an article from IBM on using the dispatcher package for this sort of thing:
From that article:
import dispatch
#dispatch.generic()
def doIt(foo, other):
"Base generic function of 'doIt()'"
#doIt.when("isinstance(foo,int) and isinstance(other,str)")
def doIt(foo, other):
print "foo is an unrestricted int |", foo, other
#doIt.when("isinstance(foo,str) and isinstance(other,int)")
def doIt(foo, other):
print "foo is str, other an int |", foo, other
#doIt.when("isinstance(foo,int) and 3<=foo<=17 and isinstance(other,str)")
def doIt(foo, other):
print "foo is between 3 and 17 |", foo, other
#doIt.when("isinstance(foo,int) and 0<=foo<=1000 and isinstance(other,str)")
def doIt(foo, other):
print "foo is between 0 and 1000 |", foo, other
You can also use a try catch to type check if necessary:
def my_function(this_node):
try:
# call a method/attribute for the Node object
if this_node.address:
# more code here
pass
except AttributeError, e:
# either this is not a Node or maybe it's a string,
# so behavior accordingly
pass
You can see an example of this in Beginning Python in the second about generators (page 197 in my edition) and I believe in the Python Cookbook. Many times catching an AttributeError or TypeError is simpler and apparently faster. Also, it may work best in this manner because then you are not tied to a particular inheritance tree (e.g., your object could be a Node or it could be something other object that has the same behavior as a Node).
No, typechecking arguments in Python is not necessary. It is never
necessary.
If your code accepts addresses as rawstring or as a Node object, your
design is broken.
That comes from the fact that if you don't know already the type of an
object in your own program, then you're doing something wrong already.
Typechecking hurts code reuse and reduces performance. Having a function
that performs different things depending on the type of the object passed
is bug-prone and has a behavior harder to understand and maintain.
You have following saner options:
Make a Node object constructor that accepts rawstrings, or a function
that converts strings in Node objects. Make your function assume the
argument passed is a Node object. That way, if you need to pass a
string to the function, you just do:
myfunction(Node(some_string))
That's your best option, it is clean, easy to understand and maintain.
Anyone reading the code immediatelly understands what is happening,
and you don't have to typecheck.
Make two functions, one that accepts Node objects and one that accepts
rawstrings. You can make one call the other internally, in the most
convenient way (myfunction_str can create a Node object and call
myfunction_node, or the other way around).
Make Node objects have a __str__ method and inside your function,
call str() on the received argument. That way you always get a string
by coercion.
In any case, don't typecheck. It is completely unnecessary and has only
downsides. Refactor your code instead in a way you don't need to typecheck.
You only get benefits in doing so, both in short and long run.