Python 中函数参数的模式匹配

Pattern matching on function parameters in Python

假设我有一个名为 generator 的函数,它 returns 一个 4 元组,在某些预先指定的范围内随机选择值。假设元组的形式为 (age, sex, location, marital_status):

age is in range(5, 85)
sex is a member of the set {"m", "f"}
location is a member of the set of all the cities in California
marital_status is a member of {"married", "single", "separated"}

另一方面,假设我定义了 20 个不同的函数,定义如下:

def p1 (age, sex, location, marital_status)
def p2 (age, sex, location, marital_status)   
.
.

其中 p1 应该接收具有以下形式值的参数:

`age` must be in the range 20 to 45
`sex` must be male
`location` could be any city in Southern California
`marital_status` could be either single or married

想象一下 p2p20 的一组不同值。

确定哪一组生成值与哪个函数匹配的实用方法是什么?

在这种情况下,所有定义都完全相同,但我可以想象在定义中可能存在细微差异的情况,例如 p18 可能是 def p1 (age, location),但对agelocation.

的可能性范围

P.S。这些模式不一定相互排斥,这意味着一组生成的值也可能匹配多个函数。

# define test t1 for function p1
def t1(params):
    return params["age"] in range(5, 85) \
        and params["sex"] in ["m", "f"] \
        and cityof(params["location"], "California") \
        and params["marital_status"] in ["married", "single", "separated"]
# and similarly for other p* functions

# define functions
def p1(params): ...
def p2(params): ...
# and so on

# bind tests and functions
RULES = {
    (t1, p1),
    (t2, p2),
    ...
}

# have the right functions called
def go(params):
    for rule in RULES:
        if rule[0](params):
            rule[1](params)

# test it
go({"age": 6, "sex": "m", "location": "somewhere", "marital_status": "single"})

一些评论:

  • 测试可以包含在函数本身中。
  • 测试函数也必须处理丢失的参数。
  • 也可以直接使用函数参数。

实际上有几种可能的变体,但主要原理是相同的:
找到匹配的函数并调用它

如果您愿意将格式化的文档字符串添加到您的函数中(而不是对每个参数进行类型检查),那么您可以考虑这样做:

# This function has a foratted doc string.
# :argument: Truth condition to be evaluated
# If any condition is False, function won't be called
def party_list(name, age, sex):
    """
    :name:"{}" != "Dad"
    :age:17< {} <25
    :sex:True
    """
    print("You're invited to my party, {}!".format(name))


# We will make some sample data
keys = ["name", "age", "sex"]
values = [["John", 24, "Male"],
          ["Sarah", 25, "Female"],
          ["Pikachu", 2, "Pokemon"],
          ["Pizza guy", 18, "Male"]]
# Turn it in to a dictionary
for key, value in enumerate(values):
    values[key] = {t:p for t, p in zip(keys, value)}


# These conditions can be dynamically made for each function,
# because we have access to the doc string from the outside
conditions = list(filter(lambda c: ':' in c, party_list.__doc__.split('\n')))
for friend in values:
    for case in conditions:
        tag, code = case.split(':')[1:]
        if not eval(code.format(friend[tag])):
            break
    else:
        party_list(friend["name"], friend["age"], friend["sex"])

作为 Python3.X(但不是 2.X)中的 Pythonic 方式,您可以附加 annotation information(关于一个函数的参数和结果)到一个函数对象。在这里你可以在装饰器中使用这个特性来包装你的函数来检查你的参数范围。

例如,您可以使用以下范围测试函数:

def rangetest(func):
    def onCall(**kargs):
        argchecks = func.__annotations__

        if all(val in range(*argchecks.get(arg)) for arg,val in kargs.items()):
            return func(**kargs)
        else :
              print ("invalid arg range")
    return onCall


@rangetest
def func(a:(1, 5), b:(4,7), c:(0, 10)):
    print(a + b + c)

演示:

func(a=2, b=6, c=8)
16
func(a=2, b=6, c=15)
invalid arg range

这里有一点。首先是,由于注释信息在字典中(python returns 它作为字典)并且字典没有特定的顺序,因此您需要在函数中使用关键字参数能够在注释信息字典中得到它的相对范围

此外,我在这里只使用了数字范围,但您可以使用一些自定义范围,例如单词列表,例如您在 all 中的 question.But 中显示的内容,您需要检查其类型,然后基于它的类型使用了正确的操作:

all(kwargs.get(arg) in range(*arg_range) if is instance (arg_range,tuple) else kwargs.get(arg) in arg_range for arg,arg_range in argchecks.items())