【问题标题】:Pattern matching on function parameters in PythonPython中函数参数的模式匹配
【发布时间】:2015-07-23 05:50:47
【问题描述】:

假设我有一个名为 generator 的函数,它返回一个 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

并想象p2 一直到p20 的一组不同值。

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

在这种情况下,所有定义完全相同,但我可以想象定义可能略有不同的情况,例如p18 可能是def p1 (age, location),对@987654332 的可能性范围有特定限制@和location

附:这些模式不一定是互斥的,这意味着一组生成的值可能会匹配多个函数。

【问题讨论】:

  • 由于您使用的是 Python 3,函数注释 可能是要走的路:python.org/dev/peps/pep-3107
  • 模式是互斥的,还是可能有更多的模式匹配?
  • @dlask:好点。模式匹配多个函数的可能性很小。
  • 是否可以在 p1-p20 上循环,检查函数本身,如果与 reqs 不匹配则抛出 ValueError 并继续执行下一个函数?避免代码重复。
  • @JLPeyret:听起来很有趣。能否详细解答一下?

标签: python function python-3.x pattern-matching parameter-passing


【解决方案1】:

作为 Python 3.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将其作为字典返回)并且字典没有特定的顺序,因此您需要在函数中使用关键字参数才能获得其在注释中的相对范围信息词典。

此外,我在这里只使用了数字范围,但您可以使用一些自定义范围,例如您在问题中显示的单词列表。但是在 all 内,您需要检查其类型,然后根据其类型使用正确的操作:

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

【讨论】:

    【解决方案2】:
    # 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"})
    

    只有几厘米:

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

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

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-03-21
        • 2012-09-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-11-07
        • 1970-01-01
        • 2015-10-19
        相关资源
        最近更新 更多