В чем разница между атрибутами класса и экземпляра? - PullRequest
126 голосов
/ 16 октября 2008

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

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

против

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

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

Ответы [ 6 ]

138 голосов
/ 16 октября 2008

Помимо соображений производительности, существует существенная семантическая разница. В случае атрибута класса есть только один упомянутый объект. В 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    
[]
36 голосов
/ 16 октября 2008

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

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

26 голосов
/ 07 декабря 2015

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

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
    
19 голосов
/ 30 октября 2014

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

Тот факт, что Алекс присваивает значение изменяемому типу, например списку, не имеет никакого отношения к тому, являются ли объекты общими или нет. Это можно увидеть с помощью функции 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 могут оказаться тем же самым экземпляром числа 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 значения не хранятся в переменных. Значения живут в value-land, сами по себе, переменные - это просто имена для значений, а присваивание просто создает новое имя для значения. Если это помогает, думайте о каждой переменной Python как shared_ptr<T> вместо T.

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

2 голосов
/ 16 октября 2008

Просто уточнение того, что сказал Алекс Ковентри, еще один Алекс (Мартелли) задал похожий вопрос в группе новостей comp.lang.python много лет назад. Он исследует семантическое различие между тем, что задумал человек, и тем, что он получил (используя переменные экземпляра).

http://groups.google.com/group/comp.lang.python/msg/5914d297aff35fae?hl=en

0 голосов
/ 22 июня 2017

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...