Pythonic way of using type as function argument

python type alias
python type annotations
python type() function
python type hint multiple types
python type checking
python function return type
python typing boolean
python function parameter type

What would be the most pythonic way to pass an object type as an agrgument in a function?

Let me give you an example. Let's say I was trying to get a configuration from an environment variable. Because all environment variables are strings I need to cast the value to the correct type.

To do this I need to tell the function the desired type. This is the purpose of the coerce argument. My first instinct is to pass in the desired type as the value for coerce. However, I am not sure if there are any implications or problems in doings so.

import os

# The Function
def get_config(config: str, coerce: type, default: any, delimiter: str = ","):
    value = os.getenv(config, None)  # Get config from environment
    if value is None:
        return default  # Return default if config is None

    if coerce is bool:
        value = str2bool(value)  # Cast config to bool
    elif coerce is int:
        value = str2int(value)  # Cast config to int
    elif coerce is list:
        value = value.split(delimiter)  # Split string into list on delimiter

    return value  # Return the config value

# Usage
os.environ["TEST_VAR"] = "True"

test_var = get_config("TEST_VAR", bool, False)

print(test_var)  #  output is True
print(type(test_var))  # output is <class 'bool'>

To me this seems more clear and pythonic than using a string such as "str" or "bool" to specify the type. However, I would like to know if there could be any problems caused by passing around built in types as function arguments.

You can simplify the code and make it more powerful by just directly passing the conversion function (type annotations are left as an exercise):

def get_config(config, convert, default):
    value = os.getenv(config, None)
    return default if value is None else convert(value)

test_var = get_config("TEST_VAR", str2bool, False)

and perhaps having a helper function for the list case:

def make_str2list(delimiter=','):
    return lambda s: s.split(delimiter)

test_var = get_config("TEST_VAR", make_str2list(':'), [])

How do Python functions handle the types of the parameters that , Python is strongly typed because every object has a type, every object knows its type, it's impossible to accidentally or deliberately use an object of a type "as if"  Python type () The type () function either returns the type of the object or returns a new type object based on the arguments passed. The type () function has two different forms: type (object) type (name, bases, dict)

Since all you are doing with the type argument is to compare it one by one to certain specific types that you're expecting, rather than actually using type to construct objects of that type, it is doing nothing different from passing in a string such as 'str' or 'bool' as an argument and compare it to several string constants.

Instead, you can make the conversion functions such as str2bool and str2int themselves an argument, so that you can call coerce(value) to convert value in a generic way. Store such conversion functions as attributes of a dedicated class for better readability, as demonstrated below:

import os
import typing

class to_type:
    bool = 'True'.__eq__
    int = int
    list = lambda s: s.split(',')

def get_config(config: str, coerce: typing.Callable = lambda s: s, default: any = None):
    value = os.getenv(config, None)
    if value is None:
        return default
    return coerce(value)

os.environ["TEST_VAR"] = "True"
print(get_config("TEST_VAR", to_type.bool))
os.environ["TEST_VAR"] = "2"
print(get_config("TEST_VAR", to_type.int))
os.environ["TEST_VAR"] = "a,b,c"
print(get_config("TEST_VAR", to_type.list))
os.environ["TEST_VAR"] = "foobar"
print(get_config("TEST_VAR"))

This outputs:

True
2
['a', 'b', 'c']
foobar

typing — Support for type hints, In the function greeting , the argument name is expected to be of type str and the Use object to indicate that a value could be any type in a typesafe manner. Python allows functions to be called using keyword arguments. When we call functions in this way, the order (position) of the arguments can be changed. Following calls to the above function are all valid and produce the same result.

In this specific instance, I'd argue that coerce should not be a type, but rather an Enum: both because get_config only supports a small set of possible values, and because it doesn't use the type values directly in its handling. If nothing else, the function signature is more precise with an Enum.

ConfigType = Enum('ConfigType', 'STR BOOL INT LIST')

def get_config(config: str, coerce: ConfigType, default: any, delimiter: str = ","):
    value = os.getenv(config, None)  # Get config from environment
    if value is None:
        return default  # Return default if config is None

    if coerce is ConfigType.BOOL:
        value = str2bool(value)  # Cast config to bool
    elif coerce is ConfigType.INT:
        value = str2int(value)  # Cast config to int
    elif coerce is ConfigType.LIST:
        value = value.split(delimiter)  # Split string into list on delimiter

    return value  # Return the config value

That said, if you really wanted to use type then Python 3.8 (which should be released in two days' time) supports literal types, meaning you could declare the function as follows:

def get_config(config: str, coerce: Literal[str, bool, int, list], default: any, delimiter: str = ","):

PEP 612 -- Parameter Specification Variables, There currently are two ways to specify the type of a callable, the function, is an instance of the Python idiom of one function passing all  The arbitrary keyword argument dictionary: If your function requires an undetermined series of named arguments, it is possible to use the **kwargs construct. In the function body, kwargs will be a dictionary of all the passed named arguments that have not been caught by other keyword arguments in the function signature. Using *args, Python

Python Type Checking (Guide) – Real Python, Similarly, the optional align argument should have type bool with the default a way to associate arbitrary expressions to function arguments and return values. There is no syntax to indicate optional or keyword arguments; such function types are rarely used as callback types. Callable[, ReturnType] (literal ellipsis) can be used to type hint a callable taking any number of arguments and returning ReturnType .

Defining Your Own Python Function – Real Python, The most straightforward way to pass arguments to a Python function is any way that makes sense for the type of object the function returns. This way, the user of the function can manipulate the recipient list as a list beforehand, and it opens the possibility to pass any sequence, including iterators, that cannot be unpacked as other sequences. The arbitrary keyword argument dictionary is the last way to pass arguments to functions.

Python Function Arguments with Types, Syntax and Examples , In this Python Function Arguments tutorial, we will learn about what function arguments used in Python and its type: Python Keyword Arguments, Default  A generic function is composed of multiple functions implementing the same operation for different types. Which implementation should be used during a call is determined by the dispatch algorithm. When the implementation is chosen based on the type of a single argument, this is known as single dispatch.

Comments
  • this is fine. do you have any specific concerns? This is probably a better fit for CodeReview not StackOverflow
  • @juanpa.arrivillaga No specific concern. Just not sure if this will come back to bite me later. Thank you for pointing out CodeReview I did not know it existed!
  • yup. plus, you’re already getting some builtins like str, int. casting strings via str does nothing, but it generalizes the concept