Переменные экземпляра и переменные класса в Python

121

У меня есть классы Python, из которых мне нужен только один экземпляр во время выполнения, поэтому было бы достаточно иметь атрибуты только один раз для каждого класса, а не для каждого экземпляра. Если будет более одного экземпляра (чего не произойдет), все экземпляры должны иметь одинаковую конфигурацию. Интересно, какой из следующих вариантов был бы лучше или более «идиоматическим» Python.

Переменные класса:

class MyController(Controller):

  path = "something/"
  children = [AController, BController]

  def action(self, request):
    pass

Переменные экземпляра:

class MyController(Controller):

  def __init__(self):
    self.path = "something/"
    self.children = [AController, BController]

  def action(self, request):
    pass
Deamon
источник
4
Прочитав этот вопрос и увидев ответ, одним из первых моих вопросов был: «Как мне получить доступ к переменным класса?» - это потому, что до этого момента я использовал только переменные экземпляра. Отвечая на мой собственный вопрос, вы делаете это через само имя класса, хотя технически вы можете сделать это и через экземпляр. Вот ссылка для чтения для всех, у кого есть такой же вопрос: stackoverflow.com/a/3434596/4561887
Габриэль Стейплз

Ответы:

159

Если в любом случае у вас есть только один экземпляр, лучше всего сделать все переменные для каждого экземпляра просто потому, что доступ к ним будет осуществляться (немного) быстрее (на один уровень «поиска» меньше из-за «наследования» от класса к экземпляру), и у этого небольшого преимущества нет недостатков.

Алекс Мартелли
источник
7
Никогда не слышали об узоре Борга? Только один экземпляр изначально был неправильным способом получить его.
Девин Жанпьер,
435
@Devin, да, я слышал о шаблоне Borg, так как я тот, кто представил его (в 2001 году, cfr code.activestate.com/recipes/… ;-). Но в простых случаях нет ничего плохого в том, чтобы просто иметь один экземпляр без принудительного применения.
Alex Martelli
2
@ user1767754, их легко сделать самостоятельно, python -mtimeitно только что сделав это в python3.4, я отмечаю, что доступ к intпеременной класса на самом деле примерно на 5-11 наносекунд быстрее, чем то же, что и переменная экземпляра на моей старой рабочей станции - не уверен, что codepath делает это так.
Alex Martelli
45

Повторяя совет Майка и Алекса и добавляя свой собственный цвет ...

Использование атрибутов экземпляра является типичным ... более идиоматическим Python. Атрибуты класса используются не так часто, поскольку их варианты использования специфичны. То же самое верно для статических методов и методов класса по сравнению с "обычными" методами. Это специальные конструкции, предназначенные для конкретных случаев использования, иначе это код, созданный ошибочным программистом, желающим показать, что он знает какой-то неясный угол программирования на Python.

Алекс упоминает в своем ответе, что доступ будет (немного) быстрее из-за на один уровень поиска меньше ... позвольте мне еще пояснить для тех, кто еще не знает, как это работает. Это очень похоже на доступ к переменным - порядок поиска:

  1. местные жители
  2. nonlocals
  3. глобалы
  4. Модульное

Для доступа к атрибутам порядок следующий:

  1. пример
  2. класс
  3. базовые классы, определенные MRO (порядок разрешения методов)

Оба метода работают «наизнанку», то есть сначала проверяются наиболее локальные объекты, а затем последовательно проверяются внешние слои.

В приведенном выше примере предположим, что вы ищете pathатрибут. Когда он встречает ссылку вроде " self.path", Python сначала проверяет атрибуты экземпляра на соответствие. Когда это не удается, он проверяет класс, из которого был создан объект. Наконец, он будет искать базовые классы. Как заявил Алекс, если ваш атрибут найден в экземпляре, его не нужно искать в другом месте, что позволяет сэкономить немного времени.

Однако, если вы настаиваете на атрибутах класса, вам понадобится дополнительный поиск. Или ваша другая альтернатива - ссылаться на объект через класс вместо экземпляра, например, MyController.pathвместо self.path. Это прямой поиск, который позволяет обойти отложенный поиск, но, как упоминает ниже Алекс, это глобальная переменная, поэтому вы теряете тот бит, который, как вы думали, собирались сохранить (если вы не создадите локальную ссылку на [глобальное] имя класса ).

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

wescpy
источник
@wescpy, но ищется MyControllerв глобальных объектах, поэтому общая стоимость выше, чем self.pathгде pathнаходится переменная экземпляра (поскольку selfявляется локальной для метода == сверхбыстрый поиск).
Alex Martelli
ах, правда. хороший улов. я думаю, единственный обходной путь - создать локальную ссылку ... на данный момент это не стоит того.
wescpy
24

В случае сомнений вам, вероятно, понадобится атрибут экземпляра.

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

Майк Грэм
источник
1
Переменные класса являются своего рода константами только для чтения. Если бы Python позволял мне определять константы, я бы записал это как константу.
deamon
1
@deamon, я с большей вероятностью вынесу свои константы полностью за пределы определений классов и назову их заглавными буквами. Помещение их внутри класса тоже нормально. Создание им атрибутов экземпляра ничего не повредит, но может быть немного странным. Я не думаю, что это проблема, из-за которой сообщество слишком сильно отстает от одного из вариантов.
Майк Грэм,
@MikeGraham FWIW, Руководство по стилю Python от Google предлагает избегать глобальных переменных в пользу переменных класса. Но есть исключения.
Деннис
Вот новая ссылка на Руководство по стилю Python от Google . Теперь там просто написано: avoid global variablesи их определение состоит в том, что глобальные переменные также являются переменными, объявленными как атрибуты класса. Тем не менее, собственное руководство по стилю Python ( PEP-8 ) должно быть первым местом, где можно найти вопросы такого рода. Тогда ваш собственный разум должен быть предпочтительным инструментом (конечно, вы также можете почерпнуть идеи, например, от Google).
colidyre
4

Тот же вопрос в производительности доступа к переменным класса в Python - код здесь адаптирован из @Edward Loper

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

Есть 4 области, из которых вы можете получить доступ к переменным:

  1. Переменные экземпляра (self.varname)
  2. Переменные класса (Classname.varname)
  3. Переменные модуля (VARNAME)
  4. Локальные переменные (имя_переменной)

Тест:

import timeit

setup='''
XGLOBAL= 5
class A:
    xclass = 5
    def __init__(self):
        self.xinstance = 5
    def f1(self):
        xlocal = 5
        x = self.xinstance
    def f2(self):
        xlocal = 5
        x = A.xclass
    def f3(self):
        xlocal = 5
        x = XGLOBAL
    def f4(self):
        xlocal = 5
        x = xlocal
a = A()
'''
print('access via instance variable: %.3f' % timeit.timeit('a.f1()', setup=setup, number=300000000) )
print('access via class variable: %.3f' % timeit.timeit('a.f2()', setup=setup, number=300000000) )
print('access via module variable: %.3f' % timeit.timeit('a.f3()', setup=setup, number=300000000) )
print('access via local variable: %.3f' % timeit.timeit('a.f4()', setup=setup, number=300000000) )

Результат:

access via instance variable: 93.456
access via class variable: 82.169
access via module variable: 72.634
access via local variable: 72.199
robertcollier4
источник