Как обнародовать список аргументов для функции python?

Я пытаюсь найти элегантный питонический способ решить следующую проблему. У меня есть класс VideoAnalysisWidget, который создает целую кучу дочерних виджетов, таких как VideoFileChoiceWidget, RegionWidget и т. д. Я хочу, чтобы VideoAnalysisWidget.__init__ передал любые соответствующие аргументы ключевого слова в VideoFileChoiceWidget.__init__, RegionWidget.__init__,... Для этого я хотел бы использовать код как следующее:

import inspect

def filterArgs(func, **kwargs):
    spec = inspect.getargspec(func)
    ks = set(kwargs.keys())
    ks.discard('self')
    kinc = ks & set(spec.args)
    kexc = ks - set(spec.args)
    inc = {k: kwargs[k] for k in kinc}
    exc = {k: kwargs[k] for k in kexc}
    return(inc, exc)

Потом в другом месте:

def __init__(self, **kwargs):
    (fcwArgs, otherArgs) = filterArgs(VideoFileChoiceWidget.__init__, **kwargs)
    fcw = VideoFileChoiceWidget(**fcwArgs)
    ...

При таком подходе мой код для VideoAnalysisWidget не должен отслеживать список аргументов VideoFileChoiceWidget. Если позже я пересмотрю VideoFileChoiceWidget, чтобы принять новый аргумент, мне не нужно будет вносить изменения в удаленный код VideoAnalysisWidget.

Теперь вот проблема. Это прекрасно работает, если VideoFileChoiceWidget имеет только явно определенные параметры ключевого слова. (Кстати, я согласен не использовать никаких позиционных параметров, кроме self, для любой из этих функций.) Но что, если VideoFileChoiceWidget также имеет аргумент **kwargs? На самом деле это так, потому что он является подклассом ContainerWidget, поэтому я хочу передать ему любые дополнительные дополнительные аргументы. (И ContainerWidget занимает ТОЛЬКО **kwargs.) По сути, проблема в том, что это мое решение не может быть вложенным.

Возможным решением было бы присоединить список дополнительных аргументов к VideoFileChoiceWidget.__init__, например:

VideoFileChoiceWidget.__init__._args = ['description', 'visible', 'children']

... затем измените filterArgs, чтобы использовать его, если он доступен. Но есть ли лучший, более питонический способ сделать это?


person Leon Avery    schedule 23.11.2014    source источник
comment
Я не уверен, что это лучший способ из-за использования inspect, но в этих случаях обычно я группирую множество kwargs в класс и передаю его.   -  person simonzack    schedule 23.11.2014
comment
Да, я вижу в этом решение. Аргумент против этого (который может быть не ХОРОШИМ аргументом) заключается в том, что он по существу перестраивает встроенные функции передачи аргументов KW в пользовательском коде, делая все менее прозрачным.   -  person Leon Avery    schedule 23.11.2014
comment
Вот мое мнение: когда аргументы являются общими и слишком большими для управления дочерними классами, как вы сказали в своем случае, это может быть сделано для разделения проблем. На самом деле это делается довольно часто.   -  person simonzack    schedule 24.11.2014


Ответы (1)


Я продолжаю использовать вариант подхода, который я изложил в своем исходном посте, в котором я прикрепляю атрибут к __init__. Однако вместо того, чтобы составлять список, я использую более гибкий подход с использованием функции. Я определяю неприсоединенную функцию argNames, которая принимает один или несколько классов или функций в качестве аргументов и возвращает набор имен аргументов, используя inspect и рекурсивно вызывая атрибут argNames функций. Затем я могу прикрепить это к VideoFileChoiceWidget.__init__ (например), используя:

class VideoFileChoiceWidget(ContainerWidget):
    ...<stuff>...
    def __init__(arg1, arg2, ..., **kwargs):
        ...<more stuff>...

    __init__.argNames = lambda: (argNames(ContainerWidget) | 
                                 {'description', 'visible', 'children'})

Любой, кто хочет передать аргументы VideoFileChoiceWidget, использует filterArgs, как указано в вопросе. Я реализовал это, и это, кажется, работает.

Ниже для справки приведены мои текущие определения argNames и filterArgs.

def argNames(*args):
    """
    Get the names of the argument for a function

    Returns:
    --------
    A set whose members are the argument names

    Parameters:
    -----------
    func1, func2, ... -- the functions to be analyzed. The arguments
        are discovered by inspecting func. In addition, if
        func.argNames is defined, it is called (without arguments) to
        get a list of addition names. Any number of functions may be
        listed. If func is a class, func.__init__ is used
        instead. 'self' is always removed from the argument list.
    """
    names = set({})
    for a in args:
        try:
            func = a
            spec = inspect.getargspec(func)
        except TypeError:
            func = a.__init__
            spec = inspect.getargspec(func)
        names |= set(spec.args)
        try:
            names |= set(func.argNames())
        except AttributeError:
            pass
    names.discard('self')
    return(names)

def filterArgs(func, **kwargs):
    """
    Select keyword arguments for a function
    """
    args = argNames(func)
    ks = set(kwargs.keys())
    ks.discard('self')
    kinc = ks & args
    kexc = ks - args
    inc = {k: kwargs[k] for k in kinc}
    exc = {k: kwargs[k] for k in kexc}
    return(inc, exc)
person Leon Avery    schedule 27.11.2014