Как заполнить конкретные позиционные аргументы с помощью partial в python?

#python

#python

Вопрос:

В принципе, то, что я хотел бы сделать, это:

 >>> from functools import partial
>>> partial(str.startswith, prefix='a')('a')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: startswith() takes no keyword arguments
  

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

PS Я понимаю, что вместо этого я могу использовать a lambda .

Комментарии:

1. Продолжайте и используйте lambda . Это будет немного медленнее, потому partial что реализовано на C. Если производительность является премией, вы также можете реализовать свой вариант на C.

Ответ №1:

Это невозможно сделать. Вы должны создать функцию-оболочку.

Предположительно, вы бы использовали аргументы ключевых слов, как вы пытались сделать — для этого они и нужны, верно? К сожалению, как вы обнаружили, стандартные библиотечные функции python не принимают именованные параметры. Таким образом, это невозможно, учитывая текущую реализацию partial , без создания другой функции для запуска помех.

Согласно принятию PEP 309, для включения был принят «конструктор типа partial(), привязывающий крайние левые позиционные аргументы и любые ключевые слова». Кроме того, в нем говорится:

Обратите внимание, что когда объект вызывается как функция, позиционные аргументы добавляются к аргументам, предоставленным конструктору, а аргументы ключевого слова переопределяют и дополняют аргументы, предоставленные конструктору.

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

Поскольку добавляются дополнительные позиционные аргументы, не было бы способа предоставить некоторый предыдущий позиционный аргумент (с помощью этой реализации).

Тем не менее, это продолжается:

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

Итак, это, по-видимому, может быть в таблице, но в нынешнем виде это реализовано не так.

Ради раскрытия, акцент в кавычках выше был моим собственным.

Ответ №2:

Я знаю, что это устарело, но я постоянно сталкиваюсь с этим, и я думаю, что создал отличное решение. Я использую слегка измененную версию partial , которая использует объект Ellipses ( ... ) в качестве значения-заполнителя, если вы не знаете его во время создания объекта. Это очень полезно именно для этого!

Вот оригинальный __call__ метод partial

 def __call__(self, /, *args, **keywords):
    keywords = {**self.keywords, **keywords}
    return self.func(*self.args, *args, **keywords)
  

Вместо этого мы можем использовать литерал ... как особый случай для указания значения-заполнителя

 >>> type(...)
<class 'ellipsis'>
  

Вот вся реализация:

 class bind(partial):
    """
    An improved version of partial which accepts Ellipsis (...) as a placeholder
    """
    def __call__(self, *args, **keywords):
        keywords = {**self.keywords, **keywords}
        iargs = iter(args)
        args = (next(iargs) if arg is ... else arg for arg in self.args)
        return self.func(*args, *iargs, **keywords)
  

Использование довольно простое

 def foo(a, b, c, /, *, d):
    print(f"A({a}) B({b}) C({c}) D({d})")

f1 = bind(foo, 1, 2, 3, d=4)
f1()

f2 = bind(foo, 1, 2, d=4)
f2(3)

f3 = bind(foo, 1, ..., 3, d=4)
f3(2)

f4 = bind(foo, ..., 2, ..., d=4)
f4(1, 3)

f5 = bind(foo, ..., d=5)
f5(1, 2, 3, d=4)
  

Комментарии:

1. Очень умная идея с ellipsys, спасибо!

2. Вы должны предложить это списку рассылки python-dev! Тем более, что PEP 309 указывает на некоторую открытость для таких предложений.

3. Действительно, очень умно, я поддерживаю, что вы должны предложить это в списке рассылки python-dev.

4. Это достаточно хорошо и должно быть пакетом, если это не так

5. Обратная ссылка: discuss.python.org/t /…

Ответ №3:

Если вам действительно это нужно, вы можете использовать rpartial из funcy сторонней библиотеки.

Его код здесь:

 def rpartial(func, *args):
    return lambda *a: func(*(a   args))
  

Итак, ваш случай можно обработать следующим образом:

 >>> startswith_a = rpartial(str.startswith, 'a')
>>> startswith_a('abc')
True
>>> startswith_a('def')
False
  

Ответ №4:

Используйте этот код:

 # See: functoolspartial for binding...

class Function(object):
    def __init__(self, fn):
        self.fn = fn

    def __call__(self, *args, **kwargs):
        return self.fn(*args, **kwargs)

    def __add__(self, other):
        def subfn(*args, **kwargs):
            return self(*args, **kwargs)   other(*args, **kwargs)
        return subfn


class arg(object):
    """Tagging class"""
    def __init__(self, index):
        self.index = index


class bind(Function):
    """Reorder positional arguments.
    Eg: g = f('yp', _1, 17, _0, dp=23)
    Then g('a', 'b', another=55) --> f('yp', 'b', 17, 'a', dp=23, another=55)
    """

    def __init__(self, fn, *pargs, **pkwargs):
        # Maximum index referred to by the user.
        # Inputs to f above this index will be passed through
        self.fn = fn
        self.pargs = pargs
        self.pkwargs = pkwargs
        self.max_gindex = max(
            (x.index if isinstance(x, arg) else -1 for x in pargs),
            default=-1)

    def __call__(self, *gargs, **gkwargs):
        fargs = 
            [gargs[x.index] if isinstance(x, arg) else x for x in self.pargs]   
            list(gargs[self.max_gindex 1:])

        fkwargs = dict(self.pkwargs)
        fkwargs.update(gkwargs)    # Overwrite keys
        return self.fn(*fargs, *fkwargs)
  

Затем попробуйте это, как:

 def myfn(a,b,c):
print(a,b,c)

bind(myfn, arg(1), 17, arg(0))(19,14)