Python - How do I differentiate between two list elements that point to the same object?
I have a Ring structure implemented as follows (based on a cookbook recipe I found):
class Ring(list):
def turn(self):
last = self.pop(0)
self.append(last)
def setTop(self, objectReference):
if objectReference not in self:
raise ValueError, "object is not in ring"
while self[0] is not objectReference:
self.turn()
Say I do the following:
x = Ring([1,2,3,4,4])
x.setTop(4)
My code will always set the first 4 (currently x[3]) to x[0]. It seems (via object identity and hash id testing between x[3] and x[4]) that Python is reusing the 4 object.
How do I tell Python that I really want the second 4 (currently x[4]) to be at the top?
Apologies for the basic question ... one of the downfalls of being a self-taught beginner.
Thanks,
Mike
===EDIT===
For what it's worth, I dropped the setTop method from the class. I had added it to the standard recipe thi开发者_运维知识库nking "hey, this would be neat and might be useful." As the answers (esp. "what's the difference", which is spot on) and my own experience using the structure show, it's a crappy method that doesn't support any of my use cases.
In other words, adding something because I could instead of fulfilling a need = fail.
From Learning Python, 4th edition -- Chapter 6:
At least conceptually, each time you generate a new value in your script by running an expression, Python creates a new object (i.e., a chunk of memory) to represent that value. Internally, as an optimization, Python caches and reuses certain kinds of un- changeable objects, such as small integers and strings (each 0 is not really a new piece of memory—more on this caching behavior later). But, from a logical perspective, it works as though each expression’s result value is a distinct object and each object is a distinct piece of memory.
The question is..
if x[3] is x[4]:
print "What's the difference?"
If you know you want the second, then do
x = Ring([1,2,3,4,4])
x.setTop(4)
x.turn()
x.setTop(4)
You can enhance setTop() to take an additional parameter and do it inside.
Cpython has an "integer cache" for smallish integers, so that values from -5 up to 255 (may vary by version or Python implentation) reuse the same object for a given value. That is, all 4s are the same int object with a value of 4. This is done to reduce the necessity for object creation.
There are a few ways to work around this.
- You can use long integers (e.g., write 4L instead of 4). Python does not use the cache for long integers. (You could also use floats, as these are likewise not cached.) If you do a lot of math with the numbers, however, this could incur some performance penalty.
- You can wrap each item in a list or tuple (reasonably convenient because there is simple syntax for this, though it's more syntax than long integers or floats).
- You can create your own object to wrap the integer. The object would have all the same methods as an integer (so it works like an integer in math, comparisons, printing, etc.) but each instance would be unique.
I personally like using long ints in this case. You can easily convert the integers to longs in the constructor, and in any method that adds an item.
It sounds like you always want to turn at least once, right? If so, re-write your setTop method like so:
def setTop(self, objectReference):
if objectReference not in self:
raise ValueError, "object is not in ring"
self.turn()
while self[0] is not objectReference:
self.turn()
Then it cycles between the expected states:
>>> x = Ring([1,2,3,4,4])
>>> x
[1, 2, 3, 4, 4]
>>> x.setTop(4)
>>> x
[4, 4, 1, 2, 3]
>>> x.setTop(4)
>>> x
[4, 1, 2, 3, 4]
>>> x.setTop(4)
>>> x
[4, 4, 1, 2, 3]
For small numbers, python will have a cache of objects premade to avoid the costs of making new objects. They will have the same object identity. Java does this as well. You need a way to make it avoid doing this.
Python reuses small integers and short strings. As far as I know, there's no way around this - you'll have to get along with this and the fact that setTop
only rotates until the first match. I suppose you could add an optinal parameter, n = 1
, and turn until the n
th match. But that's kinda beside the point, isn't it?
Unrelatedly, consider this:
>>> class Point(object):
... def __init__(self, x, y):
... self.x, self.y = x, y
... def __eq__(self, other):
... return (self.x == other.x and self.y == other.y)
...
>>> a_ring = Ring(Point(1, 2), Point(15, -9), Point(0, 0))
>>> a_ring.seTop(Point(15, -9))
Traceback ...
...
ValueError: object not in ring
Not how it is supposed to work, is it? You should use while self[0] != objectReference
(which is btw a misleading name) to avoid this.
I also don't know enough to be sure, but I guess numbers, even though beeing objects, are the same objects when used in different points of your code. Why do I think so? Look:
>>> type(2)
<type 'int'>
>>> type(lambda x:x)
<type 'function'>
>>> 2 is 2
True
>>> (lambda x: x) is (lambda x: x)
False
2 objects are not identical when created twice. But numbers are not created by you, they are already there. And it makes no sense to give one 4
a different object from another 4
. At least I don't see one.
精彩评论