__setattr__ and__delattr__This PEP proposes supporting user-defined__setattr__and__delattr__ methods on modules to extend customizationof module attribute access beyondPEP 562.
There are several potential uses of a module__setattr__:
Proper support for read-only attributes would also require adding the__delattr__ function to prevent their deletion.
It would be convenient to directly support such customization, by recognizing__setattr__ and__delattr__ methods defined in a module that would actlike normalobject.__setattr__() andobject.__delattr__() methods, except that they will be definedon moduleinstances. Together with existing__getattr__ and__dir__methods this will streamline all variants of customizing module attribute access.
For example
# mplib.pyCONSTANT=3.14prec=53dps=15defdps_to_prec(n):"""Return the number of bits required to represent n decimals accurately."""returnmax(1,int(round((int(n)+1)*3.3219280948873626)))defprec_to_dps(n):"""Return the number of accurate decimals that can be represented with n bits."""returnmax(1,int(round(int(n)/3.3219280948873626)-1))defvalidate(n):n=int(n)ifn<=0:raiseValueError('Positive integer expected')returnndef__setattr__(name,value):ifname=='CONSTANT':raiseAttributeError('Read-only attribute!')ifname=='dps':value=validate(value)globals()['dps']=valueglobals()['prec']=dps_to_prec(value)returnifname=='prec':value=validate(value)globals()['prec']=valueglobals()['dps']=prec_to_dps(value)returnglobals()[name]=valuedef__delattr__(name):ifnamein('CONSTANT','dps','prec'):raiseAttributeError('Read-only attribute!')delglobals()[name]
>>>importmplib>>>mplib.foo='spam'>>>mplib.CONSTANT=42Traceback (most recent call last):...AttributeError:Read-only attribute!>>>delmplib.foo>>>delmplib.CONSTANTTraceback (most recent call last):...AttributeError:Read-only attribute!>>>mplib.prec53>>>mplib.dps15>>>mplib.dps=5>>>mplib.prec20>>>mplib.dps=0Traceback (most recent call last):...ValueError:Positive integer expected
The current workaround is assigning the__class__ of a module object to acustom subclass oftypes.ModuleType (see[1]).
For example, to prevent modification or deletion of an attribute we could use:
# mod.pyimportsysfromtypesimportModuleTypeCONSTANT=3.14classImmutableModule(ModuleType):def__setattr__(name,value):raiseAttributeError('Read-only attribute!')def__delattr__(name):raiseAttributeError('Read-only attribute!')sys.modules[__name__].__class__=ImmutableModule
But this variant is slower (~2x) than the proposed solution. Moreimportantly, it also brings a noticeable speed regression (~2-3x) forattributeaccess.
The__setattr__ function at the module level should accept twoarguments, the name of an attribute and the value to be assigned,and returnNone or raise anAttributeError.
def__setattr__(name:str,value:typing.Any,/)->None:...
The__delattr__ function should accept one argument,the name of an attribute, and returnNone or raise anAttributeError:
def__delattr__(name:str,/)->None:...
The__setattr__ and__delattr__ functions are looked up in themodule__dict__. If present, the appropriate function is called tocustomize setting the attribute or its deletion, else the normalmechanism (storing/deleting the value in the module dictionary) will work.
Defining module__setattr__ or__delattr__ only affects lookups madeusing the attribute access syntax — directly accessing the module globals(whether byglobals() within the module, or via a reference to the module’sglobals dictionary) is unaffected. For example:
>>>importmod>>>mod.__dict__['foo']='spam'# bypasses __setattr__, defined in mod.py
or
# mod.pydef__setattr__(name,value):...foo='spam'# bypasses __setattr__globals()['bar']='spam'# here toodeff():globalxx=123f()# and here
To use a module global and trigger__setattr__ (or__delattr__),one can access it viasys.modules[__name__] within the module’s code:
# mod.pysys.modules[__name__].foo='spam'# bypasses __setattr__def__setattr__(name,value):...sys.modules[__name__].bar='spam'# triggers __setattr__
This limitation is intentional (just as for thePEP 562), because theinterpreter highly optimizes access to module globals and disabling all thatand going through special methods written in Python would slow down the codeunacceptably.
The “Customizing module attribute access”[1] section of the documentationwill be expanded to include new functions.
The reference implementation for this PEP can be found inCPython PR #108261.
This PEP may break code that uses module level (global) names__setattr__ and__delattr__, but the language referenceexplicitly reservesall undocumented dunder names, and allows“breakage without warning”[2].
The performance implications of this PEP are small, since additionaldictionary lookup is much cheaper than storing/deleting the value inthe dictionary. Also it is hard to imagine a module that expects theuser to set (and/or delete) attributes enough times to be aperformance concern. On another hand, proposed mechanism allows tooverride setting/deleting of attributes without affecting speed ofattribute access, which is much more likely scenario to get aperformance penalty.
As pointed out by Victor Stinner, the proposed API could be useful already inthe stdlib, for example to ensure thatsys.modules type is always adict:
>>>importsys>>>sys.modules=123>>>importasyncioTraceback (most recent call last): File"<stdin>", line1, in<module> File"<frozen importlib._bootstrap>", line1260, in_find_and_loadAttributeError:'int' object has no attribute 'get'
or to prevent deletion of criticalsys attributes, which makes thecode more complicated. For example, code usingsys.stderr has tocheck if the attribute exists and if it’s notNone. Currently, it’spossible to remove anysys attribute, including functions:
>>>importsys>>>delsys.excepthook>>>1+# notice the next linesys.excepthook is missing File "<stdin>", line 1 1+ ^SyntaxError: invalid syntax
Seerelated issue forother details.
Other stdlib modules also come with attributes which can be overridden (as afeature) and some input validation here could be helpful. Examples:threading.excepthook,warnings.showwarning,io.DEFAULT_BUFFER_SIZE oros.SEEK_SET.
Also a typical use case for customizing module attribute access is managingdeprecation warnings. But thePEP 562 accomplishes this scenario onlypartially: e.g. it’s impossible to issue a warning during an attempt tochange a renamed attribute.
This document is placed in the public domain or under theCC0-1.0-Universal license, whichever is more permissive.
Source:https://github.com/python/peps/blob/main/peps/pep-0726.rst
Last modified:2025-08-08 15:00:59 GMT