Forcing immutability on an object
Before I start, I'm already aware that object immutability in Python is often a bad idea, however, I believe that in my case it would be appropriate.
Let's say I'm working with a coordinate system in my code, such that each coordinate uses a struct of X, Y, Z. I've already overloaded subtraction, addition, etc. methods to do what I want. My current problem is the assignment operator, which I've read cannot be overloaded. Problem is when I have the following, I do not want A to point to the same point as B, I want the two to be independent, in case I need to overwrite a coordinate of one but not the other later:
B = Point(1,2,3)
A = B
I'm aware that I can use deepcopy, but that seems like a hack, especially since I could have a list of points that I might need to take a slice of (in which case it would again have a slice of point references, not points). I've also considered using tuples, but my points have member methods I need, and a very large portion of my code already uses the structs.
My idea was to modify Point to be immutable, since it's really only 3 floats of data, and from doing some research _new _() seems like the right function to overwrite for this. I'm not sure how to a开发者_如何学运维chieve this though, would it be something like this or am I way off?
def __new__(self):
return Point(self.x, self.y, self.z)
EDIT: My bad, I realized after reading katrielalex's post that I can't modify a parameter of immutable object once it has been defined, in which case it's not a problem that both A and B point to the same data since a reassignment would require creation of a new point. I'd say that katrielalex's and vonPetrushev's posts achieve what I want, I think I'll go with vonPetrushev's solution since I don't need to rewrite all my current code to use tuples (the extra set of parentheses and not being able to reference coordinates as point.x)
In conjunction with katrielalex's suggestion, making the Point
a named tuple would be good as well. Here I've just replaced the tuple
parent with namedtuple('Point', 'x y z')
- and that's enough for it to work.
>>> from collections import namedtuple
>>> class Point(namedtuple('Point', 'x y z')):
... def __add__(self, other):
... return Point((i + j for i, j in zip(self, other)))
...
... def __mul__(self, other):
... return sum(i * j for i, j in zip(self, other))
...
... def __sub__(self, other):
... return Point((i - j for i, j in zip(self, other)))
...
... @property
... def mod(self):
... from math import sqrt
... return sqrt(sum(i*i for i in self))
...
Then you can have:
>>> Point(1, 2, 3)
Point(x=1, y=2, z=3)
>>> Point(x=1, y=2, z=3).mod
3.7416573867739413
>>> Point(x=1, y=2, z=3) * Point(0, 0, 1)
3
>>> Point._make((1, 2, 3))
Point(x=1, y=2, z=3)
(Thanks to katrielalex for suggesting to extend the namedtuple rather than copying the code produced.)
You can make Point
a subclass of tuple
-- remember, the built-in types (at least in recent Pythons) are just more classes. This will give you the desired immutability.
However, I'm slightly confused about your suggested use case:
in case I need to overwrite a coordinate of one but not the other later:
That doesn't make sense if Point
s are immutable...
>>> class Point(tuple):
... def __add__(self, other):
... return Point((i + j for i, j in zip(self, other)))
...
... def __mul__(self, other):
... return sum(i * j for i, j in zip(self, other))
...
... def __sub__(self, other):
... return Point((i - j for i, j in zip(self, other)))
...
... @property
... def mod(self):
... from math import sqrt
... return sqrt(sum(i*i for i in self))
...
>>> a = Point((1,2,3))
>>> b = Point((4,5,6))
>>> a + b
(5, 7, 9)
>>> b - a
(3, 3, 3)
>>> a * b
32
>>> a.mod
3.7416573867739413
>>> a[0] = 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'Point' object does not support item assignment
try this:
class Point(object):
def __init__(self, x, y, z):
self._x=x
self._y=y
self._z=z
def __getattr__(self, key):
try:
key={'x':'_x','y':'_y','z':'_z'}[key]
except KeyError:
raise AttributeError
else:
return self.__dict__[key]
def __setattr__(self, key, value):
if key in ['_x','_y','_z']:
object.__setattr__(self, key, value)
else:
raise TypeError("'Point' object does not support item assignment")
So, you can construct a Point object, but not change its attributes.
精彩评论