Typing unused arguments - python

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).

Related

Python 3 Protocols - is it possible to require specific arguments in method, but allow extra arguments as well?

I am trying type hint a protocol to encompass a small variety of similar API interfaces.
I want to allow certain methods to be flexible and allow for extra arguments and keyword arguments, since different implementations have different 'bonus' args, but all of them share a common set of arguments at the first half of the signature.
This is awkward because I'm trying to write compatibility for some classes that I don't control directly, so I can't edit the implementation signatures.
from typing import Protocol
class ExampleProtocol(Protocol):
def test(self, important_arg:str, *args, **kwargs):
pass
class ExampleImplementation:
def test(self, important_arg:str):
pass
class ExampleImplementation2:
def test(self, important_arg:str, unnessary_arg:bool):
pass
implementation: ExampleProtocol = ExampleImplementation()
implementation2: ExampleProtocol = ExampleImplementation2()
Pylance gives me this:
"ExampleImplementation" is incompatible with protocol "ExampleProtocol"
"test" is an incompatible type
Type "(important_arg: str) -> None" cannot be assigned to type "(important_arg: str, *args: Unknown, **kwargs: Unknown) -> None"
Parameter "**kwargs" has no corresponding parameter
Is it possible to type hint in this case, or do I need to just use Any instead of the protocol? Does python allow hinting "dynamic" signatures that have a few important arguments?
EDIT: It seems that this isn't possible, so the best solution I could come up with is to use the Callable type to at the very least indicate that a method with that name should exist, without enforcing any arguments, like so:
class ExampleProtocol(Protocol):
test: Callable
This seems to work and not give any errors on the two examples.
The short answer here is 'no', mostly because your ExampleImplementation method test (or ExampleImplementation2) has a subset of the parameters allowed by ExampleProtocol. That is, there is some set of parameters that ExampleProtocol accepts that ExampleImplementation would break on - anything past that first argument.
Without knowing what you're trying to get at exactly, you're left with manually filtering the *agrs and **kwargs parameters for those that you care about. The former can be difficult because they're not named, they're only positional.
That said, the type-hint you're reaching for is Optional. It doesn't solve your problem on it's own, but it's a type that declares 'either this is None or it is of type x'. For instance:
def f(a: Optional[bool]) -> None:
# a is either None or a boolean value
Any doesn't really do anything for you here, I think.

Is there an update on Typing.Optional on python 3.9 or am I doing something wrong?

While I was coding for help command for my bot in Discord.py, This function was not working as I expected.
def syntax(command):
cmd_and_aliases = '|'.join([str(command), *command.aliases])
params = []
for key, value in command.params.items():
if key not in ('self', 'ctx'):
params.append(f'[{key}]' if 'NoneType' in str(value) else f'<{key}>')
params = ' '.join(params)
return f'`{cmd_and_aliases} {params}`'
What I wanted for this function was, returning and printing names and aliases to call up an discord command and what parts are necessary and what parts are not to run that command properly. As the Optional[str] in Typing can be translated as Union[str, None], What I wanted to get was what their could be and if it can be None, then it will be appended to the params list wrappend in square bracket, or else in angle bracket.
`name|aliases <necessary part> [optional part]`
This is what the function would originally would have returned, but both the necessary part and optional part are shown as wrapped in angle brackets.
#command(name='slap', aliases=['hit'])
async def slap_member(self, ctx, member: Member, *, reason: Optional[str] = 'for no reason'):
For example, for this command the result would have been like below.
slap|hit <Member> [reason]
But what I ended was getting was:
slap|hit <Member> <reason>.
Is there something I have done wrong or is there any update that would have made this function broken?
If there is something I did wrong, please help me by showing what I can do otherwise.
The "Why"
This behavior has indeed changed in 3.9; I believe this commit specifically is the cause. It looks like these changes begun in this commit, but I believe the manifestation of your particular issue is due to the former.
I'm not deeply familiar with the plumbing of the typing module, but from what I can gather, the representation of Optional is now special-cased and returns itself as-is, i.e. typing.Optional[x] instead of "expanding" to typing.Union[x, NoneType]. They still have the same "type", however, but the important bit here is that their "type" has changed to _UnionGenericAlias, whose __repr__ is:
def __repr__(self):
args = self.__args__
if len(args) == 2:
if args[0] is type(None):
return f'typing.Optional[{_type_repr(args[1])}]'
elif args[1] is type(None):
return f'typing.Optional[{_type_repr(args[0])}]'
return super().__repr__()
Possible Solutions
First, I'd like to point out that you can use command.clean_params instead of command.params (which strangely isn't documented, from what I can tell). It functions the same, except it automatically excludes self and ctx so you don't have to check for it. I'll be using it in my solutions below.
N.B. clean_params returns parameters as inspect.Parameter objects, so we have an extra layer to go through.
I came up with two possible solutions. The first is quite close to what you have, in that we do a simple string check for typing.Optional:
for key, value in command.clean_params.items():
optional = str(value.annotation).startswith('typing.Optional')
params.append(f'[{key}]' if optional else f'<{key}>')
Now I personally am not fond of string checks when it comes to determining types (Generics in this case), so I would opt for something a bit different. As described in PEP 585, we can do runtime introspection on Generics, so let's take advantage of that.
for key, value in command.clean_params.items():
anno = value.annotation
try:
optional = anno.__origin__ is Union and anno.__args__[1] is type(None)
except AttributeError:
optional = False
params.append(f'[{key}]' if optional else f'<{key}>')
Another benefit of this approach is that it will work in Python 3.8 (and I think 3.7 as well), whereas the string checking version will obviously only work in 3.9. You could leverage sys.version_info and have multiple string checks, but that seems like too much hassle in comparison.

It is possible to specify that all positional args be of either one or other argument type?

I have been using the crap out of the typing module because I have discovered it makes going back to read and understand my code about 3x easier. It also makes pycharm twice as useful.
I am aware that to specify the type of single OR positional arguments, one can do this:
def f(*args: int = 0) -> None:
# integers are expected for args
pass
Furthermore, if the positional arguments can be of multiple types, you can use Union to handle this case:
from typing import Union
def f(*args: Union[str, int]) -> None:
# integers OR strings are expected for args
pass
My question is: is there a way to tell the type checker to make sure the args are EITHER ALL ints, OR ALL strs, but not a mixture of both, but not limited to ONLY ints or ONLY strs?
I'm pretty sure you want to use a TypeVar:
T = typing.TypeVar('T', int, str) # can be either int or str, but not both in a single call
def f(*args: T) -> None:
pass

Best way to check function arguments? [closed]

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

Python type checking [duplicate]

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.

Categories