[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