Чи є гарною практикою оголошувати змінні екземпляра як None у класі в Python?


68

Розглянемо наступний клас:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

Мої колеги, як правило, визначають це так:

class Person:
    name = None
    age = None

    def __init__(self, name, age):
        self.name = name
        self.age = age

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

Особисто я не люблю останнього, тому що немає сенсу, що клас має ці властивості None.

Яка з них буде кращою практикою та з яких причин?


62
Ніколи не дозволяйте вашому IDE диктувати, який код ви пишете?
Martijn Pieters

14
До речі: використовуючи належний IDE python (наприклад, PyCharm), встановлення атрибутів у __init__вже надає автозавершення тощо. Також використання Noneзапобігає IDE виводити кращий тип для атрибуту, тому краще замість цього використовувати розумний за замовчуванням (коли можливо).
Бакуріу

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

3
"Ніколи не дозволяйте IDE диктувати, який код ви пишете" - це дискусійне питання. Станом на Python 3.6 є анотації в рядку та typingмодуль, який дозволяє вам надати підказки IDE та лінеру, якщо така річ відмічає ваші фантазії ...
cz

1
Ці завдання на рівні класу не впливають на решту коду. Вони не впливають на self. Навіть якщо self.nameі self.ageне було призначено в __init__вони не будуть відображатися в разі self, вони з'являються тільки в класі Person.
jolvi

Відповіді:


70

Я називаю останню поганою практикою за правилом "це не робить те, що ти думаєш, що робить".

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


4
Трохи схожі на docstrings тоді ;-) Звичайно, Python має зручний режим для зняття тих -OO, для тих небагатьох, хто цього потребує.
Стів Джессоп

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

8
@delnan Я погоджуюся з тим, що розмір пам’яті записів є безглуздим, я більше думав, що гоф зайняв логічний / ментальний простір, зробивши інтроспективну налагодження, і таке потребує більшого читання та сортування, я б здогадався. I ~ LL
StarWeaver

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

2
Якби ви турбувалися про 8 байт, ви б не використовували Python.
cz

26

1. Зробіть свій код легким для розуміння

Код читається набагато частіше, ніж написаний. Зробіть полегшеним завдання Вашого коду (це може бути і ви наступного року).

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

Тому я зазвичай не лише визначаю все в конструкторі, але і прагну до мінімуму число змінних атрибутів.

2. Не змішуйте членів на рівні класу та примірника

Все, що ви визначаєте прямо в classдекларації, належить до класу і ділиться всіма примірниками класу. Наприклад, коли ви визначаєте функцію всередині класу, вона стає методом, однаковим для всіх примірників. Те саме стосується членів даних. Це абсолютно не схоже на атрибути екземпляра, які ви зазвичай визначаєте __init__.

Учасники даних на рівні класу найбільш корисні як константи:

class Missile(object):
  MAX_SPEED = 100  # all missiles accelerate up to this speed
  ACCELERATION = 5  # rate of acceleration per game frame

  def move(self):
    self.speed += self.ACCELERATION
    if self.speed > self.MAX_SPEED:
      self.speed = self.MAX_SPEED
    # ...

2
Так, але змішування рівня класу і рівня екземпляра членів досить багато , що Захист робить. Він створює функції, які ми вважаємо атрибутами об'єктів, але насправді є членами класу. Те саме стосується власності та її помилок. Дати ілюзію, що робота належить до предметів, коли вона дійсно опосередковується класом - це спосіб, який не зводиться з глузду. Як це може бути все так погано, якщо це добре для самого Python.
Джон Джей Обермарк

Що ж, порядок вирішення методу в Python (також застосовно до членів даних) не дуже простий. Те, що не знайдено на рівні екземпляра, шукатиметься на рівні класу, потім серед базових класів тощо. Дійсно, можна затінити члена рівня класу (дані або метод), призначивши одноіменного члена рівня екземпляра. Але методи на рівні екземпляра прив’язані до екземплярів selfі їх не потрібно selfпередавати, тоді як методи рівня класу є незв'язаними і є простими функціями, як їх бачили в той defчас, і приймають екземпляр як перший аргумент. Тож це різні речі.
9000,

Я думаю, що AttributeErrorце гарний сигнал, ніж помилка. В іншому випадку ви проковтнете Нічого і отримаєте безглузді результати. Особливо важливий у випадку, коли атрибути визначені в __init__, тому відсутність атрибутів (але існує на рівні класу) може бути викликана лише спадкоємною спадщиною.
Davidmh

