tags:

views:

173

answers:

7

Hi all,

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 thinking "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.

A: 

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.

erikb
you want to use the `is` operator when making comparisons like this.
aaronasterling
@AaronMcSmooth thanks for pointing it out. Was already late when I wrote this.
erikb
+2  A: 

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.

Wai Yip Tung
A: 

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.

Daenyth
+4  A: 

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?"
sleepynate
I have to say that I was in the book store in a hurry an bought learning python when I was first starting with python four months ago. It's a complete and total waste of time. It's only so thick because it repeats everything _ad. nauseum_ and then still misses half of what there is to say. Python in a Nutshell is __much__ better. It says everything once and well.
aaronasterling
more on topic, the difference in this case is the rotational state of the modeled ring.
aaronasterling
Sure, but if you have two primitive constants in the ring, the rotational state of the modeled ring based on the relationship between those literals is moot. Who cares if it grabs the first reference to that constant or the second? Since they will have the same properties, there is no real difference. If one has different objects, python's reflective tools would show the difference between them, allowing there to be a discernible difference in the modeled ring. If I give you a stick marked with a "4" at each end and say "start at the 4 and go to the other end", does it matter which 4 you pick?
sleepynate
Yes it does matter. Say it's layed out 4 - 2 - 3 - 4. do you traverse 2 or 3 first? The desire to specify which it is is not at all unreasonable.
aaronasterling
Sure, but that is not the problem domain laid out in the question above. In addition, assuming our MikeRand is interested in your ring, he will have to specify positional logic (i.e.: *if the number of elements that have the same value of elements is greater than one, decide what to do*). This can be seen in Wai Yip Tung's answer. However, do to MikeRand's interest in the two constants next to one another in the list, removing one reference to the constant simply allows the other to fill its place, where removing the latter isolates the first reference. Either way: one reference to constant 4
sleepynate
A: 

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.

delnan
Good point. I'm not sure if I want it to work that way or not. I'll have to think through it.
MikeRand
+1  A: 

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.

kindall
+1  A: 

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]
Just Some Guy