Изменение глобальных переменных в доктестах

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

Основываясь на этом описании, я мог бы подумать, что следующие тесты пройдут:

X = 1


def f():
    """Function F.

    >>> X
    1
    >>> f()
    2
    >>> X
    2
    """
    global X
    X = 2
    return X


def g():
    """Function G.

    >>> g()
    1
    >>> X
    1
    """
    return X

Но вместо этого проходят следующие тесты!

X = 1


def f():
    """Function F.

    >>> X
    1
    >>> f()
    2
    >>> X
    1
    """
    global X
    X = 2
    return X


def g():
    """Function G.

    >>> g()
    2
    >>> X
    1
    """
    return X

Кажется, что глобальные переменные используются в тестах в строках документации? Но только внутри вызовов функций?

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


person selassid    schedule 30.08.2016    source источник


Ответы (1)


Не совсем. Хотя это правда, что глобальные объекты копируются неглубоко, на самом деле вы видите область видимости глобальных объектов (с использованием ключевого слова global) и то, как это фактически работает на уровне модуля в Python. Вы можете наблюдать это, вставив pdb.set_trace() внутри функции f сразу после присвоение (X = 2).

$ python -m doctest foo.py
> /tmp/foo.py(18)f()
-> return X
(Pdb) bt
  /usr/lib/python2.7/runpy.py(162)_run_module_as_main()
-> "__main__", fname, loader, pkg_name)
  /usr/lib/python2.7/runpy.py(72)_run_code()
-> exec code in run_globals
  /usr/lib/python2.7/doctest.py(2817)<module>()
-> sys.exit(_test())
  /usr/lib/python2.7/doctest.py(2808)_test()
-> failures, _ = testmod(m)
  /usr/lib/python2.7/doctest.py(1911)testmod()
-> runner.run(test)
  /usr/lib/python2.7/doctest.py(1454)run()
-> return self.__run(test, compileflags, out)
  /usr/lib/python2.7/doctest.py(1315)__run()
-> compileflags, 1) in test.globs
  <doctest foo.f[1]>(1)<module>()
-> f()
> /tmp/foo.py(18)f()
-> return X
(Pdb) pp X
2

Да, значение действительно 2 в пределах f, но давайте взглянем на его глобальные переменные. Давайте посмотрим, как они сравниваются в текущем кадре и кадре выше.

(Pdb) id(globals())
140653053803048  # remember this number, and we go up a frame
(Pdb) u
> <doctest foo.f[1]>(1)<module>()
-> f()
(Pdb) id(globals())
140653053878632  # the "shallow" clone
(Pdb) X
1
(Pdb) c

Ага, вы можете видеть, что на самом деле это НЕ одно и то же, и что X действительно 1 и не был изменен, потому что глобальные объекты находятся внутри модуля <doctest doc.f>, созданного doctest по этой причине. Давай продолжим.

(Pdb) id(globals())
140653053803048  # hey look, is the SAME number we remember
(Pdb) u
> <doctest foo.g[0]>(1)<module>()
-> g()
(Pdb) id(globals())
140653053872960  # note how this is a different shallow clone

Итак, что вы на самом деле видели, так это то, что глобальные переменные в doctest не совпадают с глобальными переменными в вашем источнике (следовательно, g вернет 2, потому что X действительно был изменен в модуле здесь f, но не в мелкой копии модуля doctest scope), хотя изначально он был скопирован из модуля, но изменения не отражаются обратно в базовый модуль, поскольку именно так работает ключевое слово global - на уровне модуля, а не между модулями.

person metatoaster    schedule 31.08.2016