Author bup
Recipients Antony.Lee, bup, ethan.furman, josh.r
Date 2018-09-21.21:26:46
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1537565206.14.0.956365154283.issue34750@psf.upfronthosting.co.za>
In-reply-to
Content
It's working as intended. locals() and vars() simply returns the current frame's f_locals. In functions, modifying this usually accomplishes nothing useful because the code object has OPTIMIZED and NEWLOCALS flags set, meaning local variables are looked up or set via the LOAD_FAST and STORE_FAST opcodes (respectively) which doesn't even look in the f_locals mapping. In this case, vars() and locals() will build a new dict[*] and fill it with the frame's fastlocals and unpack any closure cells into it.

The code object used for class bodies however is special and actually *does* use the mapping in f_locals, which for for classes ultimately built by builtins.__build_class__ (aka classes built with a `class` statement) will be whatever the metaclass's __prepare__ returns, which in the case of enums is an enum._EnumDict instance. 

So that's why metaclasses are so powerful. You don't even need to use a dictionary subclass as the class namespace, since the STORE_NAME opcode will use PyObject_SetItem; however type.__new__ will make you cast it to a dict, and even the dict that is wrapped by a MappingProxy after the class has been created will be a copy anyway. 

So anyway, there's nothing actually wrong with the current behavior. dict.update never calls `self.__getitem__`, and since `_EnumDict.__setitem__` is where all of the magic happens regular dict.update won't trigger it. I agree though that adding an update method would be nice though and can be done in just a few lines of code.

    import enum
    import sys

    def local_update(it=(), **kws):
        self = sys._getframe(1).f_locals
        d = dict(it, **kws)
        for k, v in d.items():
            self[k] = v
            
    class MyEnum(enum.Enum):
        local_update(a=1, b=2)
        
    assert MyEnum.a.value == 1

[*] it doesn't actually build a new one every time but the only practical purpose with the NEWLOCALS code.co_code flag set is for introspection with vars(), locals(), and sys._getframe
History
Date User Action Args
2018-09-21 21:26:46bupsetrecipients: + bup, ethan.furman, Antony.Lee, josh.r
2018-09-21 21:26:46bupsetmessageid: <1537565206.14.0.956365154283.issue34750@psf.upfronthosting.co.za>
2018-09-21 21:26:46buplinkissue34750 messages
2018-09-21 21:26:46bupcreate