Skip to content

catch_warnings context manager causes all warnings to be printed every time, even after exiting #73858

Description

@gerritholl
BPO 29672
Nosy @pitrou, @gerritholl, @embray, @serhiy-storchaka, @segevfiner
PRs
  • [WIP] gh-73858: Save and restore module warning registries in catch_warnings #8232
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = None
    created_at = <Date 2017-02-27.22:29:56.490>
    labels = ['type-bug', 'library']
    title = '`catch_warnings` context manager causes all warnings to be printed every time, even after exiting'
    updated_at = <Date 2021-04-02.21:55:37.433>
    user = 'https://github.com/gerritholl'

    bugs.python.org fields:

    activity = <Date 2021-04-02.21:55:37.433>
    actor = 'taldcroft'
    assignee = 'none'
    closed = False
    closed_date = None
    closer = None
    components = ['Library (Lib)']
    creation = <Date 2017-02-27.22:29:56.490>
    creator = 'Gerrit.Holl'
    dependencies = []
    files = []
    hgrepos = []
    issue_num = 29672
    keywords = ['patch']
    message_count = 10.0
    messages = ['288677', '288678', '288680', '288709', '298588', '298822', '320662', '321380', '321393', '390103']
    nosy_count = 6.0
    nosy_names = ['pitrou', 'Gerrit.Holl', 'erik.bray', 'serhiy.storchaka', 'Segev Finer', 'taldcroft']
    pr_nums = ['8232']
    priority = 'normal'
    resolution = None
    stage = 'patch review'
    status = 'open'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue29672'
    versions = []

    Activity

    1. gerritholl commented on Feb 27, 2017

      gerrithollmannequin
      MannequinAuthor

      Entering the catch_warnings context manager is causing warnings to be printed over and over again, rather than just once as it should. Without such a context manager, the behaviour is as expected:

          $ cat ./mwe.py 
          #!/usr/bin/env python3.5
          
          import warnings
          
          for i in range(3):
              try:
                  print(i, __warningregistry__)
              except NameError:
                  print(i, "first warning")
              warnings.warn("I don't like this.", UserWarning)
              print(i, __warningregistry__)
          #    with warnings.catch_warnings():
          #        pass
              print(i, __warningregistry__)
              warnings.warn("I don't like this.", UserWarning)
          $ ./mwe.py 
          0 first warning
          ./mwe.py:10: UserWarning: I don't like this.
            warnings.warn("I don't like this.", UserWarning)
          0 {'version': 0, ("I don't like this.", <class 'UserWarning'>, 10): True}
          0 {'version': 0, ("I don't like this.", <class 'UserWarning'>, 10): True}
          ./mwe.py:15: UserWarning: I don't like this.
            warnings.warn("I don't like this.", UserWarning)
          1 {'version': 0, ("I don't like this.", <class 'UserWarning'>, 15): True, ("I don't like this.", <class 'UserWarning'>, 10): True}
          1 {'version': 0, ("I don't like this.", <class 'UserWarning'>, 15): True, ("I don't like this.", <class 'UserWarning'>, 10): True}
          1 {'version': 0, ("I don't like this.", <class 'UserWarning'>, 15): True, ("I don't like this.", <class 'UserWarning'>, 10): True}
          2 {'version': 0, ("I don't like this.", <class 'UserWarning'>, 15): True, ("I don't like this.", <class 'UserWarning'>, 10): True}
          2 {'version': 0, ("I don't like this.", <class 'UserWarning'>, 15): True, ("I don't like this.", <class 'UserWarning'>, 10): True}
          2 {'version': 0, ("I don't like this.", <class 'UserWarning'>, 15): True, ("I don't like this.", <class 'UserWarning'>, 10): True}

      Uncommenting the context manager causes the warning to be printed every time:

          $ ./mwe.py 
          0 first warning
          ./mwe.py:10: UserWarning: I don't like this.
            warnings.warn("I don't like this.", UserWarning)
          0 {'version': 0, ("I don't like this.", <class 'UserWarning'>, 10): True}
          0 {'version': 0, ("I don't like this.", <class 'UserWarning'>, 10): True}
          ./mwe.py:15: UserWarning: I don't like this.
            warnings.warn("I don't like this.", UserWarning)
          1 {'version': 2, ("I don't like this.", <class 'UserWarning'>, 15): True}
          ./mwe.py:10: UserWarning: I don't like this.
            warnings.warn("I don't like this.", UserWarning)
          1 {'version': 2, ("I don't like this.", <class 'UserWarning'>, 15): True, ("I don't like this.", <class 'UserWarning'>, 10): True}
          1 {'version': 2, ("I don't like this.", <class 'UserWarning'>, 15): True, ("I don't like this.", <class 'UserWarning'>, 10): True}
          ./mwe.py:15: UserWarning: I don't like this.
            warnings.warn("I don't like this.", UserWarning)
          2 {'version': 4, ("I don't like this.", <class 'UserWarning'>, 15): True}
          ./mwe.py:10: UserWarning: I don't like this.
            warnings.warn("I don't like this.", UserWarning)
          2 {'version': 4, ("I don't like this.", <class 'UserWarning'>, 15): True, ("I don't like this.", <class 'UserWarning'>, 10): True}
          2 {'version': 4, ("I don't like this.", <class 'UserWarning'>, 15): True, ("I don't like this.", <class 'UserWarning'>, 10): True}
          ./mwe.py:15: UserWarning: I don't like this.
            warnings.warn("I don't like this.", UserWarning)

      I think this is undesirable. There is code deep inside a module that I'm using that is using the context manager, and as a consequence, I get warnings printed every time that should be printed only once.

      See also on Stack Overflow: http://stackoverflow.com/q/42496579/974555

    2. gerritholl commented on Feb 27, 2017

      gerrithollmannequin
      MannequinAuthor

      I suppose this is a consequence of the change in:

      http://bugs.python.org/issue4180

      and although I can see why the warnings filter would be reset when entering the context manager, I don't think it is desirable to have side effects for modules that are entirely unrelated.

    3. gerritholl commented on Feb 27, 2017

      gerrithollmannequin
      MannequinAuthor

      To clarify, this behaviour crosses module boundaries. So even if some piece of code many layers deep buried in a module uses this context manager, it has global consequences.

    4. changed the title [-]`catch_warnings` context manager causes warnings to be reprinted[/-] [+]`catch_warnings` context manager should reset warning registry to previous state upon exiting, to prevent warnings from being reprinted[/+] on Feb 28, 2017
    5. gerritholl commented on Feb 28, 2017

      gerrithollmannequin
      MannequinAuthor

      To resolve this, the catch_warnings context manager upon exiting should not only reset filters, but also _filters_version, and perhaps an associated dictionary? Not sure if I'm understanding the code in warnings.c correctly, and whether, for this change, it would suffice to change warnings.py or whether warnings.c would need to be updated as well.

    6. gerritholl commented on Jul 18, 2017

      gerrithollmannequin
      MannequinAuthor

      I get bitten by this frequently, and find myself patching many libraries just to avoid them from calling .catch_warnings.

      Does anyone know whether to fix this it would suffice to edit warnings.py, or would I need to dig into _warnings.c as well?

    7. changed the title [-]`catch_warnings` context manager should reset warning registry to previous state upon exiting, to prevent warnings from being reprinted[/-] [+]`catch_warnings` context manager causes all warnings to be printed every time, even after exiting[/+] on Jul 18, 2017
    8. segevfiner commented on Jul 21, 2017

      segevfinermannequin
      Mannequin

      I think you will need to save and restore _filters_version and _onceregistry. Since _filters_version is a static variable in _warnings.c you will probably also have to modify it somehow to make it accessible to warnings.py.

    9. added
      stdlibStandard Library Python modules in the Lib/ directory
      type-bugAn unexpected behavior, bug, or error
      on Jul 21, 2017
    10. embray commented on Jun 28, 2018

      @embray
      Contributor

      I just encountered this as well. The way catch_warnings is implemented is a bit "dumb" in how it treats _filter_version (it calls _filters_mutated even if the active filters are not actually changed when entering catch_warnings).

      More significantly, _filter_version is not fine-grained enough. If some warning was already displayed, calling catch_warnings() should not later cause that same warning to be displayed again unless the filters were modified in such a way, during catch_warnings(), that that warning should be displayed (e.g. changed to 'always').

      I'm not really sure what to do about that though. Maybe the "filter version" should be per-warning? Currently the value assigned to each warning in __warningregistry__ is not used (it is just set to True), so maybe that could actually be used for this.

    11. segevfiner commented on Jul 10, 2018

      segevfinermannequin
      Mannequin

      Hmm, I originally missed the per module __warningregistry__... Need to read the code more closely...

      What should the behavior be? We can add a flag that will make all warning actions be "always" when catch_warnings is in effect. But that doesn't feel right with the way catch_warnings currently behaves or what is expected of it.

      Another idea would be to add a callback that is called whenever we set a module registry, allowing catch_warnings to backup the original registry of each module and restore them all on exit (Also the _onceregistry and _filters_version).

    12. embray commented on Jul 10, 2018

      @embray
      Contributor

      Yes, I think catch_warnings should back up and restore the relevant __warningregistry__. At which point it's not even clear to me what value there is in the _filter_version...

    13. taldcroft commented on Apr 2, 2021

      taldcroftmannequin
      Mannequin

      I encountered this issue today and want to +1 getting some attention on this.

      The disconnected nature of this issue makes it especially difficult to understand -- any package in the stack can change this hidden global variable _filters_version in the warnings module that then impacts the local behavior of warnings in the user script.

      The only way I was able to finally understand that an update to an obscure dependency was breaking our regression testing was by reading the warnings source code and then monkey patching it to print diagnostic information.

      Even a documentation update would be useful. This could explain not only catch_warnings(), but in general the unexpected feature that if any package anywhere in the stack sets a warning filter, then that globally resets whether a warning has been seen before (via the call to _filters_mutated()).

    14. 18 remaining items

    15. rtobar commented on Mar 21, 2025

      @rtobar
      Contributor

      Like many before me, I've found myself bitten by this today. As others mentioned, debugging this problem to finally land into this open issue takes quite some effort, including reading warnings.py and _warnings.c. Unfortunately I don't think there's much I can do: I have a fairly complex application where, deep within the main loop, catch_warnings is called at least twice even deeper down in my dependency chain (pandas in this particular case).

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Assignees

    No one assigned

      Labels

      stdlibStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or error

      Projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions