【问题标题】:Pythonic way of using type as function argument使用类型作为函数参数的 Pythonic 方式
【发布时间】:2020-02-09 21:34:38
【问题描述】:

在函数中将对象类型作为参数传递的最 Pythonic 方式是什么?

让我举个例子。假设我试图从环境变量中获取配置。因为所有环境变量都是字符串,所以我需要将值转换为正确的类型。

为此,我需要告诉函数所需的类型。这就是coerce 参数的目的。我的第一直觉是将所需的类型作为coerce 的值传递。但是,我不确定这样做是否有任何影响或问题。

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'>

在我看来,这似乎比使用 "str""bool" 等字符串来指定类型更清晰和更符合 Python 风格。但是,我想知道将内置类型作为函数参数传递是否会导致任何问题。

【问题讨论】:

  • 这很好。你有什么具体的顾虑吗?这可能更适合 CodeReview 而不是 StackOverflow
  • @juanpa.arrivillaga 没有特别关注。只是不确定这是否会在以后回来咬我。感谢您指出 CodeReview 我不知道它存在!

标签: python function types


【解决方案1】:

由于您使用 type 参数所做的只是将其与您期望的某些特定类型逐一进行比较,而不是实际使用 type 来构造该类型的对象,因此它没有做任何不同的事情将'str''bool' 等字符串作为参数传入,并将其与多个字符串常量进行比较。

相反,您可以将str2boolstr2int 等转换函数本身作为参数,以便您可以调用coerce(value) 以通用方式转换value。将此类转换函数存储为专用类的属性以提高可读性,如下所示:

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

这个输出:

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

【讨论】:

    【解决方案2】:

    您可以通过直接传递转换函数来简化代码并使其更强大(类型注释留作练习):

    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)
    

    也许还有一个列表案例的辅助函数:

    def make_str2list(delimiter=','):
        return lambda s: s.split(delimiter)
    
    test_var = get_config("TEST_VAR", make_str2list(':'), [])
    

    【讨论】:

    • 是的。另外,您已经获得了一些内置函数,例如 strint。通过str 转换字符串什么都不做,但它概括了这个概念
    【解决方案3】:

    在这个特定的例子中,我认为强制不应该是 type,而应该是 Enum:两者都是因为 get_config 只支持一小部分可能的值,并且因为它不直接在其处理中使用类型值。如果不出意外,函数签名使用 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
    

    也就是说,如果你真的想使用 type 那么 Python 3.8(应该在两天后发布)支持literal types,这意味着你可以将函数声明如下:

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

    【讨论】:

      猜你喜欢
      • 2023-03-09
      • 2011-03-09
      • 2014-01-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-21
      • 1970-01-01
      相关资源
      最近更新 更多