Атрибуты класса Python и модуля
Мне интересно услышать некоторое обсуждение атрибутов класса в Python. Например, что является хорошим вариантом использования атрибутов класса? По большей части я не могу придумать случай, когда атрибут класса предпочтительнее использовать атрибут уровня модуля. Если это так, то почему они вокруг?
Проблема, с которой я сталкиваюсь, заключается в том, что по ошибке слишком сложно сбрасывать значение атрибута класса, а затем ваше "глобальное" значение превратилось в атрибут локального экземпляра.
Не забудьте прокомментировать, как бы вы справились со следующими ситуациями:
- Константные значения, используемые классом и/или подклассами. Это может включать в себя словарные клавиши "магического номера" или индексы списков, которые никогда не будут меняться, но возможно потребуется однократная инициализация.
- Атрибут класса по умолчанию, который в редких случаях обновляется для специального экземпляра класса.
- Глобальная структура данных, используемая для представления внутреннего состояния класса, общего для всех экземпляров.
- Класс, который инициализирует ряд атрибутов по умолчанию, не подверженных влиянию аргументов конструктора.
Некоторые похожие сообщения:
Разница между атрибутами класса и экземпляра
Ответы
Ответ 1
# 4:
Я никогда не использую атрибуты класса для инициализации атрибутов экземпляра по умолчанию (те, которые вы обычно помещаете в __init__
). Например:
class Obj(object):
def __init__(self):
self.users = 0
и никогда:
class Obj(object):
users = 0
Почему? Поскольку это непоследовательно: он не делает то, что вам нужно, когда вы назначаете что-либо, кроме объекта-инварианта:
class Obj(object):
users = []
приводит к тому, что список пользователей будет использоваться для всех объектов, которые в этом случае не нужны. Сложно разбить их на атрибуты классов и назначения в __init__
в зависимости от их типа, поэтому я всегда ставил их все в __init__
, которые я все равно нахожу.
Что касается остальных, я обычно помещаю значения класса в класс. Это не так много, потому что глобальные "злые" - они не такие большие, как на некоторых языках, потому что они все еще привязаны к модулю, если только сам модуль слишком велик, - но если внешний код хочет получить к ним доступ, удобно иметь все соответствующие значения в одном месте. Например, в module.py:
class Obj(object):
class Exception(Exception): pass
...
а затем:
from module import Obj
try:
o = Obj()
o.go()
except o.Exception:
print "error"
Помимо того, что подклассы могут изменять значение (которое не всегда требуется в любом случае), это означает, что мне не нужно с трудом импортировать имена исключений и кучу других вещей, необходимых для использования Obj. "из импорта модуля Obj, ObjException,..." становится утомительно быстро.
Ответ 2
что является хорошим вариантом использования атрибутов класса
Случай 0. Методы класса - это только атрибуты класса. Это не просто техническое сходство - вы можете получить доступ и модифицировать методы класса во время выполнения, назначив для них вызывающие вызовы.
Случай 1. Модуль может легко определить несколько классов. Разумно инкапсулировать все о class A
в A...
и все о class B
в B...
. Например,
# module xxx
class X:
MAX_THREADS = 100
...
# main program
from xxx import X
if nthreads < X.MAX_THREADS: ...
Случай 2. Этот класс имеет множество атрибутов по умолчанию, которые могут быть изменены в экземпляре. Здесь способность оставить атрибут "глобальным дефолтом" является функцией, а не ошибкой.
class NiceDiff:
"""Formats time difference given in seconds into a form '15 minutes ago'."""
magic = .249
pattern = 'in {0}', 'right now', '{0} ago'
divisions = 1
# there are more default attributes
Один создает экземпляр NiceDiff для использования существующего или слегка измененного форматирования, но локализатор на другом языке подклассы класса для реализации некоторых функций принципиально иным способом и переопределяет константы:
class Разница(NiceDiff): # NiceDiff localized to Russian
'''Из разницы во времени, типа -300, делает конкретно '5 минут назад'.'''
pattern = 'через {0}', 'прям щас', '{0} назад'
Ваши дела:
- константы - да, я помещаю их в класс. Странно сказать
self.CONSTANT = ...
, поэтому я не вижу большого риска для их скрещивания.
- Атрибут по умолчанию - смешанный, как указано выше, может перейти в класс, но также может перейти в
__init__
в зависимости от семантики.
- Глобальная структура данных --- переходит к классу, если класс только, но может также перейти в модуль, в любом случае должен быть очень хорошо документирован.
Ответ 3
Атрибуты класса часто используются для переопределения значений по умолчанию в подклассах. Например, BaseHTTPRequestHandler имеет константы класса sys_version и server_version, последний по умолчанию - "BaseHTTP/" + __version__
. SimpleHTTPRequestHandler переопределяет server_version на "SimpleHTTP/" + __version__
.
Ответ 4
Инкапсуляция - хороший принцип: когда атрибут находится внутри класса, к которому он относится, вместо того, чтобы находиться в глобальной области, это дает дополнительную информацию людям, читающим код.
В ваших ситуациях 1-4 я избегал бы глобальных глобалов, насколько могу, и предпочитаю использовать атрибуты класса, которые позволяют использовать инкапсуляцию.