【问题标题】:How can I write a generic Python 2.2 function that returns a list of unset parameters?如何编写一个返回未设置参数列表的通用 Python 2.2 函数?
【发布时间】:2012-01-11 17:47:09
【问题描述】:

我有一个带有许多输入参数的函数,我需要一个函数,该函数将为值为 '' 或 None 的每个参数返回参数名称(不是值)列表

通常我会在这种方法中抛出异常。如果有人想通过抛出异常来解决问题,那很好。我仍然要求函数返回参数名称列表。

总结

  1. 返回未设置参数的参数名称列表
  2. “unset”表示参数的值不是空字符串或None
  3. 接受单个参数:单个维度列表或字典
  4. 列表应包含完整的空参数名称集
  5. 我需要它向后兼容 Python 2.2 和 Jython
  6. 2.2 不可协商。代码必须在我们无权升级的遗留系统上运行。糟透了。
  7. 参数不是命令行参数,而是 功能。
  8. 参数存储在单独的变量中,但如果需要,我可以手动将它们放入字典中。
  9. 不是返回 Python 变量名称列表,而是返回每个空变量的用户友好描述列表。示例:“数据库名称”与“db_name”。

对提出的问题的回答:

  1. 如果遇到未知参数怎么办?我们不在乎。我们创建参数列表以验证并仅选择那些由于系统逻辑而必需的参数。因此,我们永远不会将未知参数放入要验证的参数列表中
  2. 对于非强制性或必须以其他方式验证的 UI 参数(int vs. string 等)怎么办?我们不会将非强制性参数放在传递给验证函数的列表中。对于其他更复杂的验证,我们会单独处理这些,即席。这个函数看起来很方便的原因是因为空参数是我们做的最常见的验证,并且为每个函数编写一个if not foo: 在函数之间变得乏味,我们有很多函数。
  3. 请解释“”“我们平台的性质”“”。还有“”“它到达单个变量”“”......什么命名空间中的单个变量? “”“(预处理)”“”是什么意思? – 约翰·马钦 2 天前。答:变量在全局命名空间中。我们使用代码注入(类似于 C 预处理器将代码替换为宏名称的方式,不同之处在于我们将变量值替换为标签,类似于:

    DATABASE_NAME = ^-^将用户输入的数据库名称变量放在这里^-^

预处理器运行后会这样结束:

DATABASE_NAME = "DB1"

这是一个具体的例子,说明为什么一个简单的方法抛出异常是行不通的。我已根据请求重写为使用异常而不是返回值:

def validate_parameters(params_map):
    """
    map is like {foo: "this is foo"}
    """
    missing_params_info = []
    for k,v in params_map.items():
        if not k:
            missing_params_info.append(v)
    if missing_params_info:
        raise TypeError('These parameters were unset: %s' % missing_params_info)

params = {}
params['foo'] = '1'
params['bar'] = '2'
params['empty'] = ''
params['empty2'] = ''
params['None'] = None
params_map = {
    params['foo']: 'this is foo',
    params['bar']: 'this is bar',
    params['empty']: 'this is empty',
    params['empty2']: 'this is empty2',
    params['None']: 'this is None',
}

print validate_parameters(params_map)


bash-3.00# python /var/tmp/ck.py
Traceback (most recent call last):
  File "/var/tmp/ck.py", line 26, in ?
    print validate_parameters(params_map)
  File "/var/tmp/ck.py", line 10, in validate_parameters
    raise TypeError('These parameters were unset: %s' % missing_params_info)
TypeError: These parameters were unset: ['this is empty2', 'this is None']

它对我们不起作用的两个原因:它只打印 empty2,即使还有另一个空参数“empty”。 “empty”被“empty2”覆盖,因为它们在映射中使用相同的键。

第二个原因:我需要在运行此函数后的某个时间点将描述列表放入一个变量中。也许这在例外情况下是可能的,但我现在不知道如何。

我发布的答案似乎可以解决所有这些问题,但并不理想。我已将问题标记为已回答,但如果有人发布更好的答案,我会更改它。

谢谢!

【问题讨论】:

  • 请定义“验证”。这是否意味着“有价值”?此外,返回“用户友好的描述”而不是正确的结果是一件可怕的事情。非常糟糕。请重新考虑并澄清您的要求。 func( **some_map ) 作为将任意映射作为参数传递给函数的一种方式有什么问题?
  • Validate表示参数的值不是''或者None。它必须通过测试:"if foo:" 为 True。我意识到描述要求很糟糕,但这是必须将参数值链接到 UI 上面向用户的字段的结果。你能详细说明 func( **some_map ) 吗?我不明白。我知道这是指向地图的指针,但不确定如何解决问题。谢谢!
  • 要详细说明返回“正确结果”,这意味着返回例如包含参数本身和描述的映射。如果地图为空,则成功。如果不是,我们有我们要报告的错误列表。
  • “通常我会在这种方法中抛出异常。”好的。 “我宁愿把它留给调用者来确定结果是否需要例外或只是一个警告。”什么?异常确实留给调用者处理异常、将其更改为警告或让应用程序崩溃。例外是总是正确的做法。请重新考虑您的要求。还。请不要添加 cmets。请更新问题以使其更清楚。
  • 如果提供了 unknown 参数名称,您想做什么?引发异常?

标签: python jython


【解决方案1】:

这个问题仍然很混乱。非常。

这可能是您询问对函数代码对象的自省:

def noisy_typerror( func ):
    def fix_exception( **kw ):
        try:
            # This is generally needless; mostly a waste of CPU cycles.
            if not all(kw[arg] for arg in kw  ):
                raise TypeError
            # Simply apply the function and see if a TypeError occurs 
            return func( **kw )
        except TypeError:
            required= ", ".join( func.func_code.co_varnames[:func.func_code.co_argcount] )
            provided= ", ".join( "{0}={1!r}".format(k,v) for k,v in kw.items() )
            raise TypeError( "{2}( {0} ) got {1}".format(required, provided,func.func_name) )
    return fix_exception

@noisy_typerror
def some_func( this, that, the_other ):
    a= this
    b= that
    print( this, that, the_other )

在旧版本的 Python 中应用装饰器

def the_real_func( this, that, the_other ):
   etc.

some_func= noisy_typerror( the_real_func )

这里是这个装饰器的一些用例

try:
    some_func( this=2, that=3 )
except TypeError, e:
    print e 
try:
    some_func( this=4 )
except TypeError, e:
    print e 
try:
    some_func( this=2, that=3, the_other='' )
except TypeError, e:
    print e 

我通过打印 TypeError 字符串得到这些类型的结果。

some_func( this, that, the_other ) got this=2, that=3
some_func( this, that, the_other ) got this=4
some_func( this, that, the_other ) got this=2, the_other='', that=3

【讨论】:

  • 这似乎可以满足我的需求。它在 2.6.4 上运行良好,但在 2.2 上运行不佳。你有什么具体的问题我可以回答以帮助减少问题吗?谢谢。
  • “不在 2.2 上”?您能否具体说明这可能意味着什么?我问了大概六个具体的问题,所有这些问题都得到了粗鲁和不屑一顾的答案。我觉得再问他们都没有意义。您提供的答案自相矛盾,而且 - 这里重要的是 - 没有明智的用例。有人会如何使用您提供的示例?没有有意义的上下文。没有实际问题。并且没有理由编写任何代码。但是,它似乎相当于“TypeError 没有提供足够的信息”
  • 我很抱歉遇到粗鲁和不屑一顾。我感谢您贡献的意见。它有帮助。关于 Python 2.2 兼容性的问题,有几件事阻止了上述答案的工作。第一个是装饰器,第二个是 print_function 导入。 2.2 不支持它们。当我使用这个版本运行时,我得到了 2.6.4 上没有的错误。
  • 您应该能够在没有太多帮助的情况下修复print_function 问题。删除导入。使用print 语句。装饰器问题更加微妙,因为它需要用实际的函数调用替换装饰器。
  • 示例:“我有一个包含许多输入参数的函数,我需要一个函数,该函数将为值为 '' 或 None 的每个参数返回参数名称(而非值)列表”。您似乎只是想知道所有未通过某些验证套件的参数。这似乎就是你想要的。要知道 所有 无效参数值,而不是 Python 默认的一个无效参数值。整个问题的其余部分和 cmets 似乎大多是被误导的混淆。
【解决方案2】:

我很确定我不明白这个问题,也不明白你发布的“最佳解决方案”如何满足要求,但只是从以下方面工作:

我有一个有很多输入参数的函数,我需要一个函数 这将为每个返回参数名称(不是值)列表 值为 '' 或 None 的参数

这是执行该行似乎要求的简单方法:

def validate_parameters(args):
    unset = []
    for k in args:
        if args[k] is None or args[k]=="":
            unset.append(k)
    return unset

然后从函数的 first 行调用 validate_parameters:

def foo(a, b, c):
    print "Unset:", validate_parameters(locals())

>>> foo(1, None, 3)
Unset: ['b']
>>> foo(1, None, "")
Unset: ['c', 'b']

如果不是因为 Python 2.2 的要求,您可以在单行列表理解中完成所有操作。重要的是您必须从函数的第一行调用它,以确保 locals() 只获取参数而不获取任何其他局部变量。

【讨论】:

  • 感谢您的回答。我进行了编辑以展示如何满足“用户可读”参数描述要求,然后我意识到我删除了这个要求以使问题更容易。您满足了发帖时所写的要求。谢谢!
【解决方案3】:

这是我能想到的最佳答案。它需要在调用函数之前做很多准备工作,所以我不喜欢它。但是,它满足所有要求。

感谢所有参与的人,我很抱歉这个问题需要这么多修改!

def validate_parameters(params_map):
    """
    map is like {foo: "this is foo"}
    """
    missing_params_info = []
    for k,v in params_map.items():
        if not v:
            missing_params_info.append(k)
    return missing_params_info
# or do this if you want to use exceptions:
#    if missing_params_info:
#        raise TypeError('These parameters were unset: %s' % missing_params_info)

params = {}
params['foo'] = '1'
params['bar'] = '2'
params['empty'] = ''
params['empty2'] = ''
params['None'] = None

reverse_params_map = {
    'this is foo' : params['foo'],
    'this is bar' : params['bar'],
    'this is empty' : params['empty'],
    'this is empty2' : params['empty2'],
    'this is None' : params['None'],
}

print validate_parameters(reverse_params_map)

bash-3.00# python /var/tmp/ck.py
['this is empty2', 'this is empty', 'this is None']

【讨论】:

  • -1: 注释 map is like {foo: "this is foo"} 与使用具有 {'this is foo':'1'}reverse_params_map 的代码相矛盾。非常难以理解这如何“解决”问题。它似乎只是增加了一层无用的复杂性。
【解决方案4】:

这是一种常见的“大量变量”模式。

def function( variable1, variable2, variable3, ..., variablen ):
   """user-friendly description of the function.
   :param variable1: meaning, units, range, whatever.
   :param variable2: meaning, units, range, whatever.
   ...
   :param variablen: meaning, units, range, whatever.
   :returns: range, type, whatever.
   """
   # do the processing

不要检查丢失或无效的参数。 Python 已经完成了所有需要的类型检查。只需编写您的代码。不做任何特别或额外的事情来“验证”输入。 当出现异常时,这意味着输入是错误的。

就是这么简单。

不要通过在无关的 if 语句中重写 Python 的所有类型检查来使其更复杂。

还有。

切勿将“错误返回”与有效返回混用。任何类型的错误输入必须导致异常。好的输入会返回好的值。错误的输入引发异常。

就是这么简单。不要让它变得更复杂。

调用该函数时,可以这样做:

the_variables = { 
    'variable1': some value,
    'variable2': some value,
    ...
    'variablen': some value,
}
try:
    function( **the_variables )
except Exception:
    print( function.__doc__ )

有什么遗漏吗?你会得到一个TypeError。有什么不正确的None 或空的吗?你会得到一个 ValueError(或 TypeError,这取决于)。

当出现问题时,您会打印出用户友好的函数描述。

这很好用,根本不需要太多编程。

【讨论】:

  • 我得到了抛出异常和打印文档字符串的概念。正是“进行处理”部分和我的复杂要求使这变得困难。已更新问题以尝试更好地解释。感谢您的回复。
  • “做处理”的意思是只做处理。这没有“复杂性”。只需编写最简单的代码即可完成实际工作。 “验证”不是必需的,因为无效数据会抛出异常,不会失败。
  • 要求是不抛出异常,而是将参数描述的整个列表报告为空。这个解决方案会抛出一个异常并打印函数的文档字符串。我知道如何因为空参数而引发异常。我再次更新问题以更清楚。
  • @Foo:“报告空的参数描述的整个列表”?您是指所有缺失或无效的参数 values 吗?如果是这样,您可能需要更改您正在使用的术语。
  • 问题不在于类型验证,而是在出现问题时向用户提供有意义的错误消息。如果您建议不检查参数是否为空或无,那么我们必须同意不同意。我总是在公共函数中验证参数。我相信一旦发现错误并提供有用的错误消息,就应该立即捕获它们。允许继续执行会使调试更加困难并且不安全。如果你不是在暗示这个,我误解了你。
【解决方案5】:

为什么不是 Zoidberg 装饰器?

def argsnotempty(**requiredargs):

    def decorator(func):

        def wrapper(*args, **kwargs):
            code     = func.func_code
            argsreq  = code.co_argcount - 1
            argsrec  = len(args)
            posargs  = code.co_varnames[1:argsreq + 1]
            errs     = []

            # validate positional args
            for i, arg in enumerate(args):
                if i == len(posargs):
                    break
                # falsy but not False: 0, '', None, [], etc.
                if not (arg or arg is False):
                    argname = posargs[i]
                    if argname in requiredargs:
                        errs.append(argname + " (" + requiredargs[argname] + ")")

            # validate keyword args
            for argname, arg in kwargs.iteritems():
                if argname in requiredargs:
                    if not (arg or arg is False):
                        errs.append(argname + " (" + requiredargs[argname] + ")")

            # make sure all required args are present
            for argname in requiredargs:
                if argname not in kwargs and argname not in posargs:
                    errs.append(argname + " (" + requiredargs[argname] + ")")

            return func(errs, *args, **kwargs)

        wrapper.__name__, wrapper.__doc__ = func.__name__, func.__doc__

        return wrapper

    return decorator

装饰器检查以确保指定的参数不为空,然后使用空白的“友好”参数名称列表作为第一个参数调用包装函数。它还尝试检查关键字参数。不检查未指定给装饰器的参数。

用法:

@argsnotempty(a="alpha", b="beta", g="gamma")
def foo(errs, a, b, g):
    print errs

foo(3.14, "blarney", None)    # prints "['g (gamma)']"

如果您没有获得所需的值,以下是引发异常的示例:

@argsnotempty(a="alpha", b="beta", g="gamma")
def bar(errs, a, b, g):
    if errs:
       raise ValueError("arguments " + ", ".join(errs) + " cannot be empty")

bar(0, None, "")

当然,您可以调整装饰器来为您执行此操作,而不是在每个函数中包含样板代码。

编辑:修复了一些错误

【讨论】:

  • 没有得到“Zoidberg”的笑话:(
  • Why not Zoidberg? 抱歉,我可能在 Reddit 上闲逛太多...
  • OP 希望兼容 Python 2.2。 2.2 不做装饰器。
  • 我得到了 Zoidberg 并感谢它。是的,不幸的是 2.2 的兼容性让我们受益。将更新标题以使其更加清晰。感谢您的回答。
  • 如果唯一的问题是装饰器(没有检查),您可以轻松解决。无需特殊 @ 语法即可应用装饰器,只需使用 def f(...) {...}; f = decorator(args)(f) 而不是 @decorator(args) def f(...) {...}
【解决方案6】:

你的函数听起来很大。您是否考虑过是否适合将其拆分或将其变成一个单独的类?

【讨论】:

  • 正如我最初编写的那样,该函数不到 10 行,但存在空字符串键错误。因为我已经通过一个函数完成了大部分工作,所以我认为可以用一个函数完成。如果需要的话,我会接受课程并更新标题以反映这一点。
  • 如果原来是10行,那么突破可能没有意义。只是想我会把它放在那里。
【解决方案7】:

要检查是否所有必需的参数都被传递到您的函数中,您可以创建一个字典,将所有必需的参数映射到 None,然后在每个方法的开头复制和更新该字典。

needed_params = {'one': None, 'two': None, 'three': None}

def my_func(**kwargs):
    params = needed_params.copy()
    params.update(kwargs)
    for key, value in params.iteritems():
        if not value:
            raise TypeError("You need to provide the argument %s" % key)
    result = do_stuff_here
    return result

正如 cmets 中所述,返回“用户友好”描述可能不是一个好主意。相反,如果缺少参数,您可能希望引发错误。然后,您就可以在 UI 的其他地方处理此错误。

Kindall 建议使用装饰器。根据您希望检查的复杂程度,我认为您可以使用比他的建议更简单的方法:

def check_needed_params(target):
    needed_params = {'one': None, 'two': None, 'three': ''}
    def wrapper(*args, **kwargs):
        params = needed_params.copy()
        params.update(kwargs)
        for key, value in params.iteritems():
            if not value:
                raise TypeError("You need to provide the argument '%s'" % key)
        return target(**params)
    return wrapper

您可以使用它来识别需要检查其参数的函数,如下所示:

@check_needed_params
def adder(**kwargs):
    return kwargs["one"] + kwargs["two"] + kwargs["three"]

然后,当调用此函数时,如果您提供所有结果,它将无缝运行,但如果您不提供,则会引发错误。

>>> adder(one=1, two=2, three=3)
6
>>> adder(one=1, two=2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "c:/Users/.../python-6940fCr.py", line 8, in wrapper
TypeError: You need to provide the argument three

【讨论】:

  • 你不需要 copy 模块来制作字典的浅拷贝。
  • OP 希望兼容 Python 2.2。 2.2 不做装饰器。
  • 感谢您的回答,很聪明。不幸的是,副本似乎不在 Python 2.2 中,这是我需要的条件之一。我曾想过尝试在没有副本的情况下复制它,但是我必须将多个对象传递给该方法。另外,我确实需要包含参数描述,但我看不到这样做的方法。也许我可以将它们留在函数之外的散列中并以这种方式处理。如果我在您的解决方案中遗漏了什么,请告诉我!
  • 我的装饰器中额外的复杂性是处理位置参数(更好地编写函数来调用它)和处理请求的“友好”名称福。不过,如果你只需要处理 kwargs,它确实会简化很多事情!
  • 如果我没记错的话,字典在 2.2 中应该有一个 copy 方法。至于参数描述,我不确定你为什么仍然需要它们。我认为提出异常(可能是您创建的异常)仍然是这里最好的调用,就像 S.Lott 建议的那样。如果您有更多详细信息说明为什么您需要采取不同的做法,并展示您尝试过的东西,我会非常乐意修改我的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-09-09
  • 2012-12-25
  • 2011-01-19
  • 1970-01-01
  • 1970-01-01
  • 2021-12-28
  • 2020-01-29
相关资源
最近更新 更多