【问题标题】:Function having sub functions and their arguments as arguments具有子函数及其参数作为参数的函数
【发布时间】:2019-06-18 04:13:38
【问题描述】:

我想构建一个 pandas 随机数据框。为了实现这个目的,我需要一个 Python 函数作为参数:

  • numpy 分布
  • 他们的论点

例如:

分布1:正常|参数:mean = 0,标准 dev = 1,size = 100

分布2:统一|参数:low = 0,high = 1,size = 100

等等……

我事先不知道不同的分布及其参数是什么。

然后,主函数将使用每个对应的参数生成分布的随机样本。

我尝试过类似的方法:

import numpy as np

def myfun( **kwargs ) :
    for k , v in kwargs.items() :
        print( k )
        print( v )

当我使用这些参数调用该函数时:

myfun( fun_1 = 'np.random.normal' , arg_1 = { 'loc' : 0 , 'scale' : 1 , 'size' : 7 } ,
       fun_2 = 'np.random.uniform' , arg_2 = { 'low' : 0 , 'high' : 1 , 'size' : 7 } )

输出是:

fun_1
np.random.normal
arg_1
{'loc': 0, 'scale': 1, 'size': 7}
fun_2
np.random.uniform
arg_2
{'low': 0, 'high': 1, 'size': 7}

但我的目的不是打印所需的分布及其相关参数,而是为每个分布生成一个样本。

【问题讨论】:

  • myfun 是否应该返回,比如说,np.random.normal 使用arg_1 中定义的参数调用?
  • 函数是否需要是字符串,或者将它们作为函数(不带引号)传递就足够了?比如你会通过np.random.normal而不是'np.random.normal'
  • 对不起我的例子。函数应该作为函数传递,而不是字符串。

标签: python function


【解决方案1】:

注意,函数应该是函数,而不是字符串,这样实现才能工作

如果您想返回使用一组kwargs 调用的函数,那么您已经非常接近了。我会为func 使用位置参数,然后您可以将kwargs 传递给func,这更明确一点:

def myfunc(func, **kwargs):
    return func(**kwargs)

然后,您可以将每对 func, **kwargs 包装为元组,然后执行 for 循环:

# This would be called like
somelist = [(np.random.normal, { 'loc' : 0 , 'scale' : 1 , 'size' : 7 }),
            (np.random.uniform , { 'low' : 0 , 'high' : 1 , 'size' : 7 })]

results = []

# append results to a list
for func, kwargs in somelist:
    results.append(myfunc(func, **kwargs))

通过这种方式,您不必担心您的任何变量命名,而且它更具可读性。您知道循环将处理成对的项目,在本例中为 func, kwarg 对,您的函数可以显式处理这些项目

处理字符串调用

因此,有一些方法可以完成这项任务,虽然有点棘手,但总体上应该不会太糟糕。您需要修改 myfunc 来处理函数名称:

# func is now a string, unlike above

def myfunc(func, **kwargs):
    # function will look like module.class.function
    # so split on '.' to get each component. The first will 
    # be the parent module in global scope, and everything else
    # is collected into a list
    mod, *f = func.split('.') # f is a list of sub-modules like ['random', 'uniform']
    # func for now will just be the module np
    func = globals().get(mod)
    for cls in f:
        # get each subsequent level down, which will overwrite func to
        # first be np.random, then np.random.uniform
        func = getattr(func, cls)
    return func(**kwargs)

我使用globals().get(mod) 的原因是a) 我假设您可能并不总是使用相同的模块,并且b) 从sys.modules 调用重命名的导入将产生KeyError,这是'你想要什么:

import sys
import numpy as np

sys.modules['np'] # KeyError

sys.modules['numpy']
# <module 'numpy.random' from '/Users/mm92400/anaconda3/envs/new36/lib/python3.6/site-packages/numpy/random/__init__.py'>

# globals avoids the naming conflict
globals()['np']
# <module 'numpy.random' from '/Users/mm92400/anaconda3/envs/new36/lib/python3.6/site-packages/numpy/random/__init__.py'>

然后getattr(obj, attr) 将返回每个后续模块:

import numpy as np

getattr(np, 'random')
# <module 'numpy.random' from '/Users/mm92400/anaconda3/envs/new36/lib/python3.6/site-packages/numpy/random/__init__.py'>

# the dotted access won't work directly
getattr(np, 'random.uniform')
# AttributeError

所以,总共:

import numpy as np

func, kwargs = ('np.random.normal', { 'loc' : 0 , 'scale' : 1 , 'size' : 7 })

myfunc(func, **kwargs)

array([ 0.83276777,  2.4836389 , -1.07492873, -1.20056678, -0.36409906,
       -0.76543554,  0.90191746])

您可以将其扩展到第一部分中的代码

【讨论】:

  • numpy 函数不能是字符串才能工作 - 你会得到TypeError: 'str' object is not callable。否则这是一个很好的答案
  • @GreenCloakGuy 啊,对,我必须eval 或者做一些奇怪的事情,我看看我是否可以编辑它以使其有意义
  • 你不必eval() 它,你不应该因为eval() 是危险的 - 但只是引用它们而不带引号应该有效,因为这会调用对函数的引用首先。 np.random.normal 而不是 'np.random.normal'
  • 理想情况下,是的,您会不惜一切代价避免 eval ,但 OP 特别要求将其作为字符串输入。我会要求澄清
【解决方案2】:

您可以设计一个函数,将其他函数作为输入并执行它们。这就是 ** 运算符所做的:

def myfun(**kwargs):
    kwargs['fun_1'](**kwargs['arg_1'])  # calls the function kwargs[fun_1] with the keyword args given in kwargs[arg_1]
    kwargs['fun_2'](**kwargs['arg_2'])

然后你可以像这样指定你的 kwargs:

myfun(fun_1=np.random.normal, 
      arg_1={'loc': 0, 'scale': 1, 'size': 7},
      fun_2=np.random.uniform,
      arg_2={'low': 0, 'high': 1, 'size': 7},
     )

注意np.random.normal 是如何不在引号中的 - 我们通过引用来引用实际函数,但还没有调用它(因为我们想在 myfun() 中这样做,而不是现在)。

我认为这个运算符没有正式名称(* 表示列表,** 表示字典),但我称它为解包运算符,因为它解包 一个数据结构到函数参数。


在这种情况下,声明显式命名参数通常更安全 - 你需要想出一个模式,以便使用你的函数的人知道他们应该如何命名他们的关键字。

【讨论】:

  • @GrennCloakGuy 在 myfun 中,您处理 2 个子函数调用,但您事先不知道要调用的子函数的数量。
  • 这就是为什么我说你需要为关键字想出一个模式——然后你可以遍历kwargs 并获取彼此对应的函数和参数。不过,@C.Nivs 上面的答案更好
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-24
  • 2020-01-26
  • 1970-01-01
相关资源
最近更新 更多