This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: class member inconsistancies
Type: Stage:
Components: Interpreter Core Versions:
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: georg.brandl, santaroga
Priority: normal Keywords:

Created on 2006-10-23 18:13 by santaroga, last changed 2022-04-11 14:56 by admin. This issue is now closed.

Messages (3)
msg30371 - (view) Author: EricDaigno (santaroga) Date: 2006-10-23 18:13
Hi...  When coding rapid classes I stumbled upon a
strange behaviour in the way python deals with data
membership of it's instances (new style) when using C
struct like container classes


behaviour changes when data members are declared in the
class itself as opposed to be declared in the __init__
method...

I understand that python does not perform any sort of
initialization outside of __init__ but this really
create confusion as to what is allowed and what is not
and is clear inconstancy that is either a language
usability bug of a plain buf in class
creation-initialization.

The problem happens when declaring a class that does
not contain __init__ method override but contains a
bunch of data elements that are initialized in the
declaration.

Expected behaviour would be that when a new instance is
created the python interpreter would create a new
instance and initialize the members as stated in the
class declaration and then call the __init__ method
(from object or the overriden __init__ from the class)

The reality is quite different.

Immutables (string) are dealt with correctly but
mutables (list, dict) are initialized only once.  Every
subsequent instantiation of the class will therefore
see new instances of member immutables objects but will
SHARE instances of the member mutables.

I have created a small programm that illustrate the
behaviour :

------------------------------------------------CodeStart
 

class A(object):
    def __init__(self):
        
        
        self.argsList = []
        self.dictArgs = {}
        self.ARG1 = ""
        self.ARG2 = ""
    
    def multUnnammedArgs(self,
                         *argsList):
        if not argsList is None:
            self.argsList += argsList
            
            
    def multNAmmedArgument(self,
                           **argDict):
        if not argDict is None:
            for i in argDict.iteritems():
                self.dictArgs[i[0]] = i[1]
                
    def mixedArguments(self, 
                       ARG1,
                       ARG2,
                       *argList,
                       **argDict):
        self.ARG1 = ARG1
        self.ARG2 = ARG2
        
        self.multUnnammedArgs(*argList)
        
        self.multNAmmedArgument(**argDict)
            
    def __str__(self):
        string = "Object A :"
        string += "\n\t\tARG1=" + self.ARG1 
        string += "\n\t\tARG2=" + self.ARG2 
        string += "\n\tUnnammed Arguments"
        for a in self.argsList:
            string += "\n\t\t" + a

        string += "\n\tNamed Arguments"
        for a in self.dictArgs.iteritems():
            string += "\n\t\t" + a[0] + "=" + a[1]
            
        return string


if __name__ == '__main__':
    obj = A()
    
    obj.multUnnammedArgs("arg1", "arg2", "arg3")
    print obj
    
    obj.multNAmmedArgument(TOTO = "toto",
                           TITI = "titi",
                           TaTa = "tATa")
    
    print obj   
    obj.mixedArguments(ARG1="ZeArg1",
                       ARG2="ZeArg2",
                       TOTO2 = "toto2",
                       TITI2 = "titi2",
                       TaTa2 = "tATa2")
    print obj     

-----------------------------------------------Code End


Here is the dump of my python environment 

