17

I have programmed in Python for a while, and just recently started using Ruby at work. The languages are very similar. However, I just came across a Ruby feature that I don't know how to replicate in Python. It's Ruby's freeze method.

irb(main):001:0> a = [1,2,3]
=> [1, 2, 3]
irb(main):002:0> a[1] = 'chicken'
=> "chicken"
irb(main):003:0> a.freeze
=> [1, "chicken", 3]
irb(main):004:0> a[1] = 'tuna'
TypeError: can't modify frozen array
        from (irb):4:in `[]='
        from (irb):4

Is there a way to imitate this in Python?

EDIT: I realized that I made it seem like this was only for lists; in Ruby, freeze is a method on Object so you can make any object immutable. I apologize for the confusion.

kerkeslager
  • 1,364
  • 4
  • 17
  • 34
  • What's the value of 'freezing' iterables? – Nick T Oct 15 '10 at 14:19
  • 1
    You can depend on them being the same in any case, on multiple threads, etc., and don't have to worry about the object being referenced in many places because it won't change out from under you. – kerkeslager Oct 15 '10 at 14:50

2 Answers2

15
>>> a = [1,2,3]
>>> a[1] = 'chicken'
>>> a
[1, 'chicken', 3]
>>> a = tuple(a)
>>> a[1] = 'tuna'
Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    a[1] = 'tuna'
TypeError: 'tuple' object does not support item assignment

Also, cf. set vs. frozenset, bytearray vs. bytes.

Numbers, strings are immutable themselves:

>>> a = 4
>>> id(a)
505408920
>>> a = 42        # different object
>>> id(a)
505409528
SilentGhost
  • 307,395
  • 66
  • 306
  • 293
  • Hmm. You can freeze any object in Ruby, it's defined on Object. I don't think this is the same. Might be sufficient though. – steenslag Oct 15 '10 at 14:17
  • @steenslag: python variables are prob different from the ruby variables, it's therefore never the same thing. Integers and strings are immutable in Python, freezing dicts is trivial, as any other object. – SilentGhost Oct 15 '10 at 14:21
  • 4
    I was aware of tuples and I have edited my original question to clarify that I'm talking about all object. I contest your claim that freezing any object is trivial in Python. At least it's not as trivial as `obj.freeze()`, unless there's something I don't know. – kerkeslager Oct 15 '10 at 14:52
  • 2
    @kerkeslager: you've accepted an answer that does exactly what my code did, only in an awkward non-native way. You cannot freeze a built-in object in python of course, but that's not the what you're asking is it? – SilentGhost Oct 15 '10 at 15:02
  • How is freezing dicts trivial? It is still [under discussion](http://stackoverflow.com/questions/2703599/what-would-a-frozen-dict-be). – Franklin Yu Feb 01 '17 at 03:50
13

You could always subclass list and add the "frozen" flag which would block __setitem__ doing anything:

class freezablelist(list):
    def __init__(self,*args,**kwargs):
        list.__init__(self, *args)
        self.frozen = kwargs.get('frozen', False)

    def __setitem__(self, i, y):
        if self.frozen:
            raise TypeError("can't modify frozen list")
        return list.__setitem__(self, i, y)

    def __setslice__(self, i, j, y):
        if self.frozen:
            raise TypeError("can't modify frozen list")
        return list.__setslice__(self, i, j, y)

    def freeze(self):
        self.frozen = True

    def thaw(self):
        self.frozen = False

Then playing with it:

>>> from freeze import freezablelist as fl
>>> a = fl([1,2,3])
>>> a[1] = 'chicken'
>>> a.freeze()
>>> a[1] = 'tuna'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "freeze.py", line 10, in __setitem__
    raise TypeError("can't modify frozen list")
TypeError: can't modify frozen list
>>> a[1:1] = 'tuna'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "freeze.py", line 16, in __setslice__
    raise TypeError("can't modify frozen list")
TypeError: can't modify frozen list
>>>
Nick T
  • 25,754
  • 12
  • 83
  • 121
  • Ah! I didn't think of using `__set__` and `__setitem__`! Good call. – kerkeslager Oct 15 '10 at 14:54
  • 1
    @kerkeslager: so how exactly does this solve your "freeze any object" problem? – SilentGhost Oct 15 '10 at 14:57
  • 1
    Yeah, this obviously isn't general (but nothing is, as no equivalent, ubiquitous mechanism in Python is), but it should be applicable to any sort of class if you subclass and override mutators (e.g. `__setitem__`, `__setattr__`, `__setslice__`) – Nick T Oct 15 '10 at 15:03
  • 1
    change lines 4-7 in self.frozen = kwargs.get('frozen', False) – Ant Oct 15 '10 at 15:19
  • @Ant: Done. I'm somewhat rusty with dictionaries, don't know all the proper methods to use when. – Nick T Oct 15 '10 at 18:24
  • @SilentGhost It doesn't exactly, but in concept it's what I was looking for. As Nick T says, it's simple enough to override all the mutators (as done above with __setitem__ and __setslice__). – kerkeslager Oct 18 '10 at 21:37