Функция, имеющая подфункции и их аргументы в качестве аргументов - PullRequest
0 голосов
/ 17 июня 2019

Я бы хотел построить случайный фрейм данных для панд. Для этого мне нужна функция Python, принимающая в качестве аргументов:

  • numpy распределения
  • их аргументы

Например:

распределение 1: нормальное | аргументы: означает = 0, стандартное устройство = 1, размер = 100

распределение 2: униформа | аргументы: низкий = 0, высокий = 1, размер = 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}

Но моя цель состоит не в том, чтобы напечатать требуемые распределения и связанные с ними параметры, а в том, чтобы создать образец для каждого распределения.

Ответы [ 2 ]

1 голос
/ 17 июня 2019

Обратите внимание, что для работы этой реализации функции должны быть функциями, а не строками

Если вы хотите вернуть функцию, вызванную с набором 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), заключается в том, что а) я предполагаю, что вы не всегда используете один и тот же модуль, и б) вызов переименованного импорта из 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])

И вы можете просто расширить это до кода в первом разделе

0 голосов
/ 17 июня 2019

Вы можете разработать функцию, которая принимает другие функции в качестве входных данных и выполняет их.Вот что делает оператор **:

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(), а не сейчас).

Я не думаю, что есть официальное название для этого оператора (*для списков и ** для диктов), но я называю это оператором распаковки, потому что распаковывает структуру данных в аргументы функции.


Обычно безопаснее чтобы указать явные именованные параметры, в этой ситуации - вам нужно придумать шаблон, чтобы люди, использующие вашу функцию, знали, как они должны называть свои ключевые слова.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...