@Davidmh: виявлена ​​помилка завжди краще, ніж виявлена ​​помилка! Я б сказав, що якщо вам потрібно створити атрибут, Noneі це значення не має сенсу на момент побудови екземпляра, у вас є проблеми в архітектурі і вам доведеться переосмислити життєвий цикл значення атрибута або його початкового значення. Зауважте, що рано визначаючи свої атрибути, ви можете виявити таку проблему ще до того, як ви написали решту класу, не кажучи вже про запуск коду.
9000,

Весело! ракети! у будь-якому разі, я впевнений, що це добре, щоб зробити параметри рівня класу та змішати їх… до тих пір, поки рівень класу містить за замовчуванням тощо.
Ерік Аронесті

18

Особисто я визначаю членів у методі __ init __ (). Я ніколи не замислювався над тим, щоб визначити їх у класі. Але те, що я завжди роблю: я запускаю всіх членів у методі __ init__, навіть тих, які не потрібні методу __ init__.

Приклад:

class Person:
    def __init__(self, name, age):
        self._name = name
        self._age = age
        self._selected = None

   def setSelected(self, value):
        self._selected = value

Я думаю, що важливо визначити всіх членів в одному місці. Це робить код більш читабельним. Чи це всередині __ init __ () чи зовні, це не так важливо. Але команді важливо взяти на себе більш-менш однаковий стиль кодування.

О, і ви можете помітити, що я коли-небудь додаю префікс "_" до змінних членів.


13
Ви повинні встановити всі значення в конструкторі. Якщо значення було встановлено в самому класі, воно розподілятиметься між екземплярами, що добре для None, але не для більшості значень. Тому нічого не змінюйте з цього приводу. ;)
Ремко Хашинг

4
Значення за замовчуванням для параметрів, а також можливість приймати довільні позиційні та ключові аргументи робить його набагато менш болісним, ніж можна було б очікувати. (І насправді можна делегувати конструкцію іншим методам або навіть окремим функціям, тож якщо вам справді потрібна багаторазова диспетчеризація, ви також можете це зробити).
Шон Віейра

6
@Benedict Python не має контролю доступу. Основним підкресленням є прийнята конвенція щодо детальної інформації про впровадження. Див. PEP 8 .
Doval

3
@Doval Існує надзвичайно вагома причина для префіксації імен атрибутів за допомогою _: Щоб вказати, що це приватне! (Чому так багато людей у ​​цій темі плутають або наполовину плутають Python з іншими мовами?)

1
Ах, значить, ви є одним із тих властивостей або функцій для всіх людей, які виявили взаємодію ... Вибачте, що не зрозуміли. Цей стиль мені завжди здається надмірним, але він має таке.
Джон Джей Обермарк

11

Це погана практика. Ці значення вам не потрібні, вони захаращують код, і вони можуть спричинити помилки.

Поміркуйте:

>>> class WithNone:
...   x = None
...   y = None
...   def __init__(self, x, y):
...     self.x = x
...     self.y = y
... 
>>> class InitOnly:
...   def __init__(self, x, y):
...     self.x = x
...     self.y = y
... 
>>> wn = WithNone(1,2)
>>> wn.x
1
>>> WithNone.x #Note that it returns none, no error
>>> io = InitOnly(1,2)
>>> InitOnly.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: class InitOnly has no attribute 'x'

Мені буде важко назвати це "причиною помилки". Неоднозначно, що ви маєте на увазі, запитуючи "x" тут, але ви, можливо, дуже хочете, щоб було найбільш ймовірно початкове значення.
Джон Джей Обермарк

Я мав би пояснити, що це може призвести до помилки при неправильному використанні.
Daenyth

Більш високий ризик все ще ініціалізується на об'єкт. Жоден насправді не є досить безпечним. Єдина помилка, яку він має спричинити, - це проковтнути справді кульгаві винятки.
Джон Джей Обермарк

1
Якщо не викликати помилок, це проблема, якщо помилки запобігли б більш серйозним проблемам.
Ерік Аронесті

0

Я піду з "трохи схожим на docstrings, тоді" і оголошу це нешкідливим, доки це завждиNone , або вузький діапазон інших значень, все незмінне.

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

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

Примушування повної ініціалізації як політики досягає того ж самого більш пітонічного способу, але якщо в коді є фактичний код __init__, це забезпечує більш чітке місце для документування використовуваних змінних.

Очевидно, що велика проблема тут полягає в тому, що вона спокушає людей ініціалізуватися з іншими значеннями None, що може бути погано:

class X:
    v = {}
x = X()
x.v[1] = 2

залишає глобальний слід і не створює екземпляр для x.

Але це більше химерність в Python в цілому, ніж у цій практиці, і ми вже повинні бути параноїчними щодо цього.

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