---------------------------------------------Dump Start
[eric@speigel ~]$ python -v
# installing zipimport hook
import zipimport # builtin
# installed zipimport hook
# /lesia/toolbox/python/lib/python2.4/site.pyc matches
/lesia/toolbox/python/lib/python2.4/site.py
import site # precompiled from
/lesia/toolbox/python/lib/python2.4/site.pyc
# /lesia/toolbox/python/lib/python2.4/os.pyc matches
/lesia/toolbox/python/lib/python2.4/os.py
import os # precompiled from
/lesia/toolbox/python/lib/python2.4/os.pyc
import posix # builtin
# /lesia/toolbox/python/lib/python2.4/posixpath.pyc
matches /lesia/toolbox/python/lib/python2.4/posixpath.py
import posixpath # precompiled from
/lesia/toolbox/python/lib/python2.4/posixpath.pyc
# /lesia/toolbox/python/lib/python2.4/stat.pyc matches
/lesia/toolbox/python/lib/python2.4/stat.py
import stat # precompiled from
/lesia/toolbox/python/lib/python2.4/stat.pyc
# /lesia/toolbox/python/lib/python2.4/UserDict.pyc
matches /lesia/toolbox/python/lib/python2.4/UserDict.py
import UserDict # precompiled from
/lesia/toolbox/python/lib/python2.4/UserDict.pyc
# /lesia/toolbox/python/lib/python2.4/copy_reg.pyc
matches /lesia/toolbox/python/lib/python2.4/copy_reg.py
import copy_reg # precompiled from
/lesia/toolbox/python/lib/python2.4/copy_reg.pyc
# /lesia/toolbox/python/lib/python2.4/types.pyc matches
/lesia/toolbox/python/lib/python2.4/types.py
import types # precompiled from
/lesia/toolbox/python/lib/python2.4/types.pyc
# /lesia/toolbox/python/lib/python2.4/warnings.pyc
matches /lesia/toolbox/python/lib/python2.4/warnings.py
import warnings # precompiled from
/lesia/toolbox/python/lib/python2.4/warnings.pyc
# /lesia/toolbox/python/lib/python2.4/linecache.pyc
matches /lesia/toolbox/python/lib/python2.4/linecache.py
import linecache # precompiled from
/lesia/toolbox/python/lib/python2.4/linecache.pyc
import encodings # directory
/lesia/toolbox/python/lib/python2.4/encodings
#
/lesia/toolbox/python/lib/python2.4/encodings/__init__.pyc
matches
/lesia/toolbox/python/lib/python2.4/encodings/__init__.py
import encodings # precompiled from
/lesia/toolbox/python/lib/python2.4/encodings/__init__.pyc
# /lesia/toolbox/python/lib/python2.4/codecs.pyc
matches /lesia/toolbox/python/lib/python2.4/codecs.py
import codecs # precompiled from
/lesia/toolbox/python/lib/python2.4/codecs.pyc
import _codecs # builtin
#
/lesia/toolbox/python/lib/python2.4/encodings/aliases.pyc
matches
/lesia/toolbox/python/lib/python2.4/encodings/aliases.py
import encodings.aliases # precompiled from
/lesia/toolbox/python/lib/python2.4/encodings/aliases.pyc
#
/lesia/toolbox/python/lib/python2.4/encodings/utf_8.pyc
matches
/lesia/toolbox/python/lib/python2.4/encodings/utf_8.py
import encodings.utf_8 # precompiled from
/lesia/toolbox/python/lib/python2.4/encodings/utf_8.pyc
Python 2.4.2 (#1, Aug  1 2006, 16:57:48)
[GCC 4.0.2 20051125 (Red Hat 4.0.2-8)] on linux2
Type "help", "copyright", "credits" or "license" for
more information.
dlopen("/lesia/toolbox/python/lib/python2.4/lib-dynload/readline.so",
2);
import readline # dynamically loaded from
/lesia/toolbox/python/lib/python2.4/lib-dynload/readline.so
>>>
-------------------------------------------Dump end
msg30372 - (view) Author: EricDaigno (santaroga) Date: 2006-10-23 18:17
Logged In: YES 
user_id=1628112

darn....

Attached the wrong code ...  My bad, sorry about that.

Here is the correct test programm
 ------------------------------------------------Code Start

class A(object):
    aString = "first init..."
    aList = []
    aDict = {}

    def __str__(self):
        string = ""
        
        string += "_____________" + self.aString
        
        string += "\n\t aList= "
        for elem in self.aList:
            string += "\n\t\t" + elem
        
        string += "\n\t aDict= "
        for elem in self.aDict.keys():
            string += "\n\t\t" + elem + "=" + self.aDict[elem]
        
        string += "\n--------------"
        
        return string

if __name__ == '__main__':
    firstInstance = A()
    print firstInstance
    print "This is as expected from the class declaration."
    print "Members are initialized as declared in the class "
    print "and the new instance is clean"
    
    firstInstance.aString = "firstInstance's String"
    firstInstance.aList.append("firstInstance first List
element")
    firstInstance.aList.append("firstInstance second List
element")
    firstInstance.aList.append("firstInstance third List
element")
    firstInstance.aDict["firstInstance"] =
"SpecificToFirstInstance"
    firstInstance.aDict["whatever"] = "FirstInstance's Whatever"
    
    print firstInstance
    print "Again... So far so good"
    print ""
    
    secondInstance = A()
    print secondInstance
    print "This however is completely unsuspected."
    print "The class initialized the string properly but "
    print "mutables have not been replaced with the new
instances of list and dict"
    print "as we soiuld expect from the class declaration"
    print "It gets beter..."

    secondInstance.aString = "secondInstance String"
    secondInstance.aList.append("secondInstance first List
element")
    secondInstance.aList.append("secondInstance second List
element")
    secondInstance.aList.append("secondInstance third List
element")
    secondInstance.aDict["secondInstance"] =
"SpecificToSecondInstance"
    secondInstance.aDict["whatever"] = "econdInstance's
Whatever"

    print firstInstance
    print secondInstance
    print "I therefore conclude that when dealing with "
    print "'default' initlialization that Immutables are
dealt with properly but"
    print "mutables mutables are not initialized."
    print "This behaviour is contradictory from what one
would expect when "
    print "declaring a simple class that acts like a C struct"

 ------------------------------------------Code End
msg30373 - (view) Author: Georg Brandl (georg.brandl) * (Python committer) Date: 2006-10-23 20:41
Logged In: YES 
user_id=849994

This is not a bug. Names bound in a class namespace are
really attributes of the class object.
Class instances can access these attributes as "self.attr",
but as long as they aren't rebound
(by "self.attr = value"), they are attributes of the class,
not the instance.
History
Date User Action Args
2022-04-11 14:56:20adminsetgithub: 44158
2006-10-23 18:13:35santarogacreate