Yes - this is the expected and desired behavior of a Python function.
Possible typing tips with syntax def x(a: int, b: int)->int:
- are just that: tips. There are some programs that check statically this is - before you run the program, if there is "type leak" - that is, if any point in your program calls this function with something that is not an integer. The official program for this is the mypy
- but note that both the use of a type checker like mypy and doing something after it issues its warnings are completely optional.
The right practice is to ensure - through unit testing and integration testing - that your functions do what you want when they receive the right parameters.
As commented in the pergunra, Python is a language for "adults who consent to what they do" - so, if your function was created, and is documented as such, to work with integers and yet you call it strings, you must know what you are doing. (And to prevent this from happening unintentionally, there is type-hinting syntax and checkers like mypy).
What happens is that if you call the function with incompatible realmetne parameters, at some point, will be raised ma exception of TypeError
in the body of the function (in this case, if you pass a number and a string, for example). Python being a dynamic language, this is caused at runtime - so the importance of having tests in your program.
Now, in exceptional cases - you see, it’s not supposed to be the rule - you can put type checkers at runtime. Python allows much more sophisticated mechanisms than the example of if type(a) == ...
that you put in. But all these checks can do at runtime is ... raise an exception. An exception that would eventually happen naturally when the code tries to execute something impossible with the past parameters. With few exceptions - as in this case: the strings are concatenated. But again, if it were dis parameters that could not be summed with the "+" operator, it would make little difference to the program at runtime if the error happened right at the function input, or at the bottom line, where the sum operation is done.
On the other hand, this kind of thing is definitely not the role of the compiler of a dynamic language like Python. The language, like PHP Javascript, Ruby, simply does not need to reserve space beforehand when compiling the program for a single parameter type in the function. Nuances like C, Java, G are static - and the compile-time error happens not to be "nice and find possible flaws" - but why the incompatible parameter does not "fit" into the function call, already at compile time. Then, again, in Python there is the optional step of static typing checking with mypy.
Now, just to not close the answer without any concrete example, I’ll give you an example of how you could check the input and output parameters of a function without having to encode a if
with an expression for each parameter.
One possible approach is with the use of a "Decorator" - this in Python is a function that takes another function as a parameter (the function that is "decorated"), and returns a third function - which via rule is the original "enveloped" function in some complementary code. There is a special syntax to apply decorators, which makes its use very practical.
So in this case, we can combine the typehints and create a developer that,
when calling the function, it checks whether the parameters passed are in accordance with the types specified by type-hinting in the decorated function. In case of incompatible parameters, the Developer raises a Typeerror, otherwise proceed with the original call.
Python syntax allows a great deal of flexibility in how parameters are passed to a function - and create this Decorator to account for all possible ways, among named parameters, expanded parameters with "*" that took into account all possible forms of parameter passing would be reasonably complex - if you are actually going to use something like this, it is better to make use of the existing utility functions in the module inspect
from the standard library. A developer that acts only on parameters passed by name however, can be quite simple. Type-hints are inside the attributo __annotations__
of the original function:
def typecheck(func):
annotations = func.__annotations__
def wrapper(*args, **kwargs):
if args:
raise TypeError("Only named parameters allowed")
for name, value in kwargs.items():
if not name in annotations:
raise TypeError(f"Parameter {name} does not exist")
if not isinstance(value, annotations[name]):
raise TypeError(f"Parameter {name} must be an instance of {annotations[name].__name__}")
# all parameter type parameters succesful - perform original function call:
return func(**kwargs)
return wrapper
And at the interactive prompt:
In [16]: @typecheck
...: def soma(a: int, b: int):
...: return a + b
...:
In [17]: soma(a="f", b="a")
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
(...)
TypeError: Parameter a must be an instance of <class 'int'>
updating
The questioner continued in the comments:
Okay, note, I know what I’m doing when programming, but, like,
is polymorphism? I learned that to differentiate functions in a
class, compiler checks your signature, ie the amount of
parameters and their types. If I can’t tell the compiler what they are
the types, as it will differentiate two functions with the same name and
same amount of attributes, but with different behaviors?
Being a dynamic typing language, the classical concept of "polymorphism" does not exist in Python - what comes as arguments for any function or method are "Python objects".
In fact, no need to rewrite almost all methods because of trivial changes in objects passed as an advantage parameter. For example, any function made to perform calculations with basic operations on numeric types will work with integers, floats, complexes, fractions, Decimals, etc... (and of course, you can specify that you only want the type numbers.Number
no type-hinting to encompass all numeric types, thanks to "inheritance adoption" mechanisms will work fine - but you do not need to limit your method if this is not necessary)
If you need different behaviors according to the typing of the incoming parameters, your options are as follows:
- Use a block
if/elif/else
to select the desired behavior
- Use the decorator
functools.singledispatch
- it allows, with the use of decorators, a form of polymorphism in separate functions based on the typing of first argument passed to a function - https://docs.python.org/3/library/functools.html#functools.singledispatch
- Rethink your design - it makes sense that you have a function with the same signature that does different things depending on the type of data?
- for example, if you are working with interfaces in classes, and want some methods with the same name to be available in several different classes, the first parameter (self) will always be an object of the class itself - and in that case, the distinct implementation already naturally happens in the class itself. Polymorphism would only enter to distinguish the code from the second parameter onwards.
- Create a set of utilities and patterns between decorators and tables in dictionaries to have "personal" tools for polymorphism in your projects. This is very quiet and fun given the nature of the language
What exactly do you want to do? If it is to ensure that the values are integer, you have already done so. Weak typing languages are thus by definition and mainly Python, where the main philosophy is: the developer knows what he is doing, so if the function should not receive a string, there should not exist in the code such a call.
– Woss
@Andersoncarloswoss actually, this is just my resistance, which I got used to with C++, but how does polymorphism look if I don’t have the signatures?
– Felipe Nascimento