Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.python > #8685 > unrolled thread
| Started by | Saqib Ali <saqib.ali.75@gmail.com> |
|---|---|
| First post | 2011-07-02 14:59 -0700 |
| Last post | 2011-07-03 12:25 +1000 |
| Articles | 9 — 5 participants |
Back to article view | Back to comp.lang.python
Inexplicable behavior in simple example of a set in a class Saqib Ali <saqib.ali.75@gmail.com> - 2011-07-02 14:59 -0700
Re: Inexplicable behavior in simple example of a set in a class Chris Rebert <clp2@rebertia.com> - 2011-07-02 15:14 -0700
Re: Inexplicable behavior in simple example of a set in a class Saqib Ali <saqib.ali.75@gmail.com> - 2011-07-02 15:23 -0700
Re: Inexplicable behavior in simple example of a set in a class Chris Rebert <clp2@rebertia.com> - 2011-07-02 16:25 -0700
Re: Inexplicable behavior in simple example of a set in a class Chris Angelico <rosuav@gmail.com> - 2011-07-03 10:46 +1000
Re: Inexplicable behavior in simple example of a set in a class Chris Rebert <clp2@rebertia.com> - 2011-07-02 18:07 -0700
Re: Inexplicable behavior in simple example of a set in a class Chris Angelico <rosuav@gmail.com> - 2011-07-03 11:14 +1000
Re: Inexplicable behavior in simple example of a set in a class Peter Otten <__peter__@web.de> - 2011-07-03 00:22 +0200
Re: Inexplicable behavior in simple example of a set in a class Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2011-07-03 12:25 +1000
| From | Saqib Ali <saqib.ali.75@gmail.com> |
|---|---|
| Date | 2011-07-02 14:59 -0700 |
| Subject | Inexplicable behavior in simple example of a set in a class |
| Message-ID | <2dc52e22-5c0f-4e24-8197-df616db1a42c@u2g2000yqb.googlegroups.com> |
I have written two EXTREMELY simple python classes. One class
(myClass1) contains a data attribute (myNum) that contains an integer.
The other class (myClass2) contains a data attribute (mySet) that
contains a set.
I instantiate 2 instances of myClass1 (a & b). I then change the value
of a.myNum. It works as expected.
Then I instantiate 2 instances of myClass2 (c & d). I then change the
value of c.mySet. Bizarrely changing the value of c.mySet also affects
the value of d.mySet which I haven't touched at all!?!?! Can someone
explain this very strange behavior to me? I can't understand it for
the life of me.
Please see below the source code as well as the output.
-------------------------- SOURCE CODE ------------------------------
import sets
class myClass1:
myNum = 9
def clearNum(self):
self.myNum = 0
def __str__(self):
return str(self.myNum)
class myClass2:
mySet = sets.Set(range(1,10))
def clearSet(self):
self.mySet.clear()
def __str__(self):
return str(len(self.mySet))
if __name__ == "__main__":
# Experiment 1. Modifying values of member integers in two
different instances of a class
# Works as expected.
a = myClass1()
b = myClass1()
print "a = %s" % str(a)
print "b = %s" % str(b)
print "a.clearNum()"
a.clearNum()
print "a = %s" % str(a)
print "b = %s\n\n\n" % str(b)
# Experiment 2. Modifying values of member sets in two different
instances of a class
# Fails Unexplicably. d is not being modified. Yet calling
c.clearSet() seems to change d.mySet's value
c = myClass2()
d = myClass2()
print "c = %s" % str(c)
print "d = %s" % str(d)
print "c.clearSet()"
c.clearSet()
print "c = %s" % str(c)
print "d = %s" % str(d)
-------------------------- OUTPUT ------------------------------
> python.exe myProg.py
a = 9
b = 9
a.clearNum()
a = 0
b = 9
c = 9
d = 9
c.clearSet()
c = 0
d = 0
[toc] | [next] | [standalone]
| From | Chris Rebert <clp2@rebertia.com> |
|---|---|
| Date | 2011-07-02 15:14 -0700 |
| Message-ID | <mailman.566.1309644846.1164.python-list@python.org> |
| In reply to | #8685 |
On Sat, Jul 2, 2011 at 2:59 PM, Saqib Ali <saqib.ali.75@gmail.com> wrote:
<snip>
> Then I instantiate 2 instances of myClass2 (c & d). I then change the
> value of c.mySet. Bizarrely changing the value of c.mySet also affects
> the value of d.mySet which I haven't touched at all!?!?! Can someone
> explain this very strange behavior to me? I can't understand it for
> the life of me.
<snip>
> class myClass2:
>
> mySet = sets.Set(range(1,10))
>
> def clearSet(self):
> self.mySet.clear()
>
> def __str__(self):
> return str(len(self.mySet))
Please read a tutorial on object-oriented programming in Python. The
official one is pretty good:
http://docs.python.org/tutorial/classes.html
If you do, you'll find out that your class (as written) has mySet as a
class (Java lingo: static) variable, *not* an instance variable; thus,
it is shared by all instances of the class, and hence the behavior you
observed. Instance variables are properly created in the __init__()
initializer method, *not* directly in the class body.
Your class would be correctly rewritten as:
class MyClass2(object):
def __init__(self):
self.mySet = sets.Set(range(1,10))
def clearSet(self):
# ...rest same as before...
Cheers,
Chris
--
http://rebertia.com
[toc] | [prev] | [next] | [standalone]
| From | Saqib Ali <saqib.ali.75@gmail.com> |
|---|---|
| Date | 2011-07-02 15:23 -0700 |
| Message-ID | <6441039f-f226-4632-8e74-2b6415bf914e@n28g2000vbs.googlegroups.com> |
| In reply to | #8686 |
> Instance variables are properly created in the __init__() > initializer method, *not* directly in the class body. > > Your class would be correctly rewritten as: > > class MyClass2(object): > def __init__(self): > self.mySet = sets.Set(range(1,10)) > > def clearSet(self): > # ...rest same as before... Thanks Chris. That was certainly very helpful!! So just out of curiosity, why does it work as I had expected when the member contains an integer, but not when the member contains a set?
[toc] | [prev] | [next] | [standalone]
| From | Chris Rebert <clp2@rebertia.com> |
|---|---|
| Date | 2011-07-02 16:25 -0700 |
| Message-ID | <mailman.567.1309649149.1164.python-list@python.org> |
| In reply to | #8688 |
On Sat, Jul 2, 2011 at 3:23 PM, Saqib Ali <saqib.ali.75@gmail.com> wrote:
>> Instance variables are properly created in the __init__()
>> initializer method, *not* directly in the class body.
>>
>> Your class would be correctly rewritten as:
>>
>> class MyClass2(object):
>> def __init__(self):
>> self.mySet = sets.Set(range(1,10))
>>
>> def clearSet(self):
>> # ...rest same as before...
>
>
> Thanks Chris. That was certainly very helpful!!
>
> So just out of curiosity, why does it work as I had expected when the
> member contains an integer, but not when the member contains a set?
To explain that, one must first understand that name lookup on an
object looks in the following places, in order:
1. the instance itself
2. the instance's class
3. the instance's superclasses
So, if we have:
class Foo(object):
bar = 7
foo_inst = Foo()
then both `foo_inst.bar` and `Foo.bar` refer to the same value.
However, if we then do:
foo_inst.bar = 42
then we'll have:
foo_inst.bar == 42 and Foo.bar == 7
Now back to your actual question. In clearNum(), you do:
self.myNum = 0
which creates a *new* instance variable that shadows the class
variable of the same name, like in my example. If you check, you'll
indeed see that myClass1.myNum is still 9 after calling clearNum().
By contrast, in clearSet() you do:
self.mySet.clear()
which just mutates the existing Set object in-place. No new variable
is created, and mySet is still a class variable and thus shared by all
instances.
Further reading:
http://effbot.org/zone/python-objects.htm
http://effbot.org/zone/call-by-object.htm
Cheers,
Chris
--
http://rebertia.com
[toc] | [prev] | [next] | [standalone]
| From | Chris Angelico <rosuav@gmail.com> |
|---|---|
| Date | 2011-07-03 10:46 +1000 |
| Message-ID | <mailman.571.1309653963.1164.python-list@python.org> |
| In reply to | #8688 |
On Sun, Jul 3, 2011 at 8:23 AM, Saqib Ali <saqib.ali.75@gmail.com> wrote:
> So just out of curiosity, why does it work as I had expected when the
> member contains an integer, but not when the member contains a set?
It's not integer vs set; it's the difference between rebinding and
calling a method. It's nothing to do with object orientation; the same
happens with ordinary variables:
>>> a=b=1
>>> a,b
(1, 1)
>>> a=2
>>> a,b
(2, 1)
>>> c=d=[]
>>> c,d
({}, [])
>>> c.append("Test")
>>> c,d
(['Test'], ['Test'])
But:
>>> c=['Foobar']
>>> c,d
(['Foobar'], ['Test'])
When you do a=2 or c=['Foobar'], you're rebinding the name to a new
object. But c.append() changes that object, so it changes it
regardless of which name you look for it by.
Chris Angelico
[toc] | [prev] | [next] | [standalone]
| From | Chris Rebert <clp2@rebertia.com> |
|---|---|
| Date | 2011-07-02 18:07 -0700 |
| Message-ID | <mailman.572.1309655278.1164.python-list@python.org> |
| In reply to | #8688 |
On Sat, Jul 2, 2011 at 5:46 PM, Chris Angelico <rosuav@gmail.com> wrote:
> On Sun, Jul 3, 2011 at 8:23 AM, Saqib Ali <saqib.ali.75@gmail.com> wrote:
>> So just out of curiosity, why does it work as I had expected when the
>> member contains an integer, but not when the member contains a set?
>
> It's not integer vs set; it's the difference between rebinding and
> calling a method. It's nothing to do with object orientation; the same
> happens with ordinary variables:
<snip>
>>>> c=d=[]
>>>> c,d
> ({}, [])
Nasty typo in your pseudo-interpreter-session there...
Cheers,
Chris
[toc] | [prev] | [next] | [standalone]
| From | Chris Angelico <rosuav@gmail.com> |
|---|---|
| Date | 2011-07-03 11:14 +1000 |
| Message-ID | <mailman.575.1309655643.1164.python-list@python.org> |
| In reply to | #8688 |
On Sun, Jul 3, 2011 at 11:07 AM, Chris Rebert <clp2@rebertia.com> wrote:
>>>>> c,d
>> ({}, [])
>
> Nasty typo in your pseudo-interpreter-session there...
Whoops! This is what I get for trying to be too smart!
>>> c,d
([], [])
Thanks for catching that, Chris. :)
(another) Chris
[toc] | [prev] | [next] | [standalone]
| From | Peter Otten <__peter__@web.de> |
|---|---|
| Date | 2011-07-03 00:22 +0200 |
| Message-ID | <iuo5mu$45h$1@solani.org> |
| In reply to | #8685 |
Saqib Ali wrote: > > > I have written two EXTREMELY simple python classes. One class > (myClass1) contains a data attribute (myNum) that contains an integer. > The other class (myClass2) contains a data attribute (mySet) that > contains a set. > > I instantiate 2 instances of myClass1 (a & b). I then change the value > of a.myNum. It works as expected. self.value = new_value sets the attribute to a new value while self.value.modifying_method() keeps the old value (the object with the same id(object)), but changes the state of that old object. All variables bound to that object will "see" that internal change of state. As integers are "immutable" objects whose state cannot be changed you'll never observe the behaviour described below with them. > Then I instantiate 2 instances of myClass2 (c & d). I then change the > value of c.mySet. Bizarrely changing the value of c.mySet also affects > the value of d.mySet which I haven't touched at all!?!?! Can someone > explain this very strange behavior to me? I can't understand it for > the life of me. What you access as c.mySet or d.mySet is really myClass2.mySet, i. e. a class attribute and not an instance attribute. If you want a set per instance you have to create it in the __init__() method: >>> class MyClass: ... def __init__(self): ... self.my_set = set(range(10)) ... def clear_set(self): ... self.my_set.clear() ... >>> a = MyClass() >>> b = MyClass() >>> a.my_set.add(42) >>> a.my_set, b.my_set (set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 42]), set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) >>> b.clear_set() >>> a.my_set, b.my_set (set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 42]), set([]))
[toc] | [prev] | [next] | [standalone]
| From | Steven D'Aprano <steve+comp.lang.python@pearwood.info> |
|---|---|
| Date | 2011-07-03 12:25 +1000 |
| Message-ID | <4e0fd31a$0$29983$c3e8da3$5496439d@news.astraweb.com> |
| In reply to | #8685 |
Saqib Ali wrote:
> I have written two EXTREMELY simple python classes. One class
> (myClass1) contains a data attribute (myNum) that contains an integer.
> The other class (myClass2) contains a data attribute (mySet) that
> contains a set.
>
> I instantiate 2 instances of myClass1 (a & b). I then change the value
> of a.myNum. It works as expected.
>
> Then I instantiate 2 instances of myClass2 (c & d). I then change the
> value of c.mySet. Bizarrely changing the value of c.mySet also affects
> the value of d.mySet which I haven't touched at all!?!?!
But that is wrong -- you HAVE touched it. Look carefully: in myClass2, you
have this:
class myClass2:
mySet = sets.Set(range(1,10))
def clearSet(self):
self.mySet.clear()
mySet is a class attribute, shared by ALL instances, and mySet.clear()
modifies it in place. This is exactly the same as this snippet:
>>> a = set([1, 2, 3])
>>> b = a
>>> b.clear()
>>> a
set([])
>>> b
set([])
In Python, attributes assigned in the class scope are shared between all
instances. Attributes assigned directly on self are not:
class Test:
a = set([1, 2, 3])
def __init__(self):
self.b = set([1, 2, 3])
>>> x = Test()
>>> y = Test()
>>> x.a is y.a # The same set is shared by both instances.
True
>>> x.b is y.b # Each instance gets its own set.
False
So why does myClass1 behave differently? Simple: look at the clear method:
class myClass1:
myNum = 9
def clearNum(self):
self.myNum = 0
It assigns a new attribute, rather than modifying the object in place!
Assignment to self.myNum creates a new unshared attribute:
>>> z = myClass1()
>>> z.myNum
9
>>> z.__dict__ # No instance attributes yet.
{}
>>> z.clearNum()
>>> z.__dict__ # Now there is one.
{'myNum': 0}
>>> z.__class__.myNum # the class attribute still exists
9
The simplest way to fix this is to move the declaration of self.mySet into
the __init__ method.
--
Steven
[toc] | [prev] | [standalone]
Back to top | Article view | comp.lang.python
csiph-web