25

Note: I'm not talking about preventing the rebinding of a variable. I'm talking about preventing the modification of the memory that the variable refers to, and of any memory that can be reached from there by following the nested containers.

I have a large data structure, and I want to expose it to other modules, on a read-only basis. The only way to do that in Python is to deep-copy the particular pieces I'd like to expose - prohibitively expensive in my case.

I am sure this is a very common problem, and it seems like a constant reference would be the perfect solution. But I must be missing something. Perhaps constant references are hard to implement in Python. Perhaps they don't quite do what I think they do.

Any insights would be appreciated.


While the answers are helpful, I haven't seen a single reason why const would be either hard to implement or unworkable in Python. I guess "un-Pythonic" would also count as a valid reason, but is it really? Python does do scrambling of private instance variables (starting with __) to avoid accidental bugs, and const doesn't seem to be that different in spirit.


EDIT: I just offered a very modest bounty. I am looking for a bit more detail about why Python ended up without const. I suspect the reason is that it's really hard to implement to work perfectly; I would like to understand why it's so hard.

max
  • 49,282
  • 56
  • 208
  • 355

5 Answers5

13

It's the same as with private methods: as consenting adults authors of code should agree on an interface without need of force. Because really really enforcing the contract is hard, and doing it the half-assed way leads to hackish code in abundance.

Use get-only descriptors, and state clearly in your documentation that these data is meant to be read only. After all, a determined coder could probably find a way to use your code in different ways you thought of anyways.

knitti
  • 6,817
  • 31
  • 42
  • 4
    I'm not worried about hacks. I'm just helping myself avoid bugs. When I pass a certain container, I call various function that may (accidentally) modify its contents. I'd like to know about that. – max Nov 16 '10 at 08:22
  • Of course, if the language you're using has ways to actually enforce these things then those features should be used! – Billy ONeal Nov 16 '10 at 16:59
  • 1
    @max Why focus on accidental mutations as an important source of bugs? I can't recall a single time I've had a bug because I've accidentally modified something that was supposed to be constant. However, I do remember literally hundreds of times I've had to fix up code because the const annotations didn't agree. –  Nov 20 '10 at 11:15
  • 1
    @Paul Hankin: I have complicated nested data structures, and it's all too easy to forget to make a deep copy when I should have (I modify the copy to perform various calculations). – max Nov 20 '10 at 11:21
10

In PEP 351, Barry Warsaw proposed a protocol for "freezing" any mutable data structure, analogous to the way that frozenset makes an immutable set. Frozen data structures would be hashable and so capable being used as keys in dictionaries.

The proposal was discussed on python-dev, with Raymond Hettinger's criticism the most detailed.

It's not quite what you're after, but it's the closest I can find, and should give you some idea of the thinking of the Python developers on this subject.

Gareth Rees
  • 64,967
  • 9
  • 133
  • 163
  • Thank you, very helpful. While I was just limiting my interest to `const` to help avoid bugs, this thread covered a more "useful" / "broad" concept of `concept` as a hashable datatype. Still, very interesting read. – max Nov 21 '10 at 12:52
8

There are many design questions about any language, the answer to most of which is "just because". It's pretty clear that constants like this would go against the ideology of Python.


You can make a read-only class attribute, though, using descriptors. It's not trivial, but it's not very hard. The way it works is that you can make properties (things that look like attributes but call a method on access) using the property decorator; if you make a getter but not a setter property then you will get a read-only attribute. The reason for the metaclass programming is that since __init__ receives a fully-formed instance of the class, you actually can't set the attributes to what you want at this stage! Instead, you have to set them on creation of the class, which means you need a metaclass.

Code from this recipe:

# simple read only attributes with meta-class programming

# method factory for an attribute get method
def getmethod(attrname):
    def _getmethod(self):
        return self.__readonly__[attrname]

    return _getmethod

class metaClass(type):
    def __new__(cls,classname,bases,classdict):
        readonly = classdict.get('__readonly__',{})
        for name,default in readonly.items():
            classdict[name] = property(getmethod(name))

        return type.__new__(cls,classname,bases,classdict)

class ROClass(object):
    __metaclass__ = metaClass
    __readonly__ = {'a':1,'b':'text'}


if __name__ == '__main__':
    def test1():
        t = ROClass()
        print t.a
        print t.b

    def test2():
        t = ROClass()
        t.a = 2

    test1()
Katriel
  • 120,462
  • 19
  • 136
  • 170
  • I appreciate this information, it's interesting. But this doesn't address the problem I was referring to at all: suppose the attribute is a container. Even though the attribute is unchangeable, I can still modify all its elements. Also, can I modify this recipe to pass a constant dictionary (at the shallow level)? – max Nov 16 '10 at 08:15
  • 1
    Question about your sample code: What would prevent somebody from doing t.__readonly__['a'] = 2 and modifying data? – Ehsan Foroughi Nov 16 '10 at 09:45
  • 2
    @Max: if you want nested read-only structures then yes, you will have to implement this idea once per level until you hit immutables. You could also write your own `dict` class to handle read-only dictionaries, by modifying `getitem` and `setitem`. It won't be nice (or foolproof), because it's not the way one is meant to write Python. – Katriel Nov 16 '10 at 13:14
  • @Ehsan: nothing. You could hardwire `__readonly__` into the metaclass, but I'm pretty sure you could still get around it. The idea is that you should discourage writing the attribute, not prevent it completely -- that is probably impossible and at least very difficult. (For instance, I think you could probably replace the descriptor object itself.) – Katriel Nov 16 '10 at 13:16
  • "Just because" is very rarely the reason for any competent design decision. – Glenn Maynard Nov 22 '10 at 15:54
1

While one programmer writing code is a consenting adult, two programmers working on the same code seldom are consenting adults. More so if they do not value the beauty of the code but them deadlines or research funds.

For such adults there is some type safety, provided by Enthought's Traits.

You could look into Constant and ReadOnly traits.

fungusakafungus
  • 227
  • 1
  • 10
1

For some additional thoughts, there is a similar question posed about Java here:
Why is there no Constant feature in Java?

When asking why Python has decided against constant references, I think it's helpful to think of how they would be implemented in the language. Should Python have some sort of special declaration, const, to create variable references that can't be changed? Why not allow variables to be declared a float/int/whatever then...these would surely help prevent programming bugs as well. While we're at it, adding class and method modifiers like protected/private/public/etc. would help enforce compile-type checking against illegal uses of these classes. ...pretty soon, we've lost the beauty, simplicity, and elegance that is Python, and we're writing code in some sort of bastard child of C++/Java.

Python also currently passes everything by reference. This would be some sort of special pass-by-reference-but-flag-it-to-prevent-modification...a pretty special case (and as the Tao of Python indicates, just "un-Pythonic").

As mentioned before, without actually changing the language, this type of behaviour can be implemented via classes & descriptors. It may not prevent modification from a determined hacker, but we are consenting adults. Python didn't necessarily decide against providing this as an included module ("batteries included") - there was just never enough demand for it.

Community
  • 1
  • 1
Gerrat
  • 28,863
  • 9
  • 73
  • 101
  • Python does not use pass-by-reference. It uses "references", but it passes those by value. Compare `def foo(x) { x = 10; } bar = 5; foo(bar); print bar` (Python with sinful braces because comments don't preverse newlines/indentation, pass-by-value) with `void foo(int &x) { x = 10; } /*...*/ int bar = 5; foo(bar); cout << bar;` (C++, pass-by-reference). –  Nov 21 '10 at 16:28