[Python-ideas] Override dict.__new__ to raise if cls is not dict; do the same for str, list, etc.

Chris Angelico rosuav at gmail.com
Fri Apr 22 02:06:39 EDT 2016


On Fri, Apr 22, 2016 at 3:17 PM, Ben Finney <ben+python at benfinney.id.au> wrote:
> Random832 <random832 at fastmail.com> writes:
>
>> On Thu, Apr 21, 2016, at 23:49, Steven D'Aprano wrote:
>> > Where does attribute lookup come into this?
>>
>> Because looking up an attribute implies getting an item from the
>> object or class's __dict__ with a string key of the name of the
>> attribute (see below for the documented basis for this assumption).
>
> No, that's not what it implies. The ‘__dict__’ of an object is an
> implementation detail, and is not necessarily used for attribute lookup.
>
> As the documentation says:
>
>     A class has a namespace implemented by a dictionary object. Class
>     attribute references are translated to lookups in this dictionary,
>     e.g., C.x is translated to C.__dict__["x"] (although there are a
>     number of hooks which allow for other means of locating attributes).
>
>     <URL:https://docs.python.org/3.5/reference/datamodel.html>

Okay. That definitely implies that the class itself has a real dict,
though. And it should be possible, using a metaclass, to manipulate
that, right?

>>> class AutoCreateDict(dict):
...     def __missing__(self, key):
...         return "<%r>" % key
...     def __repr__(self):
...         return "AutoCreateDict" + super().__repr__()
...
>>> class DemoMeta(type):
...     def __new__(*a):
...         print("new:", a)
...         return type.__new__(*a)
...     @classmethod
...     def __prepare__(*a):
...         print("prepare:", a)
...         return AutoCreateDict()
...
>>> class Demo(metaclass=DemoMeta): pass
...
prepare: (<class '__main__.DemoMeta'>, 'Demo', ())
new: (<class '__main__.DemoMeta'>, 'Demo', (),
AutoCreateDict{'__qualname__': 'Demo', '__module__': "<'__name__'>"})
>>> Demo.asdf
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'Demo' has no attribute 'asdf'
>>> Demo().asdf
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Demo' object has no attribute 'asdf'
>>> Demo.__dict__
mappingproxy({'__dict__': <attribute '__dict__' of 'Demo' objects>,
'__doc__': None, '__weakref__': <attribute '__weakref__' of 'Demo'
objects>, '__module__': "<'__name__'>"})
>>> Demo().__dict__
{}

Maybe I'm just misunderstanding how metaclasses should be written, but
this seems like it ought to work. And it's not making any use of the
AutoCreateDict - despite __prepare__ and __new__ clearly being called,
the latter with an AutoCreateDict instance. But by the time it gets
actually attached to the dictionary, we have a mappingproxy that
ignores __missing__. The docs say "are translated to", implying that
this will actually be equivalent.

Using a non-dict-subclass results in prompt rejection in the type() constructor:

>>> class DictLike:
...     def __getitem__(self, key):
...         print("DictLike get", key)
...         return "<%r>" % key
...     def __setitem__(self, key, value):
...         print("DictLike set", key, value)
...
>>> class Demo(metaclass=DemoMeta): pass
...
prepare: (<class '__main__.DemoMeta'>, 'Demo', ())
DictLike get __name__
DictLike set __module__ <'__name__'>
DictLike set __qualname__ Demo
new: (<class '__main__.DemoMeta'>, 'Demo', (), <__main__.DictLike
object at 0x7f2b41b96ac8>)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in __new__
TypeError: type() argument 3 must be dict, not DictLike

Is there any way to make use of this documented transformation?

ChrisA


More information about the Python-ideas mailing list