Вопрос-Ответ

What is the difference between class and instance attributes?

В чем разница между атрибутами класса и экземпляра?

Есть ли какое-либо значимое различие между:

class A(object):
foo = 5 # some default value

против.

class B(object):
def __init__(self, foo=5):
self.foo = foo

Если вы создаете много экземпляров, есть ли разница в производительности или требованиях к пространству для двух стилей? Когда вы читаете код, считаете ли вы, что значение двух стилей существенно отличается?

Переведено автоматически
Ответ 1

Существует существенное семантическое различие (помимо соображений производительности):


  • когда атрибут определен в экземпляре (что мы обычно и делаем), могут быть ссылки на несколько объектов. Каждый получает совершенно отдельную версию этого атрибута.

  • когда атрибут определен в классе, упоминается только один базовый объект, поэтому, если операции над разными экземплярами этого класса одновременно пытаются установить / (добавить / расширить / вставить / и т.д.) Атрибут, то:

    • если атрибут имеет встроенный тип (например, int, float, boolean, string), операции над одним объектом перезапишут (сгладят) значение

    • если атрибут имеет изменяемый тип (например, list или dict), мы получим нежелательную утечку.



Например:

>>> class A: foo = []
>>> a, b = A(), A()
>>> a.foo.append(5)
>>> b.foo
[5]
>>> class A:
... def __init__(self): self.foo = []
>>> a, b = A(), A()
>>> a.foo.append(5)
>>> b.foo
[]
Ответ 2

Разница в том, что атрибут класса является общим для всех экземпляров. Атрибут экземпляра уникален для этого экземпляра.

Если исходить из C ++, атрибуты класса больше похожи на статические переменные-члены.

Ответ 3

Вот очень хороший пост, и резюмируйте его, как показано ниже.

class Bar(object):
## No need for dot syntax
class_var = 1

def __init__(self, i_var):
self.i_var = i_var

## Need dot syntax as we've left scope of class namespace
Bar.class_var
## 1
foo = Bar(2)

## Finds i_var in foo's instance namespace
foo.i_var
## 2

## Doesn't find class_var in instance namespace…
## So look's in class namespace (Bar.__dict__)
foo.class_var
## 1

И в визуальной форме

введите описание изображения здесь

Назначение атрибутов класса


  • Если атрибут класса задан путем доступа к классу, он переопределит значение для всех экземпляров


      foo = Bar(2)
    foo.class_var
    ## 1
    Bar.class_var = 2
    foo.class_var
    ## 2


  • Если переменная класса устанавливается при обращении к экземпляру, она переопределяет значение только для этого экземпляра. Это по сути переопределяет переменную класса и превращает ее в переменную экземпляра, доступную интуитивно только для этого экземпляра.


      foo = Bar(2)
    foo.class_var
    ## 1
    foo.class_var = 2
    foo.class_var
    ## 2
    Bar.class_var
    ## 1


Когда бы вы использовали атрибут класса?


  • Хранение констант. Поскольку к атрибутам класса можно обращаться как к атрибутам самого класса, часто бывает приятно использовать их для хранения констант всего класса, специфичных для конкретного класса


      class Circle(object):
    pi = 3.14159

    def __init__(self, radius):
    self.radius = radius
    def area(self):
    return Circle.pi * self.radius * self.radius

    Circle.pi
    ## 3.14159
    c = Circle(10)
    c.pi
    ## 3.14159
    c.area()
    ## 314.159


  • Определение значений по умолчанию. В качестве тривиального примера мы могли бы создать ограниченный список (т. Е. Список, который может содержать только определенное количество элементов или меньше) и выбрать ограничение по умолчанию в 10 элементов


      class MyClass(object):
    limit = 10

    def __init__(self):
    self.data = []
    def item(self, i):
    return self.data[i]

    def add(self, e):
    if len(self.data) >= self.limit:
    raise Exception("Too many elements")
    self.data.append(e)

    MyClass.limit
    ## 10


Ответ 4

Поскольку люди в комментариях здесь и в двух других вопросах, помеченных как дублирующие, похоже, одинаково сбиты с толку по этому поводу, я думаю, стоит добавить дополнительный ответ поверх Алекса Ковентри.

Тот факт, что Alex присваивает значение изменяемого типа, например list , не имеет никакого отношения к тому, являются ли объекты общими или нет. Мы можем увидеть это с помощью id функции или is оператора:

>>> class A: foo = object()
>>> a, b = A(), A()
>>> a.foo is b.foo
True
>>> class A:
... def __init__(self): self.foo = object()
>>> a, b = A(), A()
>>> a.foo is b.foo
False

(Если вам интересно, почему я использовал object() вместо, скажем, 5, это для того, чтобы не столкнуться с двумя совершенно другими проблемами, в которые я не хочу здесь вдаваться; по двум разным причинам полностью раздельно созданные 5 s могут в конечном итоге оказаться одним и тем же экземпляром number 5. Но полностью созданные отдельно object() s не могут.)


Итак, почему это a.foo.append(5) в примере Алекса влияет b.foo, а a.foo = 5 в моем примере нет? Что ж, попробуйте a.foo = 5 в примере Алекса и обратите внимание, что это не влияет b.foo там тоже.

a.foo = 5 просто превращает a.foo в имя для 5. Это не влияет на b.foo или любое другое имя для старого значения, на которое a.foo раньше ссылались. * Немного сложно, что мы создаем атрибут экземпляра, который скрывает атрибут класса, ** но как только вы это получите, здесь не произойдет ничего сложного.


Надеюсь, теперь очевидно, почему Алекс использовал список: тот факт, что вы можете изменять список, означает, что проще показать, что две переменные называют один и тот же список, а также означает, что в реальном коде важнее знать, есть ли у вас два списка или два имени для одного и того же списка.


* Путаница для людей, знакомых с таким языком, как C ++, заключается в том, что в Python значения не хранятся в переменных. Значения существуют сами по себе, переменные - это просто имена для значений, а присваивание просто создает новое имя для значения. Если это поможет, думайте о каждой переменной Python как о shared_ptr<T> вместо T.

** Некоторые люди пользуются этим преимуществом, используя атрибут класса в качестве "значения по умолчанию" для атрибута экземпляра, которое экземпляры могут устанавливать, а могут и не устанавливать. Это может быть полезно в некоторых случаях, но также может сбивать с толку, поэтому будьте осторожны с этим.

python