Welcome, guest | Sign In | My Account | Store | Cart

make a class instance to immutable one. we can call fields, methods, and properties. This airms to make easier to develop multi thread object-oriented programming.

Python, 74 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# -*- coding: utf-8 -*-
"""
immutablize instance sample
"""
import operator
import types



class A(object):
    def __init__(self, a, b):
        self.a = a
        self.__b = b
    
    def say(self):
        print self.a,self.__b
    
    def incr(self):
        self.a = self.a + 100
    
    def incr2(self):
        self.incr()


def frozen_instance(instance):
    def new__(_cls, inst):
        return tuple.__new__(_cls, tuple([inst] + inst.__dict__.values()))
    
    def repr__(self):
        return repr(self[0])
    
    def getattr__(self, name):
        return types.MethodType(self[0].__class__.__dict__[name], self, self[0].__class__)
    klass = instance.__class__
    nlass = type('Frozen'+klass.__name__,(tuple,),
        dict(__new__=new__, __repr__=repr__, __getattr__=getattr__)
    )
    for i,k in enumerate(instance.__dict__.keys()):
        setattr(nlass, k, property(operator.itemgetter(1+i)))
    
    return nlass(instance)




if __name__ == '__main__':
    
    a = A(1, "aaa")
    print "[a]"
    a.say()
    frozen_a = frozen_instance(a)
    print "[frozen_a]"
    frozen_a.say()

    a.incr()
    a.say()
    
    frozen_a.incr2()


-----outputs-----
[a]
1 aaa
[frozen_a]
1 aaa
101 aaa
Traceback (most recent call last):
  File "fix.py", line 58, in <module>
    frozen_a.incr2()
  File "fix.py", line 22, in incr2
    self.incr()
  File "fix.py", line 19, in incr
    self.a = self.a + 100
AttributeError: can't set attribute

5 comments

Steven D'Aprano 12 years, 8 months ago  # | flag

Doesn't work for instances with no __dict__.

>>> frozen_instance([])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 14, in frozen_instance
AttributeError: 'list' object has no attribute '__dict__'
>>>
>>> class A(object):
...     __slots__ = 'x'
...
>>> frozen_instance(A())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 14, in frozen_instance
AttributeError: 'A' object has no attribute '__dict__'
Steven D'Aprano 12 years, 8 months ago  # | flag

It also breaks instances with __getitem__.

>>> class A(list):  pass
...
>>> a = A([1, 2, 3])
>>> frozen_instance(a)[1]  # expecting 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: tuple index out of range
KATO Kanryu (author) 12 years, 8 months ago  # | flag

supported for raw list and dict:

def frozen_instance(instance):
    if type(instance) == dict:
        def new__(_cls, inst):
            return dict.__new__(_cls, dict(inst.items()))

        def setitem__(self, *args):
            raise AttributeError("can't set item")
        klass = instance.__class__
        nlass = type('Frozen'+klass.__name__,(dict,),
            dict(__new__=new__, __setitem__=setitem__, __delitem__=setitem__, 
                update=setitem__, pop=setitem__, popitem=setitem__, clear=setitem__)
        )
        return nlass(instance)

    if type(instance) == list:
        def new__(_cls, inst):
            return tuple.__new__(_cls, tuple(inst))

        def repr__(self):
            return repr(list(self))

        def getattr__(self, name):
            raise AttributeError("can't set item")
        klass = instance.__class__
        nlass = type('Frozen'+klass.__name__,(tuple,),
            dict(__new__=new__, __repr__=repr__, __getattr__=getattr__)
        )
        return nlass(instance)

    if hasattr(instance, '__slots__'):
        slots = instance.__slots__ if type(instance.__slots__) == list else [instance.__slots__]
        def new__(_cls, inst):
            return tuple.__new__(_cls, tuple([inst] + [getattr(inst, s) for s in slots]))

        def repr__(self):
            return repr(self[0])

        def getattr__(self, name):
            return types.MethodType(self[0].__class__.__dict__[name], self, self[0].__class__)
        klass = instance.__class__
        nlass = type('Frozen'+klass.__name__,(tuple,),
            dict(__new__=new__, __repr__=repr__, __getattr__=getattr__)
        )
        for i,k in enumerate(slots):
            setattr(nlass, k, property(operator.itemgetter(1+i)))

        return nlass(instance)

    def new__(_cls, inst):
        return tuple.__new__(_cls, tuple([inst] + inst.__dict__.values()))

    def repr__(self):
        return repr(self[0])

    def getattr__(self, name):
        return types.MethodType(self[0].__class__.__dict__[name], self, self[0].__class__)
    klass = instance.__class__
    nlass = type('Frozen'+klass.__name__,(tuple,),
        dict(__new__=new__, __repr__=repr__, __getattr__=getattr__)
    )
    for i,k in enumerate(instance.__dict__.keys()):
        setattr(nlass, k, property(operator.itemgetter(1+i)))

    return nlass(instance)
KATO Kanryu (author) 12 years, 8 months ago  # | flag

newer sample:

    # frozen list
    frozen_a = frozen_instance([1, 2, 3])
    print frozen_a
    print len(frozen_a)
    print frozen_a.count(2)
    print frozen_a.index(2)

    try: frozen_a.sort()
    except AttributeError, e: print "AttributeError:", e

    # frozen dict
    frozen_d = frozen_instance(dict(a=1,b=2))
    print frozen_d.keys(), frozen_d.values(), frozen_d.items()
    try: frozen_d["a"] = 4
    except AttributeError, e: print "AttributeError:", e
    try: frozen_d.update([("c", 3)])
    except AttributeError, e: print "AttributeError:", e
    try: frozen_d.pop("b")
    except AttributeError, e: print "AttributeError:", e
    try: frozen_d.popitem()
    except AttributeError, e: print "AttributeError:", e
    try: frozen_d.clear()
    except AttributeError, e: print "AttributeError:", e
    print frozen_d.setdefault("a",5)
    print frozen_d

    class S(object):
        __slots__ = 'x'
        def __init__(self, x):
            self.x = x

    s = S(123)
    frozen_s = frozen_instance(s)
    print frozen_s.x

    class A(list):  pass
    a = A([1, 2, 3])
    print a[1]
    print frozen_instance(a)[0][1]
KATO Kanryu (author) 12 years, 8 months ago  # | flag

upper sample code outputs the following: [1, 2, 3] 3 1 1 AttributeError: can't set item ['a', 'b'] [1, 2] [('a', 1), ('b', 2)] AttributeError: can't set item AttributeError: can't set item AttributeError: can't set item AttributeError: can't set item AttributeError: can't set item 1 {'a': 1, 'b': 2} 123 2 2