Переменные класса, разница между instance.var и instance .__ class __. Var

Я вижу, что это было помечено как дубликат "В чем разница между переменными класса и экземпляра?" Однако я не верю, что использую какие-либо переменные экземпляра в моем примере, ни один из моих классов не имеет __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. Чем они отличаются и когда лучше использовать одно перед другим?

Практически никогда не следует использовать __class__.

chepner 13.05.2018 00:15

@chepner, В верхнем ответе на дубликат в конце есть объяснение, в котором обсуждается, почему вы получаете такое же значение.

Stephen Rauch 13.05.2018 00:22

Нет абсолютно никакой разницы в доступе к переменной класса, если у вас нет переменной экземпляра, которая ее затеняет.

MisterMiyagi 13.05.2018 00:24

@MisterMiyagi На самом деле это неправда. Попробуйте это с помощью метода (или атрибута класса, имеющего значение функции, что одно и то же).

abarnert 13.05.2018 00:26
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
4
42
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Python сначала будет искать имя (атрибут) в пространстве имен экземпляра / dict. Если он там не найден, он будет искать в пространстве имен класса. Если он по-прежнему не находит там, он будет проходить через базовые классы, соблюдая MRO (порядок разрешения методов).

Что вы там сделали, так это определить атрибуты класса Foo.foo и 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))

И вы сможете понять разницу.

Это немного упрощено, но я думаю, что это достаточно для OP, поэтому я поддержал его, но написал другой ответ.

abarnert 13.05.2018 00:34
Ответ принят как подходящий

Хотя Ответ Габриэля Рейса прекрасно объясняет эту конкретную ситуацию, на самом деле существует является разница между f.foo и f.__class__.fooдаже если 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.

Разница в том, что, напрямую обращаясь к f.__class__.foo, вы завершаете работу вокруг протокол дескриптора, что и заставляет работать методы, @property и подобные вещи.

Если вы хотите получить полную информацию, прочитайте связанный HOWTO, но вкратце он говорит о том, что это немного больше, чем говорится в ответе Габриэля:

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

Но если он находит его в пространстве имен класса (или любом базовом классе) и находит дескриптор (значение с помощью метода __get__), он выполняет дополнительный шаг. Детали зависят от того, является ли это дескриптор данных или нет (в основном, имеет ли он также метод __set__), но краткая версия заключается в том, что вместо того, чтобы дать вам значение, он вызывает __get__ для значения и дает вам это значение. возвращается. Функции имеют метод __get__, который возвращает связанный метод; у свойств есть метод __get__, который вызывает метод получения свойства; и т.п.

Другие вопросы по теме