Search code examples
pythonsubclassimmutability

How to subclass int and make it mutable


Is it possible to subclass int and make it mutable?

Consider the following class:

class CustomBitVector(int):

    # bit 7
    @property
    def seventh_property(self):
        return bool(self & (1 << 7))

    @seventh_property.setter
    def seventh_property(self, val):
        self |= bool(val) << 7

    # bit 6
    @property
    def sixth_property(self):
        return bool(self & (1 << 6))

    @sixth_property.setter
    def sixth_property(self, val):
        self |= bool(val) << 6


    # ... a few more of these ...

    # bit 0
    @property
    def zeroth_property(self):
        return bool(self & (1 << 0))

    @zeroth_property.setter
    def zeroth_property(self, val):
        self |= bool(val) << 0

I trying to make a nice interface to a bit vector. I'm reading a proprietary protocol off of a socket and I've made classes to represent the messages I'm sending/receiving. Often these messages include bit vectors and it would be nice to deal with them like this.

This already works great for reading the bit vector values, but setting them doesn't work because int is immutable.

If I rewrite one of the setters like:

@sixth_property.setter
def sixth_property(self, val):
    print 'before:', self
    self |= bool(val) << 6
    print 'after:', self

Then I get this behavior:

In [2]: c = CustomBitVector()

In [3]: c.sixth_property
Out[3]: False

In [4]: c.sixth_property = True
before: 0
after: 64

In [5]: c
Out[5]: 0

In [6]: c.sixth_property
Out[6]: False

I can see my folly ... I'm assigning to self instead of modifying it. How can I modify self in this case?

Any crazy hack to make this happen? Maybe using a metaclass or something?


UPDATE

I forgot to mention a requirement:

Instances of CustomBitVector must behave like int. In particular, I need to be able to pass them to struct.pack


Solution

  • Is it possible to subclass int and make it mutable?

    Sort of. You can add all the mutable parts you want, but you can't touch the int parts, so the degree of mutability you can add won't help you.

    Instead, don't use an int subclass. Use a regular object that stores an int. If you want to be able to pass it to struct.pack like an int, implement an __index__ method to define how to interpret your object as an int:

    class IntLike(object): # not IntLike(int):
        def __init__(self, value=0):
            self.value = value
        def __index__(self):
            return self.value
        ...
    

    You can implement additional methods like __or__ for | and __ior__ for in-place, mutative |=. Don't try to push too hard for complete interoperability with ints, though; for example, don't try to make your objects usable as dict keys. They're mutable, after all.

    If it's really important to you that your class is an int subclass, you're going to have to sacrifice the c.sixth_property = True syntax you want. You'll have to pick an alternative like c = c.with_sixth_property(True), and implement things non-mutatively.