[Python-Dev] Add a frozendict builtin type

Nick Coghlan ncoghlan at gmail.com
Tue Feb 28 02:00:08 CET 2012


On Tue, Feb 28, 2012 at 9:34 AM, Victor Stinner
<victor.stinner at gmail.com> wrote:
>>> The blacklist implementation has a major issue: it is still possible
>>> to call write methods of the dict class (e.g. dict.set(my_frozendict,
>>> key, value)).
>>
>> It is also possible to use ctypes and violate even more invariants.
>> For most purposes, this falls under "consenting adults".
>
> My primary usage of frozendict would be pysandbox, a security module.
> Attackers are not consenting adults :-)
>
> Read-only dict would also help optimization, in the CPython peephole
> or the PyPy JIT.

I'm pretty sure the PyPy jit can already pick up and optimise cases
where a dict goes "read-only" (i.e. stops being modified).

I think you need to elaborate on your use cases further, and explain
what *additional* changes would be needed, such as allowing frozendict
instances as __dict__ attributes in order to create truly immutable
objects in pure Python code.

In fact, that may be a better way to pitch the entire PEP. In current
Python, you *can't* create a truly immutable object without dropping
down to a C extension:

>>> from decimal import Decimal
>>> x = Decimal(1)
>>> x
Decimal('1')
>>> hash(x)
1
>>> x._exp = 10
>>> x
Decimal('1E+10')
>>> hash(x)
10000000000

Contrast that with the behaviour of a float instance:

>>> 1.0.imag = 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: attribute 'imag' of 'float' objects is not writable

Yes, it's arguably covered by the "consenting adults" rule, but
really, Decimal instances should be just as immutable as int and float
instances. The only reason they aren't is that it's hard enough to set
it up in Python code that the Decimal implementation settles for "near
enough is good enough" and just uses __slots__ to prevent addition of
new attributes, but doesn't introduce the overhead of custom
__setattr__ and __delattr__ implementations to actively *prevent*
modifications.

We don't even need a new container type, we really just need an easy
way to tell the __setattr__ and __delattr__ descriptors for
"__slots__" that the instance initialisation is complete and further
modifications should be disallowed.

For example, if Decimal.__new__ could call "self.__lock_slots__()" at
the end to set a flag on the instance object, then the slot
descriptors could read that new flag and trigger an error:

>>> x._exp = 10
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: attribute '_exp' of 'Decimal' objects is not writable

To be clear, all of this is currently *possible* if you use custom
descriptors (such as a property() implementation where setattr and
delattr look for such a flag) or override __setattr__/__delattr__.
However, for a micro-optimised type like Decimal, that's a hard choice
to be asked to make (and the current implementation came down on the
side of speed over enforcing correctness). Given that using __slots__
in the first place is, in and of itself, a micro-optimisation, I
suspect Decimal is far from the only "immutable" type implemented in
pure Python that finds itself having to make that trade-off. (An extra
boolean check in C is a *good* trade-off of speed for correctness.
Python level descriptor implementations or attribute access overrides,
on the other hand... not so much).

Cheers,
Nick.

-- 
Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia


More information about the Python-Dev mailing list