How to convert the type of function's arguments in Python at runtime?
Converting the type according to type hints in python

Search for a command to run...
Converting the type according to type hints in python

Good shit,your berojgari helps alot
Five abandoned Python-Go bridges, then the Unix socket that shipped.

Ten million digits, an 18-byte perceptual index, and a nearest-neighbor search that runs in a Web Worker.

Data race While the definite definition of data race differs with the concurrency model and the language, it's safe to assume that data race is when 2 or more threads try to access the same memory where at least one of them is a write operation. Seei...

File handling in Python with aiofiles and asyncio

Most of us have run into a situation where we have to change the type of a parameter in function; either it is simple str -> int or something far more complex. But at some point, we'll be repeating the code too much just for converting the types of the parameter instead of actual logic which drastically increases the code length and decreases readability.
Example:
# Defining a function with Type Hints (I'll explain this below)
def greater(a: int, b: int) -> int:
# Converting from str to int
a = int(a)
b = int(b)
# This is equivalent of
# if a > b:
# return a
# else:
# return b
return a if a > b else
# Taking input
a = input('Enter a number')
b = input('Enter another number')
# Passing the arguements to the function
greatest = greater(a, b)
print(greatest)
It's not that big of a problem for a single function, but assume that you're creating a python module that accepts certain data types, it'd be tiresome for users to convert the data type every time before passing to your module or you'd have to repeat your code just for converting the data types.
Type hints in python is a way of telling python the type of data you'd want to accept and return. It is mostly used in functions, but you can use it while declaring variables as well.
Python being a dynamically typed language, type hints don't interfere with the core principles of python. Type hints won't affect the running of the code during runtime. Type hinting as str and passing int is perfectly okay.
NOTE: Type hint is not something that executes. Python ignores your type hints while running your code.
def func_a(a, b):
return a + b
def func_a(a: int, b: int) -> int:
return a + b
Explanation:
a: int means that the parameter a needs to be a int type.-> int means that the function func_a returns a value with type inttyping python moduleThis module provides runtime support for type hints. The most fundamental support consists of the types Any, Union, Callable, TypeVar, and Generic.
typing.Any?Any is a special type indicating an unconstrained type. Every type is compatible with Any. Any is compatible with every type. It is used when you don't know the exact type of data, and the type of data may be anything.
typing.Callable?Callable is used to denote something that can be called. Most often, it is used to denote a function. Callable[[int, int], str] means that the provided function will take 2 integer arguments and return a value with the type of str. Checkout PEP 677 for more info.
typing.TypeVar?TypeVar is used for creating dynamic type hints. It is mainly used by static type checkers and IDE for proper autocompletion. Check out Python Docs for more info.
For a detailed introduction to type hints, see PEP 483.
This is a good solution, but it's only viable if there are only a few parameters in a function.
Example:
import typing as t
# Creating a TypeVar _T
_T = TypeVar('_T')
# Creating a function that converts the type
def convert_type(arg: t.Any, converter: _T) -> _T:
return converter(arg)
# Function that sqaures the given number
def square(number: t.Any) -> int:
# Converting str -> int
number = convert_type(number, int)
return number ** 2
Code Explanation:
_T). You can use TypeVar for dynamic type hints. convert_type function takes the converter with type _T and the returns value of type _T. So if we pass str in converter, the function would show str as the return value. convert_type which accepts arg and converter as it's 2 parameters.str, convert_type(number, int) is used to convert the number from str to int.The code seems good to use everywhere, but once we increase the number of parameters in the function, it starts to have the same problems like repetition of code and less readability as mentioned above.
Python has an interesting feature called decorators to add functionality to an existing code. This is also called metaprogramming because a part of the program tries to modify another part of the program at runtime.
Simply put, decorators are just a sugar-coated version of using one function on top of another function.
import typing as t
# Creating a function which takes another function
# as the parameter
def make_pretty(func: t.Callable) -> t.Callable:
# Running the passed function after printing
print("I got prettier")
func()
# Defining the actual function
def print_something() -> None:
print('something')
# Passing the actual function inside make_pretty()
make_pretty(print_something)
import typing as t
# Creating the decorator make_pretty
# It takes the function automatically as the
# paramater if you use the @decorator syntax
def make_pretty(func: t.Callable) -> t.Callable:
# This inner function takes all the arguments (args)
# and keyword arguments (kwargs) passed to the
# function we decorate. It can be None as well
def inner_function(*args, **kwargs):
# We can do anything we want here
print("I got prettier")
# Finally running the passed function
func(*args, **kwargs)
# Returning the inner function so that
# decorator can use it properly
return inner_function
# using @make_pretty decorates print_something()
# function with make_pretty() function
@make_pretty
def print_something() -> None:
print("something")
# Actually running the function
print_something()
Explanation:
function(*args) means if we call function(1, 2, 3, 4, 5), the value of the arguement args would be (1, 2, 3, 4, 5) {Tuple} and vice-versa.function(**kwargs) means if we call function(a=1, b=2, c=3), the value of the arguement kwargs would be {a: 1, b: 2, c: 3} {Dictionary} and vice-versa.args and kwargs will remain None.@ is used to denote the usage of a decorator.For converting the argument type according to the type hints in runtime, we'll have to get the type hints of the function.
from typing import get_type_hints
def add_1(a: int) -> int:
return a + 1
print(get_type_hints(add_1))
OUTPUT:
{'a': <class 'int'>, 'return': <class 'int'>}
import typing as t
# Creating a TypeVar for dynamic type hinting in the function
_T = t.TypeVar("_T")
# Creating a function for converting type_conversion
def type_conversion(value: t.Any, value_type: type[_T]) -> _T:
# If the value's type has a function named 'convert_types'
# than we call the function and pass the value to it
if hasattr(value_type, "convert_types"):
return value_type.convert_types(value)
# Returning the value if it's of Any type
elif value_type is t.Any:
return value
# if it doesn't we return the value by forcefully
# trying to convert
return value_type(value)
# Creating a decorator to convert types
def convert_types(func: t.Callable) -> t.Callable:
def inner_function(*args, **kwargs) -> t.Any:
# Getting the type hints of the function
annotations = t.get_type_hints(func).copy()
# Removing the 'return' from the dictioary
# as it is unnecessary right now
annotations.pop("return")
# Getting only the values of annotations for
# looping with the args of the function
args_annotations = annotations.values()
new_args = list(args)
# Looping through the annotations and args
for index, (arg, arg_type) in enumerate(zip(args, args_annotations)):
# If arg is not of the required type, we convert it
if not isinstance(arg, arg_type):
new_args[index] = type_conversion(arg)
for arg_value, (arg_name, arg_type) in zip(kwargs, annotations.items()):
# If arg is not of the required type, we convert it
if not isinstance(arg_value, arg_type):
kwargs[arg_name] = type_conversion(arg_value)
return func(*new_args, **kwargs)
return inner_function
# Creating a simple function to add 1
@convert_types
def add_1(a: int) -> int:
return a + 1
# Passing a string to the function
print(add_1("1234"))
OUTPUT:
1235
type_conversion for converting the type of the value according to the passed required type. This function first checks if the type has a method convert_types and if it does, the value is passed onto that method. Else if the value is of type typing.Any, only the value is returned else the value is forcefully tried to convert.convert_types decorator gets the type hints of the function, checks if the type hints are matching with the value types, and it's not, the values' type is changed. zip function is used to combine 2 iterables. Zipping 2 tuples; (1, 2, 3) and ('a', 'b', 'c') will produce a zip object with value ((1, 'a'), (2, 'b'), (3, 'c')).enumerate function is used to get the value and index while looping through the iterable at once. isinstance is used to check if the value is an instance of the passed class.While it may seem like the above type checker only supports the built-in data types in python. It's not actually true. We can make our own classes compatible with the type checker by simply adding a convert_types @classmethod.
import typing as t
class MyOwnClass:
@classmethod
def convert_types(cls, value: t.Any) -> 'MyOwnClass':
return str(value)
NOTE: Yes, you can type-hint as strings as well instead of actual objects. You can use
-> 'int'instead of-> intas per your use case.
Now you can type-hint your parameters with MyOwnClass and it'd still work.
t.Union types; it'd be better to use pydantic.Python type hints were a game changer to developer experience ever since it was introduced in python 3.5. Python runtime checking is a fun topic to experiment with and might have a very wide range of use cases. But one must use it with extreme caution as it may introduce unexpected slowdowns and bugs to the code.