Является ли хорошей практикой объявлять переменные экземпляра как 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.

Какой из них будет лучше практиковать и по каким причинам?


63
Никогда не позволяйте вашей IDE диктовать, какой код вы пишете?
Мартин Питерс

14
Между прочим: использование надлежащей Python IDE (например, 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
Немного похоже на строки документации ;-) Конечно, в Python есть удобный режим для удаления тех -OO, для тех немногих, кто в этом нуждается.
Стив Джессоп

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

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

3
На самом деле, если бы вы были таким параноиком по поводу космоса, вы бы заметили, что это экономит пространство и тратит время. Неинициализированные члены переходят на использование значения класса, и, следовательно, нет набора копий имени переменной, сопоставленных с None (по одному для каждого экземпляра). Таким образом, стоимость составляет несколько байтов в классе, а экономия составляет несколько байтов на экземпляр. Но каждый неудачный поиск по имени переменной стоит совсем немного времени.
Джон Джей Обермарк

2
Если бы вы беспокоились о 8 байтах, вы бы не использовали Python.
cz

26

1. Сделайте ваш код легким для понимания

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

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

Поэтому я обычно не только определяю все в конструкторе, но и стараюсь свести к минимуму количество изменяемых атрибутов.

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
Да, но смешивание членов на уровне класса и на уровне экземпляра - это почти то, что делает def . Он создает функции, которые мы считаем атрибутами объектов, но на самом деле являемся членами класса. То же самое относится к собственности и тому подобное. Создание иллюзии, что работа принадлежит объектам, когда она действительно опосредована классом, является проверенным временем способом не сходить с ума. Как все может быть так плохо, если это нормально для самого Python.
Джон Джей Обермарк

Ну, порядок разрешения методов в Python (также применимый к элементам данных) не очень прост. То, что не найдено на уровне экземпляра, будет искать на уровне класса, затем среди базовых классов и т. Д. Вы действительно можете скрыть элемент уровня класса (данные или метод), назначив член уровня экземпляра с тем же именем. Но методы уровня экземпляра будут связаны с экземпляра selfи не нужно selfбыть переданы, в то время как методы класса уровня являются несвязанными и простые функции , как видно на defвремя, и принимает экземпляр в качестве первого аргумента. Так что это разные вещи.
9000

Я думаю, что это AttributeErrorхороший сигнал, чем ошибка. В противном случае вы бы проглотили None и получили бы бессмысленные результаты. Это особенно важно в данном случае, когда атрибуты определены в __init__, поэтому отсутствующий атрибут (но существующий на уровне класса) может быть вызван только ошибочным наследованием.
Davidmh

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

Веселье! ракеты! в любом случае, я почти уверен, что можно делать переменные уровня класса и смешивать их ... до тех пор, пока уровень класса содержит значения по умолчанию и т. д.
Erik Aronesty

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, но не для большинства значений. Так что ничего не меняй об этом. ;)
Remco Haszing

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

6
@Benedict Python не имеет контроля доступа. Главное подчеркивание - принятая конвенция для деталей реализации. Смотри PEP 8 .
Довал

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'

Мне было бы трудно назвать это «причиной ошибки». Это неоднозначно, что вы подразумеваете под запросом «х», но вы вполне можете захотеть получить наиболее вероятное начальное значение.
Джон Джей Обермарк

Я должен был прояснить, что это может вызвать ошибку, если используется неправильно.
Дениф

Более высокий риск все еще инициализируется для объекта. Никто на самом деле не достаточно безопасен. Единственная ошибка, которую он собирается вызвать, - это проглочение действительно глупых исключений.
Джон Джей Обермарк

1
Неспособность вызвать ошибки - проблема, если ошибки предотвратили бы более серьезные проблемы.
Эрик Аронести

0

Я пойду с «немного похожими на строки документации» и объявлю это безвредным, если оно всегдаNone или узкий диапазон других значений неизменны.

Он пахнет атавизмом и чрезмерной привязанностью к статически типизированным языкам. И это не так хорошо, как код. Но это имеет незначительную цель, которая остается в документации.

Он документирует ожидаемые имена, поэтому, если я объединю код с кем-то, и у одного из нас будет «username», а у другого user_name, есть подсказка людям, что мы расстались и не используем одни и те же переменные.

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

Очевидно, что БОЛЬШАЯ проблема здесь в том, что это побуждает людей инициализироваться со значениями, отличными от None, что может быть плохо:

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

оставляет глобальный след и не создает экземпляр для x.

Но это больше причуды в Python в целом, чем в этой практике, и мы уже должны быть параноиками по этому поводу.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.