It's important understanding that in Python everything is an object (including functions, and a class declaration itself)
When you do this:
class Classy():
inty = 3
stringy = "whatevs"
You're assigning inty and stringy to the Class, not to the instances. Check this:
class Classy():
inty = 3
stringy = "whatevs"
print(Classy.__dict__)
Wait... A class with a __dict__? Yeah, because Classy is also an instance (of type classobj, since you're using old style classes, which you shouldn't really do, by the way... You should inherit from object, which gives you access to more goodies)
>>> print(type(Classy))
<type 'classobj'>
Now, if you created an instance of classy, and put an inty value to it, you would have:
class Classy():
inty = 3
stringy = "whatevs"
def __init__(self):
self.inty = 5
classy = Classy()
print("__dict__ of instance: %s" % classy.__dict__)
print("__dict__ of Class: %s" % classy.__class__.__dict__)
Which outputs
__dict__ of instance: {'inty': 5}
__dict__ of Class: {'__module__': '__main__', 'inty': 3, '__doc__': None, '__init__': <function __init__ at 0x1080de410>, 'stringy': 'whatevs'}
See the inty being 5 in the __dict__ of the instance but still being 3 in the __dict__ of the class? It's because now you have two inty: One attached to classy, an instance of the class Classy and another one attached to the class Classy itself (which is, in turn, an instance of classobj)
If you did
classy = Classy()
print(classy.inty)
print(classy.stringy)
You'd see:
5
whatevs
Why? Because when you try to get inty on the instance, Python will look for it in the __dict__ of the instance first. If it doesn't find it, it will go to the __dict__ of the class. That is what's happening on classy.stringy. Is it in the classy instance? Nopes. Is it in the Classy class? Yep! Aight, return that one... And that's the one you see.
Also, I mentioned that the Classy class is an object, right? And as such, you can assign it to something else like this:
What = Classy # No parenthesis
foo = What()
print(foo.inty)
And you'll see the 5 that was "attached" in Classy.__init__ because when you did What = Classy, you're assigning the class Classy to a variable named What, and when you do foo=What() you're actually running the constructor of Classy (remember: What and Classy are the same thing)
Another thing Python allows (and that I personally don't like because then it makes code very difficult to follow) is attaching attributes to instances "on-the-fly":
classy = Classy()
try:
print(classy.other_thing)
except AttributeError:
print("Oh, dang!! No 'other_thing' attribute!!")
classy.other_thing = "hello"
print(classy.other_thing)
Will output
Oh, dang!! No 'other_thing' attribute!!
hello
Oh, and did I say that functions are objects? Yeah, they are... and as such, you can also assign attributes to them (also, something that makes code really, really confusing) but you could do it...
def foo_function():
return None # Very dummy thing we're doing here
print("dict of foo_function=%s" % foo_function.__dict__)
foo_function.horrible_thing_to_do = "http://www.nooooooooooooooo.com/"
print("Horrible thing? %s" % foo_function.horrible_thing_to_do)
Outputs:
dict of foo_function={}
Horrible thing? http://www.nooooooooooooooo.com/