Reputation: 1645
I see this was flagged as a duplicate of "What is the difference between class and instance variables?" However I don't believe I'm using any instance variables in my example, neither of my classes has a __init__
I'm editing class variables in two different ways and trying to understand the difference between them, not the difference between a class and instance variable.
I'm trying to understand the difference between calling a class variable just with .var
and with .__class__.var
. I thought it was to do with subclassing so I wrote the following code.
class Foo:
foo = 0
class Bar(Foo):
bar = 1
def print_class_vals(f, b):
""" prints both instant.var and instant.__class__.var for foo and bar"""
print("f.foo: {}, {} "
"b.foo: {}, {} "
"b.bar: {}, {} "
"".format(f.foo, f.__class__.foo,
b.foo, b.__class__.foo,
b.bar, b.__class__.bar))
f = Foo()
b = Bar()
print_class_vals(f, b)
Foo.foo += 1
print_class_vals(f, b)
Bar.foo += 1
print_class_vals(f, b)
Bar.bar += 1
print_class_vals(f, b)
This outputs the following:
f.foo: 0, 0, b.foo: 0, 0, b.bar: 1, 1
f.foo: 1, 1, b.foo: 1, 1, b.bar: 1, 1
f.foo: 1, 1, b.foo: 2, 2, b.bar: 1, 1
f.foo: 1, 1, b.foo: 2, 2, b.bar: 2, 2
I can't seem to find any difference between calling inst.var
and inst.__class__.var
. How are they different and when should I use one over the other?
Upvotes: 1
Views: 137
Reputation: 365617
While Gabriel Reis's answer explains this particular situation perfectly, there actually is a difference between f.foo
and f.__class__.foo
even if foo
isn't shadowed by an instance attribute.
Compare:
>>> class Foo:
... foo = 1
... def bar(self): pass
... baz = lambda self: None
>>> f = Foo()
>>> f.foo
1
>>> f.__class__.foo
1
>>> f.bar
<bound method Foo.bar of <__main__.Foo object at 0x11948cb00>>
>>> f.__class__.bar
<function __main__.Foo.bar(self)>
>>> f.bar()
>>> f.__class__.bar()
TypeError: bar() missing 1 required positional argument: 'self'
And the same is true for f.baz
.
The difference is that, by directly accessing f.__class__.foo
, you're making an end-run around the descriptor protocol, which is the thing that makes methods, @property
, and similar things work.
If you want the full details, read the linked HOWTO, but the short version is that there's a bit more to it than Gabriel's answer says:
Python will look up for a name (attribute) first in the instance namespace/dict. If it doesn't find there, then it will look up in the class namespace. If it still doesn't find there, then it will walk through the base classes respecting the MRO (method resolution order).
But if it finds it in the class namespace (or any base class), and what it finds is a descriptor (a value with a __get__
method), it does an extra step. The details depend on whether it's a data or non-data descriptor (basically, whether it also has a __set__
method), but the short version is that instead of giving you the value, it calls __get__
on the value and gives you what that value returns. Functions have a __get__
method that returns a bound method; properties have a __get__
method that calls the property get method; etc.
Upvotes: 2
Reputation: 79
Python will look up for a name (attribute) first in the instance namespace/dict. If it doesn't find there, then it will look up in the class namespace. If it still doesn't find there, then it will walk through the base classes respecting the MRO (method resolution order).
What you've done there, is to define the class attributes Foo.foo
and Bar.bar
.
You never modified any instance namespace.
Try this:
class Foo:
foo = 1
f = Foo()
f.foo = 2
print('f.foo = {!r}'.format(f.foo))
print('f.__class__.foo = {!r}'.format(f.__class__.foo))
And you will be able to understand the difference.
Upvotes: 2