Синтаксис класса Python - это хорошая идея? - PullRequest
4 голосов
/ 22 февраля 2010

У меня возникает желание определить мои классы Python следующим образом:

class MyClass(object):
    """my docstring"""

    msg = None
    a_variable = None
    some_dict = {}

    def __init__(self, msg):
        self.msg = msg

Объявление переменных объекта (msg, a_variable и т. Д.) Вверху, как Java, хорошо, плохо или безразлично? Я знаю, что в этом нет необходимости, но все же соблазнительно это сделать.

Ответы [ 4 ]

8 голосов
/ 22 февраля 2010

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

class MyClass(object):
    msg = "ABC"

print MyClass.msg     #prints ABC
a = MyClass()
print a.msg           #prints ABC
a.msg = "abc"
print a.msg           #prints abc
print MyClass.msg     #prints ABC
print a.__class__.msg #prints ABC

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

Один из недостатков использования этого метода в том, что он может привести к ошибкам, так как добавляет скрытое состояние в класс. Скажем, кто-то исключил self.msg = "ABC" из конструктора (или, более реалистично, код был подвергнут рефакторингу и изменено только одно из определений)

a = MyClass()
print a.msg   #prints ABC

#somewhere else in the program
MyClass.msg = "XYZ"

#now the same bit of code leads to a different result, despite the expectation that it
#leads to the same result.
a = MyClass()
print a.msg   #prints XYZ

Гораздо лучше избегать определения сообщений на уровне класса, а затем избегать проблем:

class MyClass(object):
    pass

print MyClass.msg #AttributeError: type object 'MyClass' has no attribute 'msg'
6 голосов
/ 22 февраля 2010

Объявление переменных непосредственно внутри определения класса делает их переменными класса вместо переменных экземпляра. Переменные класса в некоторой степени похожи на статические переменные в Java и должны использоваться как MyClass.a_variable. Но они также могут использоваться как self.a_variable, что является проблемой, потому что наивные программисты могут рассматривать их как переменные экземпляра. Например, ваша переменная "some_dict" будет совместно использоваться каждым экземпляром MyClass, поэтому, если вы добавите к нему ключ "k", это будет видно любому экземпляру.

Если вы всегда помните о переопределении переменных класса, то практически нет разницы с переменными экземпляра. Только первоначальное определение в MyClass останется. Но в любом случае, это не очень хорошая практика, так как вы можете столкнуться с проблемами, если не назначить эти переменные заново!

Лучше напиши класс так:

class MyClass(object):
    """
    Some class
    """

    def __init__(self, msg):
        self.__msg = msg
        self.__a_variable = None
        self.__some_dict = {}

Использование двух символов подчеркивания для «частных» переменных ( псевдоприватное! ) необязательно. Если переменные должны быть открытыми, просто сохраните их имена без префикса __.

4 голосов
/ 22 февраля 2010

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

class MyClass(object):    
    msg = 'FeeFiFoFum'   
    def __init__(self, msg):
        self.msg = msg

m=MyClass('Hi Lucy')

Обратите внимание, что у нас есть 'Hi Lucy' в качестве значения.

print(m.__dict__)
# {'msg': 'Hi Lucy'}

Обратите внимание, что в диктовке MyClass (доступ через m.__class__) по-прежнему FeeFiFoFum.

print(m.__class__.__dict__)
# {'__dict__': <attribute '__dict__' of 'MyClass' objects>, '__module__': '__main__', '__init__': <function __init__ at 0xb76ea1ec>, 'msg': 'FeeFiFoFum', 'some_dict': {}, '__weakref__': <attribute '__weakref__' of 'MyClass' objects>, '__doc__': 'my docstring', 'a_variable': None}

Другой (возможно, более простой) способ увидеть это:

print(m.msg)
# Hi Lucy
print(MyClass.msg)
# FeeFiFoFum
1 голос
/ 22 февраля 2010

Когда вы объявляете класс, Python проанализирует его код и поместит все в пространство имен класса; тогда класс будет использоваться в качестве своего рода шаблона для всех объектов, производных от него, - но любой объект будет иметь свою собственную копию ссылки.
Обратите внимание, что у вас всегда есть ссылка; таким образом, если вы сможете изменить указанный объект, это изменение отразится на всех местах, где оно используется. Однако слот для данных-членов уникален для каждого экземпляра, и, следовательно, назначение его новому объекту не будет отражаться ни в каком другом месте, где он используется.

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

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

  1. Имя уже доступно на уровне класса, и вы можете использовать его без создания нового объекта; это может показаться изящным для объявления констант в пространствах имен, но во многих случаях имя модуля уже может быть хорошим.
  2. Имя добавляется на уровне класса - это означает, что вы не сможете легко смоделировать его во время модульных тестов и что, если у вас есть дорогостоящая операция, вы получите ее в самый момент импорта.

Обычно, просматривая код, я вижу членов, объявленных на уровне класса с небольшим подозрением; Есть много хороших вариантов использования для них, но также вполне вероятно, что они есть как привычка из предыдущего опыта работы с другими языками программирования.

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