Peculiar Behavior Of Classes In Python
Solution 1:
The class is a different object than instances of the class. Classes have attributes, and instances have attributes. Every time you try to access an attribute of an instance, if the instance does not have the attribute, it is looked up on the class. If you set an attribute on an instance, it is always set on the instance, even if that means it shadows an attribute of the same name on the class.
In your class IBAN, you defined an attribute ISOcode
on the class. When you do numberOne.ISOcode = 'ES'
, you set a new attribute on the instance. You have decoupled the value of numberOne.ISOcode
from the value of IBAN.ISOcode
. But any new instances you create (like numberTwo
) will still not have their own instance ISOcode. When you then set IBAN.ISOcode
, you are changing the class attribute.
You can think of the class attribute as like a "default". When you do obj.attr
, if obj
does not have its own attribute attr
, it takes the value that the class has at that moment. If you change the value of class's attribute, you change the default; if you later try to access the attribute on an instance that doesn't have its own attribute, they will see the new default that you created.
Here is a somewhat more streamlined example:
>>> class Foo(object):
... attr = "Something"
>>> one = Foo()
>>> two = Foo()
At first, all have the same value, because I didn't specify any instance-specific values, so one
and two
both get their value from the class.
>>> Foo.attr
'Something'
>>> one.attr
'Something'
>>> two.attr
'Something'
Now I will set a new value just for one
. This does not affect Foo
, and two
is still gettings its value from Foo
, so two
is also not affected.
>>> one.attr = "This is only for #1"
>>> Foo.attr
'Something'
>>> one.attr
'This is only for #1'
>>> two.attr
'Something'
Now I will set a new value on the class Foo
. This doesn't affect one
, because one
has its own value. But since two
doesn't have its own value, it is getting its value from the class, and since I changed the class's value, this changes what value I get from two
:
>>> Foo.attr = "This is the class value"
>>> Foo.attr
'This is the class value'
>>> one.attr
'This is only for #1'
>>> two.attr
'This is the class value'
The thing to remember is that every attribute lookup is always dynamically computed right when you do it. Creating an instance does not automatically copy class attributes from the class to the instance. When you access an attribute on an instance, the instances says to itself "Do I have my own attribute with this name? If so, return that value. If not, return the class's value." It asks itself this every time you try to access an attribute; there is no moment at which the instance becomes "independent" of the class.
Solution 2:
ISOcode
as defined in your example is what's known as a class attribute, not an instance attribute
You should set self.ISOcode
inside your class's __init__
method to make it unique to each instance. Otherwise the attribute is stored in the class.
class IBAN:
def __init__(self):
self.ISOcode = ' '
Solution 3:
You are doing some confusing name shadowing by setting instance attributes over class attributes.
>>> class A():
stuff = ''
>>> a = A()
>>> a.stuff
''
>>> a.stuff = 'thing' # sets an instance attribute
>>> a.stuff
'thing'
>>> a.__class__.stuff
''
>>> a.__class__.stuff = 'blah'
>>> a.stuff # instance attributes are checked FIRST
'thing'
>>> aa = A()
>>> aa.stuff
'blah'
Basically, don't do this. If you intend for attributes to be instance attributes, set them up in __init__
.
Solution 4:
Python has class attributes and instance attributes. When you assign a value like
x.ISOcode = 'b'
It creates a member attribute for the instance. When you access an attribute in an object first it searches in member attributes, if not found it searches in class attributes.
Consider the following example
class IBAN(object):
ISOcode = 'a'
print IBAN.ISOcode // outputs a
x = new IBAN()
print x.ISOcode // outputs "a"
x.ISOcode = 'b' // creates a member variable for instance x instead of modifying class variable
print IBAN.ISOcode // outputs "a"
print x.ISOcode // outputs "b"
print x.__class__.ISOcode // outputs "a"
y = new IBAN()
print y.ISOcode // outputs "a"
IBAN.ISOcode = 'c'
print IBAN.ISOcode // outputs "c"
print x.ISOcode // outputs "b" because member variable is set
print x.__class__.ISOcode // outputs "c"
print y.ISOcode // outputs "c" because it still uses class variable. no instance variable set for this instance
Solution 5:
The behavior you're seeing is because of the fallback behavior of member access on python objects. numberOne
is an instance of the IBAN
class, and when you try to access a member of it, such as with numberOne.ISOCode
, it will check to see if the object itself has a member named ISOCode
. If it does, it will use that. If not, then it will fallback to the object's type, and check to see if the type has a member of that name, and use that.
However, when you set a member on an object, for instance when numberOne.ISOCode = 'ES'
, it doesn't do the fallback behavior, it either uses the existing member of that object, or if there is no such member it will create it.
It probably doesn't matter because you've never done OOP before, but Python does things a bit different than other language. You don't declare instance members inside a python class, so when you set ISOCode
directly under the IBAN
class, you're making it a member of the IBAN
class object, not individual instances of that class. If you want all instances of the class to have a field, assign it in the __init__
constructor function for that class. For instance:
#!/bin/python
class IBAN:
def __init__(self):
self.ISOCode = ' '
This will ensure that when an instance is created, it will have it's own ISOCode
member.
Post a Comment for "Peculiar Behavior Of Classes In Python"