Необходимы ли эти привязки атрибутов». ` при реализации «functools.partial»?

#python #scope #functools

Вопрос:

docs.python.org говорит, что functools.partial это примерно эквивалентно:

 def partial(func, /, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = {**keywords, **fkeywords}
        return func(*args, *fargs, **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc
 

(Примечание: / используется для обозначения func только позиционного аргумента partial . См. [1].)

Если я правильно понимаю, когда на переменную ссылаются во вложенной функции, например newfunc , Python сначала ищет определение переменной во вложенной функции. Если определение там не найдено, Python затем будет искать определение во включающей области (т. е. во внешней функции; partial в данном случае). Итак, действительно ли необходимы явные .func привязки , .args , и .keywords атрибутов к newfunc вышеперечисленным? Я попробовал пример без указанных привязок, и он partial сработал просто отлично? Есть ли случай, когда они могут понадобиться?

 def partial(func, /, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = {**keywords, **fkeywords}
        return func(*args, *fargs, **newkeywords)
    # newfunc.func = func
    # newfunc.args = args
    # newfunc.keywords = keywords
    return newfunc

# p0, p1, p2 are positional arguments
# kw0, kw1, kw2 are keyword-only arguments
def foo3(p0, p1, p2, *, kw0, kw1, kw2):
    return 100*p2   10*p1   1*p0, kw0   kw1   kw3

foo2 = partial(foo3, 1, kw0=1 1j)

print(foo2(2,3,kw1=2 2j, kw2=3 3j)) # (321, (6 6j))
 

. Необходимы ли привязки, если keywords fkeywords словари или содержат элемент с func args ключевым словом , или keywords в качестве ключевого слова? Каков был бы пример, когда это необходимо? Насколько я могу судить, это не причина, потому что работает следующее:

 def partial(func, /, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = {**keywords, **fkeywords}
        return func(*args, *fargs, **newkeywords)
    # newfunc.func = func
    # newfunc.args = args
    # newfunc.keywords = keywords
    return newfunc

# p0, p1, p2 are positional arguments
# kw0, kw1, kw2 are keyword-only arguments
def foo3(p0, p1, p2, kw0, kw1, kw2, **kwargs):
    return 100*p2   10*p1   1*p0, kw0   kw1   kw2   sum(kwargs.values())

foo2 = partial(foo3, 1, kw0=1 1J, func=10, args=10j, keywords=100 100j)

print(foo2(2,3,kw1=2 2J, kw2=3 3J, func=20, args=20j, keywords=200 200j)) # (321, (226 6j))
 

[1] https://docs.python.org/3/whatsnew/3.8.html#positional-only-parameters

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

1. Обратите внимание, что newfunc.func это атрибут объекта функции ; это не то же самое, что переменная func внутри newfunc . Однако я должен признаться, что не знаю ответа на очевидный последующий вопрос об этом…

2. Попался. Последующее действие: допустим, мы установили атрибут для объекта функции (т. Е. newfunc.func = func ). Затем, когда newfunc делается ссылка на func , какое значение func использует Python? Использует ли он newfunc.func или func как передано в partial функцию (в данном случае они оба одинаковы , потому что мы это сделали newfunc.func = func , но что, если бы мы этого не сделали?)?

3. Функции @joseville-это просто объекты, вы можете устанавливать произвольные атрибуты для объектов. В этом нет ничего особенно экзотического или особенного, например class Foo: pass ; затем foo = Foo(); foo.a = 42; foo.b = [1,2,3] и т. Д. И т. Д. Это ничего не влияет на переменные, на которые ссылаются в коде функций

4. @juanpa.arrivillaga Теперь я понимаю, как устанавливать атрибуты для функций, спасибо. Кроме того, я попытался установить произвольный атрибут для dict экземпляра, который, по моему мнению, является объектом, но это не сработало: a = {}; a.attr = 1 вызвано AttributeError: 'dict' object has no attribute 'attr' .

5. @joseville да, некоторые объекты предотвращают произвольную настройку атрибутов. Некоторые встроенные объекты, или объекты, написанные как расширения c, или определяемые пользователем классы с __slots__

Ответ №1:

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

Следующее ( Python 3.9.5 )

 class partial:
    """New function with partial application of the given arguments
    and keywords.
    """

    __slots__ = "func", "args", "keywords", "__dict__", "__weakref__"

    def __new__(cls, func, /, *args, **keywords):
        if not callable(func):
            raise TypeError("the first argument must be callable")

        if hasattr(func, "func"):
            args = func.args   args
            keywords = {**func.keywords, **keywords}
            func = func.func

        self = super(partial, cls).__new__(cls)

        self.func = func
        self.args = args
        self.keywords = keywords
        return self

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

 

Когда вы заменяете self на newfunc , они почти такие же.

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

1. Спасибо. Я понимаю большую часть этого кода, за исключением значения __slots__ , почему __new__ используется вместо __init__ и почему super вызывается, но , похоже, причина хранения атрибутов в self / newfunc заключается в том, что вы можете передать partial экземпляр как func в новый partial , не теряя при этом первые partial func , args и keywords . Например, если вы сделаете: partial(partial(foo, 1, b=3), 2, c=4) , то первые частичные args и keywords (1 и b=3) соответственно) будут объединены со вторыми частичными args и keywords (2 и c=4 соответственно).

2. @joseville Ввод обработки __new__ в __init__ имеет тот же эффект. Что касается обработки внутри __new__ или __init__ и генерации self с использованием super , может быть, это ближе к ООП? hasattr(func, "func") Можно сказать, что эта логика в обрабатывает входящий экземпляр partail для объединения параметров args и kwargs . Вы можете думать self как newfunc , поэтому newfunc() можете быть истолкованы как self() .

3. __slots__ __call__

4. Атрибуты func , args , и kwargs , сохраненные в self , будут использоваться при вызове экземпляра, а именно self() .