Почему эти два подхода дают разные результаты? - PullRequest
0 голосов
/ 16 ноября 2018
def once(fcn):
        func = [fcn]
        def inner(*args):
            return func.pop()(*args) if len(func) else None
        return inner


def add(a,b):
    return a+b


oneAddition = once(add)
print(oneAddition(2,2)) # 4
print(oneAddition(2,2)) # None
print(oneAddition(12,200)) # None

print(once(add)(2,2)) # 4
print(once(add)(2,2)) # Should return None, returns 4
print(once(add)(12,200)) # Should return None, returns 212

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

Что меня действительно заинтересовало, так это то, что oneAddition = Once (добавить) -> OneAddition (2,2) и Once (Add) (x, y) ведут себя по-разному.

Во втором методе кажется, что внешняя функция также выполняется.В первом методе внешняя функция выполняется только при создании (во многом как декораторы).

Может кто-нибудь объяснить мне, почему это так?Большое спасибо.

PS Я знаю, что использование нелокальных переменных было бы гораздо более подходящим решением, я просто включил подход function-in-a-list , потому что он выглядит довольно круто (найденоэто онлайн).

1 Ответ

0 голосов
/ 16 ноября 2018

Каждый вызов once(add) создает новое закрытие inner с новой ссылкой func и возвращает его. Таким образом, каждый из ваших последних трех print работает с полностью независимыми списками func, отсюда и вывод.

Простой способ убедиться в этом - взглянуть на байт-код:

>>> dis(once)
  2           0 LOAD_FAST                0 (fcn)
              2 BUILD_LIST               1
              4 STORE_DEREF              0 (func)

  3           6 LOAD_CLOSURE             0 (func)
              8 BUILD_TUPLE              1
             10 LOAD_CONST               1 (<code object inner at 0x10c64c300, file "<stdin>", line 3>)
             12 LOAD_CONST               2 ('once.<locals>.inner')
             14 MAKE_FUNCTION            8
             16 STORE_FAST               1 (inner)

  5          18 LOAD_FAST                1 (inner)
             20 RETURN_VALUE

Обратите внимание, что once создает новый func список при каждом вызове (BUILD_LIST), который, в свою очередь, используется возвращаемым закрытием (MAKE_FUNCTION).

Еще один способ убедиться в этом - напечатать id(func) внутри inner():

>>> def once(fcn):
...     func = [fcn]
...     def inner(*args):
...         print(id(func))  # <-- here
...         return func.pop()(*args) if len(func) else None
...     return inner
...
>>> a1 = once(add)  # assign these so they don't get GC'd
>>> a2 = once(add)
>>> a1(2,2)
4503952392
4
>>> a2(2,2)
4503934024
4

Обратите внимание, что идентификаторы разные.

...