Змінні екземпляра проти змінних класів в Python


120

У мене є класи 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

4
Прочитавши це запитання і побачивши відповідь, одне з моїх перших запитань було: "Отже, як я отримую доступ до змінних класу?" - це тому, що до цього моменту я використовував лише змінні екземпляри. Відповідаючи на моє власне запитання, ви робите це через саме ім'я класу, хоча технічно це можна зробити і через екземпляр. Ось посилання для читання для всіх, хто має те саме запитання: stackoverflow.com/a/3434596/4561887
Габріель Штаплес

Відповіді:


158

Якщо у вас все-таки є лише один екземпляр, найкраще робити всі змінні за примірником, просто тому, що до них буде доступно (трохи) швидше (один менший рівень "пошуку" через "успадкування" від класу до інстанції), і немає жодних недоліків, які можна зважити на цю невелику перевагу.


7
Ніколи не чув про зразок Борга? Лише мати один екземпляр був неправильним способом мати його в першу чергу.
Девін Жанп'єр

434
@ Девін, так, я чув про схему Борга, оскільки я її представив (у 2001 р., Cfr code.activestate.com/recipes/… ;-). Але немає нічого поганого, у простих випадках - просто мати єдиний екземпляр без примусового виконання.
Алекс Мартеллі

2
@ user1767754, легко зробити їх самостійно, python -mtimeit- але щойно зробив це в python3.4. Зауважу, що доступ до intзмінної класу насправді приблизно на 5 - 11 наносекунд швидший, ніж та сама змінна інстанція на моїй старій робочій станції - не впевнений, що codepath робить це так.
Алекс Мартеллі

45

Подальше відлуння поради Майка та Алекса та додавання мого власного кольору ...

Використання атрибутів екземпляра є типовим ... більш ідіоматичним Python. Атрибути класу використовуються не так сильно, оскільки випадки їх використання є специфічними. Те саме стосується статичних та класових методів порівняно з "нормальними" методами. Це спеціальні конструкції, що стосуються конкретних випадків використання, інакше це код, створений непристойним програмістом, який хоче показати, що вони знають якийсь незрозумілий куточок програмування Python.

У своїй відповіді Алекс зазначає, що доступ буде (трохи) швидшим завдяки одному меншому рівню пошуку ... Дозвольте мені уточнити для тих, хто ще не знає, як це працює. Він дуже схожий на змінний доступ - порядок пошуку якого:

  1. місцеві жителі
  2. нелокальні
  3. глобалів
  4. вбудовані

Для доступу до атрибутів порядок:

  1. екземпляр
  2. клас
  3. базові класи, визначені MRO (порядок роздільної здатності методу)

Обидві методи працюють "зсередини", тобто більшість локальних об'єктів перевіряються спочатку, потім зовнішні шари перевіряються послідовно.

У вашому прикладі вище, скажімо, ви шукаєте pathатрибут. Коли він зустрічає посилання типу " self.path", Python спочатку розгляне атрибути екземпляра для відповідності. Коли це не вдається, він перевіряє клас, з якого об'єкт був створений. Нарешті, він шукатиме базові класи. Як зазначив Алекс, якщо ваш атрибут знайдений в екземплярі, його не потрібно шукати в іншому місці, отже, і ваша невелика економія часу.

Однак якщо ви наполягаєте на атрибутах класу, вам потрібен додатковий пошук. Або іншою вашою альтернативою є посилання на об'єкт через клас замість екземпляра, наприклад, MyController.pathзамість self.path. Це прямий пошук, який обійде відкладений пошук, але, як згадує Алекс нижче, це глобальна змінна, і ви втрачаєте той біт, який ви думали, що збираєтеся зберегти (якщо ви не створите локальну посилання на ім'я [глобального] класу ).

Суть полягає в тому, що ви повинні використовувати атрибути екземплярів більшу частину часу. Однак трапляться випадки, коли атрибут класу є правильним інструментом для роботи. Код, використовуючи обидва одночасно, вимагатиме максимальної ретельності, оскільки використання selfлише отримує об’єкт атрибута екземпляра та тініє доступ до однойменного атрибута класу. У цьому випадку для використання посилання на нього потрібно використовувати атрибут доступу за назвою класу.


@wescpy, але MyControllerшукаються в глобал, так що загальна вартість вище , ніж self.pathде pathє змінним екземпляром (так як selfце локальний до методу == супер-швидкий пошук).
Алекс Мартеллі

ах, правда. хороший улов. Я здогадуюсь, єдине рішення - це створити локальну довідку ... на даний момент це насправді не варто.
wescpy

24

Коли ви сумніваєтесь, ви, ймовірно, хочете атрибут примірника.

Атрибути класу найкраще зарезервувати для особливих випадків, коли вони мають сенс. Єдиний дуже поширений випадок використання - це методи. Не рідкість використання атрибутів класу для констант лише для читання, про які повинні знати екземпляри (хоча єдиною вигодою для цього є те, якщо ви також хочете отримати доступ поза межами класу), але вам, безумовно, слід бути обережними щодо зберігання будь-якого стану в них , що рідко хочеться. Навіть якщо у вас буде лише один екземпляр, ви повинні написати клас, як і будь-який інший, що зазвичай означає використання атрибутів екземпляра.


1
Змінні класу є своєрідними константами лише для читання. Якби Python дозволив мені визначити константи, я б написав це як константи.
Діамон

1
@deamon, я трохи більше шансів поставити свої константи повністю поза визначеннями класу і назвати їх усіма великими літерами. Помістити їх всередину класу теж непогано. Зробити їх атрибутами екземплярів нічого не зашкодить, але може бути трохи дивним. Я не думаю, що це питання, коли громада занадто сильно затримує один із варіантів.
Майк Грехем,

@MikeGraham FWIW, керівництво Google щодо стилю Python пропонує уникати глобальних змінних на користь змінних класу. Однак є винятки.
Денніс

Ось нове посилання на керівництво Google щодо стилю Python . Тепер написано просто: avoid global variablesі їх визначення полягає в тому, що глобальні змінні є також змінними, які оголошуються атрибутами класу. Однак власне керівництво стилем Python ( PEP-8 ) має стати першим місцем для запитань подібного роду. Тоді ваш власний розум має стати інструментом вибору (звичайно, ви також можете отримати ідеї від Google, наприклад).
коллідір

4

Те саме питання щодо продуктивності доступу до змінних класу в Python - код тут адаптований від @Edward Loper

Локальні змінні - це найшвидший доступ до них, зв'язаний із модульними змінними, за ними - Класові змінні, за ними - інстанції.

Існує 4 сфери, до яких можна отримати доступ до змінних:

  1. Змінні інстанції (self.varname)
  2. Змінні класи (Classname.varname)
  3. Змінні модуля (VARNAME)
  4. Локальні змінні (varname)

Тест:

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.