Skip to content

PEP 726: Support defining __setattr__() and __delattr__() in a module #106016

Closed

Description

@vstinner

The Python stdlib sys module implements multiple API with simple attributes like sys.path, sys.modules, etc. Converting these APIs to getter and setter functions would be an annoying and expensive migration, a lot of code expect these attributes and access them directly.

It's not possible to execute code before or after a module attribute is set to validate inputs or to update internal states.

Example:

$ python3.12
>>> import sys
>>> sys.modules = 123

>>> import asyncio
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<frozen importlib._bootstrap>", line 1260, in _find_and_load
AttributeError: 'int' object has no attribute 'get'

If sys.modules is set to an invalid type, import stops working and produces an error which makes no sense.

Another problem is that the internals of Python have a copy of sys attributes and so don't respect sys changes. Recently, I discovered that PyImport_AddModule() uses a "copy" of sys.modules (a reference to the dict). Overriding sys.modules has no effect: it still changes the original sys.modules dict. See #105998 (comment) for technical details.

Supporting __setattr__() would allow to update the internal reference to sys.modules (PyInterpreterState.imports.modules).

Adding __delattr__() would help prevent to delete critical sys attributes which makes the code more complicated. For example, code using sys.stderr has to check if the attribute exists and if it's not None. Currently, it's possible to remove any sys attribute, including functions:

$ python3.12
>>> import sys
>>> del sys.excepthook
>>> 1+
sys.excepthook is missing
  File "<stdin>", line 1
    1+
      ^
SyntaxError: invalid syntax

Notice the sys.excepthook is missing error mesage.

In Python 3.7, support for __getattr__() and dir() was added to modules by PEP 562 – Module __getattr__ and __dir__.


This change alone doesn't allow to validate changes of mutable objects like sys.path list. For example, it was proposed to deprecate or disallow adding bytes strings to sys.path. For this, sys.path should use a different type (such as a list subclass): it's not directly related to this issue.

Linked PRs

Activity

  1. vstinner commented on Jun 23, 2023

    @vstinner
    MemberAuthor

    Currently, it's possible to implement this feature by overriding the module in sys.modules with a different class which implements __setattr__(). Proof-of-concept mymodule.py:

    # regular module code
    attr = 123
    
    def func():
        print("called func()")
    
    # override the module in sys.modules to implement __setattr__()
    class FakeModule:
        def __init__(self, ns):
            vars(self).update(ns)
    
        def __setattr__(self, name, value):
            print("setting %s=%s" % (name, value))
            object.__setattr__(self, name, value)
    
    import sys
    sys.modules[__name__] = FakeModule(globals())

    Example:

    $ python3
    >>> import mymodule
    >>> mymodule.x = 1
    setting x=1
    

    But this approach doesn't work for C extensions (which is what I care about here) ;-)

    I found the code (that I updated for Python 3) in an old comp.lang.python discussion (2010) about overriding __setattr__() of a module: https://groups.google.com/g/comp.lang.python/c/2H53WSCXhoM

    This feature was also requested recently: https://discuss.python.org/t/extend-pep-562-with-setattr-for-modules/25506

  2. erlend-aasland commented on Jun 23, 2023

    @erlend-aasland
    Contributor

    Feature or bug? ;)

  3. added
    type-featureA feature request or enhancement
    and removed
    type-bugAn unexpected behavior, bug, or error
    on Jun 23, 2023
  4. vstinner commented on Jun 23, 2023

    @vstinner
    MemberAuthor

    Feature or bug? ;)

    Feature request :-)

  5. erlend-aasland commented on Jun 23, 2023

    @erlend-aasland
    Contributor

    Since, module __getattr__ and __dict__ required a (small) PEP (PEP-562), I assume this feature should also get a (small) PEP :)

  6. skirpichev commented on Jun 23, 2023

    @skirpichev
    Member

    I assume this feature should also get a (small) PEP :)

    I did a draft PEP, with __setattr__ support only. Despite for some support from core devs, there are no sponsors yet.

    Let me know if it does make sense to extend this effort.

  7. added a commit that references this issue on Aug 22, 2023
  8. added 2 commits that reference this issue on Aug 22, 2023
  9. added a commit that references this issue on Aug 22, 2023
  10. added a commit that references this issue on Aug 22, 2023
  11. added a commit that references this issue on Aug 22, 2023
  12. added a commit that references this issue on Aug 22, 2023
  13. added a commit that references this issue on Aug 22, 2023
  14. changed the title [-]Support defining __setattr__() and __delattr__() in a module[/-] [+]PEP 726: Support defining __setattr__() and __delattr__() in a module[/+] on Nov 8, 2023
  15. joaoe commented on Nov 11, 2023

    @joaoe

    Hi.

    I see there is a PEP for this
    https://peps.python.org/pep-0726/

    There is a use case I've encountered recently which would require a proper efficient support for setattr and that is the warnings module.
    As you well know, it is not thread safe, and that module has a lot of monkeypatch going one replacing the showwarning function. Exactly what is described in that PEP.

    Fixing that would require changing how catch_warnings() and filters work, so that no monkeypatching happens. However, that is very non-backwards compatible if 3rd parties are trying to do the same. A way to fix that would be to detect attributes being set in the module and issue an deprecation warning. Later fail with an error. And that requires a module.setattr. However, adding a class for such an important module could perhaps represent other performance or compatibility problems.

    #91505

  16. skirpichev commented on Nov 12, 2023

    @skirpichev
    Member

    @joaoe, I think this scenario at least partially mentioned in the discussion thread: https://discuss.python.org/t/32640/19. And also in the PEP (the last paragraph of the "Discussion" section), let me know if you feel this should be rephrased somehow. I'm going to submit a SC issue to ask for final decision.

    Practical benefits for package or module maintainers are very important (see https://discuss.python.org/t/32640/17) to the fate of PEP. So, I'm suggesting to share your thoughts in the discussion thread.

  17. vstinner commented on Dec 20, 2023

    @vstinner
    MemberAuthor

    The decision is now in the hands of the Steering Council: python/steering-council#216 For now, I prefer to close this issue. Another issue can be opened once the PEP will be approved.

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

    interpreter-core(Objects, Python, Grammar, and Parser dirs)type-featureA feature request or enhancement

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions