#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()
.4. Атрибуты
func
,args
, иkwargs
, сохраненные вself
, будут использоваться при вызове экземпляра, а именноself()
.