我看到这被标记为"类和实例变量之间的区别是什么?"但是我不相信我在我的示例中使用了任何实例变量,我的类都没有__init__
我以两种不同的方式编辑类变量并尝试理解差异在它们之间,不类和实例变量之间的区别。
我试图了解仅使用.var
和.__class__.var
调用类变量之间的区别。我认为这与子类化有关,所以我编写了以下代码。
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)
这输出以下内容:
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
我似乎无法在调用inst.var
和inst.__class__.var
之间找到任何区别。它们有何不同,我何时应该使用另一个?
答案 0 :(得分:2)
Python将首先在实例命名空间/ dict中查找名称(属性)。如果它没有找到,那么它将在类命名空间中查找。如果它仍然没有找到,那么它将遍历关于MRO的基类(方法解析顺序)。
您在那里所做的是定义类属性Bar.bar
和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))
。
您从未修改过任何实例名称空间。
试试这个:
{{1}}
你将能够理解其中的差异。
答案 1 :(得分:2)
尽管Gabriel Reis's answer完美地解释了这种特殊情况,但实际上 <{1}}和f.foo
之间存在差异,即使f.__class__.foo
isn& #39;被实例属性遮蔽。
比较
foo
>>> 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'
也是如此。
不同之处在于,通过直接访问f.baz
,您可以围绕descriptor protocol进行最终操作,这就是制作方法f.__class__.foo
和类似的事情有效。
如果您想了解完整的详细信息,请阅读链接的HOWTO,但简短版本的内容比Gabriel的回答更多:
Python将首先在实例命名空间/ dict中查找名称(属性)。如果它找不到,那么它将在类命名空间中查找。如果它仍然没有找到,那么它将遍历关于MRO的基类(方法解析顺序)。
但是如果它在类名称空间(或任何基类)中找到它,并且它找到的是描述符(具有@property
方法的值),它会执行额外的步骤。细节取决于它是数据还是非数据描述符(基本上,它是否 还有__get__
方法),但简短的版本是,而不是给你值,它在值上调用__set__
并为您提供该值返回的值。函数有__get__
方法,返回绑定方法;属性有__get__
方法,可以调用属性get方法;等