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

StackOverflow https://stackoverflow.com/questions/207000

Вопрос

Существует ли какое-либо значимое различие между:

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

против.

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

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

Это было полезно?

Решение

Помимо соображений производительности, существует значительный семантический разница.В случае атрибута класса имеется ссылка только на один объект.В параметре instance-attribute-set-at-instantiation может быть несколько объектов, на которые ссылаются.Например

>>> 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    
[]

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

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

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

Вот очень хороший Публикация, и резюмируйте это, как показано ниже.

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 = MyClass(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

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

enter image description here

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

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

    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
    

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

Тот факт, что 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, это для того, чтобы избежать столкновения с двумя совершенно другими проблемами, в которые я не хочу здесь вдаваться;по двум разным причинам, совершенно раздельно созданным 5s может в конечном итоге быть одним и тем же экземпляром number 5.Но совершенно отдельно созданный object()я не могу.)


Итак, почему же это происходит 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.

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

Есть еще одна ситуация.

Атрибуты класса и экземпляра являются Дескриптор.

# -*- encoding: utf-8 -*-


class RevealAccess(object):
    def __init__(self, initval=None, name='var'):
        self.val = initval
        self.name = name

    def __get__(self, obj, objtype):
        return self.val


class Base(object):
    attr_1 = RevealAccess(10, 'var "x"')

    def __init__(self):
        self.attr_2 = RevealAccess(10, 'var "x"')


def main():
    b = Base()
    print("Access to class attribute, return: ", Base.attr_1)
    print("Access to instance attribute, return: ", b.attr_2)

if __name__ == '__main__':
    main()

Выше будет выведен:

('Access to class attribute, return: ', 10)
('Access to instance attribute, return: ', <__main__.RevealAccess object at 0x10184eb50>)

Один и тот же тип доступа к экземпляру через класс или инстанс возвращает разный результат!

И я нашел в c.Определение PyObject_GenericGetAttr, и отличный Публикация.

Объясните

Если атрибут найден в словаре классов, которые составляют.объекты MRO, затем проверьте, указывает ли искомый атрибут на дескриптор данных (который является не чем иным, как классом, реализующим как __get__ и тот __set__ методы).Если это произойдет, разрешите поиск атрибута, вызвав __get__ метод дескриптора данных (строки 28-33).

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top