سؤال

I'm learning Python, but have no OOP experience. I'm entering the following lines in IDLE (Python 3.3), and the behavior of the objects is confusing me:

>>> class IBAN:
    ISOcode = '  '
    checkDigits = '00'
    class CCC:
        bankCode = '0000'
        branchCode = '0000'
        checkBank = '0'
        checkAccount = '0'
        account = '0000000000'


>>> >>> numberOne = IBAN()
>>> numberOne.CCC
<class '__main__.IBAN.CCC'>
>>> numberOne.CCC.bankCode
'0000'
>>> numberOne.ISOcode = 'ES'
>>> IBAN.ISOcode
'  '
>>> numberOne.ISOcode
'ES'
>>> IBAN.ISOcode = 'FR'
>>> numberOne.ISOcode
'ES'
>>> IBAN.ISOcode = 'FR'
>>> IBAN.ISOcode
'FR'
>>> numberTwo = IBAN()
>>> numberTwo.ISOcode
'FR'
>>> IBAN.ISOcode = 'IT'
>>> IBAN.ISOcode
'IT'
>>> numberOne.ISOcode
'ES'
>>> numberTwo.ISOcode
'IT'
>>> IBAN.ISOcode is numberTwo.ISOcode
True
>>> 

Why is the ISOcode attribute of object numberTwo the same as the ISOcode of IBAN, but not the same as the ISOcode of objectnumberOne?

I expected something like this:

>>> numberOne.ISOcode
'ES'
>>> numberTwo.ISOcode
'FR'

This, however, doesn't match my results.

I found a similar question on StackOverflow, that made me think the results should be:

>>> numberOne.ISOcode
'IT'
>>> numberTwo.ISOcode
'IT'

But that doesn't match my results either.

Can anybody explain or link to resources that explain the behavior of Classes in Python? I tried to study the Python Tutorial and "Unifying types and classes in Python 2.2" by Guido van Rossum, but I wasn't able to understand what they were saying.

Thank you!

هل كانت مفيدة؟

المحلول

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.

نصائح أخرى

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 = '  '

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__.

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

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.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top