I’m wondering if it’s possible to use a class to define function args in pure Python, or maybe by using a package.
For example:
class FunArgs:
first: int
second: str
def my_fun(FunArgs):
pass
if __name__ == "__main__":
my_fun(first=1, second="OK")
The reason to use that syntax is just its simplicity.
Often we tend to add too much of args in functions and also put notes about them, which reduce its readability.
The example is very simple, but in general those types are longer and more complex, which reduce readability more.
8
I won’t go too much into details about the appropriateness of such an approach to class design (after all, Python’s philosophy is basically “do whatever you want as long as you know what you are doing”).
The simplest way you can get to such a syntax is using dataclass
.
from dataclasses import dataclass
@dataclass
class FunArgs:
first: int
second: int
def my_fun(fun_args: FunArgs):
print(f'This is first: {fun_args.first}')
print(f'This is second: {fun_args.second}')
if __name__ == "__main__":
my_fun(FunArgs(1, "OK"))
3
This isn’t a recommendation, but if you’re looking to increase the readability in your codebase, consider using a decorator that validates **kwargs based on a provided dataclass. This way, you can avoid passing a dataclass instance when calling your function. Instead, the dataclass serves solely for validating that the correct arguments are passed, similar to how a Python function with named arguments would behave if an argument is missing. You define the decorator once (in this case args_with_class
) and then you can reuse it on all your functions. You are free to change the name to something that makes sense for you.
This validation in the decorator is just an example, Pydantic might be a good option here.
from dataclasses import dataclass, fields
from typing import Callable, Type
@dataclass
class FunArgs:
first: int
second: str
def args_with_class(dataclass_type: Type):
def decorator(func: Callable):
def wrapper(*args, **kwargs):
# Extract dataclass fields
required_fields = {f.name for f in fields(dataclass_type)}
# Check if all required fields are present in kwargs
missing_fields = required_fields - kwargs.keys()
if missing_fields:
raise TypeError(f"Missing required arguments: {', '.join(missing_fields)}")
# Call the actual function
return func(*args, **kwargs)
return wrapper
return decorator
@args_with_class(FunArgs)
def my_fun(**kwargs):
print(f'This is first: {kwargs["first"]}')
print(f'This is second: {kwargs["second"]}')
if __name__ == "__main__":
# Correct usage
my_fun(first=1, second=2)
# Example with incorrect types
try:
my_fun(first=1, second="OK")
except TypeError as e:
print(e)
# Example with missing arguments
try:
my_fun(first=1)
except TypeError as e:
print(e)
4