Давно уже сталкиваюсь с одним неудобством в повседневной работе с джангой.
Например у нас есть моделька:
class Entry( models.Model ):
title = models.CharField( max_length = 150 )
type = <...>
И каким полем выразить тип (да, знаю, что имя конфликтует со встроенным, но тут это не принципиально)? "Ха!" - скажут некоторые. Да просто взять IntegerField и сделать типы целыми числами от 0 до сколько надо. Легко!
TYPES = ( ( 0, _( "inactive" ) ),
( 1, _( "active" ) ) )
class Entry( models.Model ):
title = models.CharField( max_length = 150 )
type = models.PositiveIntegerField( choices = TYPES )
Казалось бы, проблема решена. И этим можно пользоваться. Но, тут сразу начинаются неудобства.
Для того чтобы код не превращался в склад "магических чисел" надо добавить соответствующие константы, которые описывают эти два типа.
INACTIVE = 0
ACTIVE = 1
# или даже
INACTIVE, ACTIVE = range( 0, 2 )
потом можно пойти ещё дальше и выразить choices через них, чтобы было меньше дублирования
TYPES = ( ( INACTIVE, _( "inactive" ) ),
( ACTIVE, _( "active" ) ) )
Теперь можно вполне себе удобно использовать это хозяйство. И выборки делать и прочие проверки с присваиваниями
e = Entry.objects.get( name = "Спартак чемпион!", type = INACTIVE )
e.type = ACTIVE
e.save()
Но, это не удобно! Всё равно от повторений избавиться не удалось. Да и красоты мало. Конечно константы как и choices можно объявить в scope класса, чтобы было хоть какое-то логическая связь, но всё равно - так не интересно.
Потом, что будет если в просмотрите raw базу? Увидите там кучу строк, где в поле "тип" какие-то не понятные числа(ну для вас понятные, если вы этот код сравнительно недавно писали, а если давно?) значения. Что за ними скрывается?
Так, думаем дальше. новая идея приходит так же быстро как и первая - надо использовать строковые литералы для обозначения типа и сменить тип поля в модели на CharField. И пускай это чуть-чуть менее эффективно с точки зрения объема информации.
INACTIVE = "inactive"
ACTIVE = "active"
Зато мы решаем в миг проблему просмотра базы "из вне". Теперь поля получили более осмысленные значения. Но, не решили проблему "много букв".
Увы в питоне нет такой полезной фичи, как перечисления - enums. Которые есть например в С++.
Но если их нет, то почему бы нам их не сделать. Да, можно найти уже готовые, но мы пойдем своим путем! Будем писать сами и специально для нашего случая(использования для джанго моделей), пусть и немного упрощенно.
За несколько минут вот такой класс получился:
class Enum( object ):
def __init__( self, **kwargs ):
self.attrs = kwargs
for key, value in kwargs.iteritems():
setattr( self, key, key )
def __iter__(self):
return self.attrs.iteritems()
Теперь с его использованием, перепишем исходную модель:
class Entry( models.Model ):
# сразу занесем в scope
# создаем enum, в результате имена параметров станут значениями в базе,
# а значения самих параметров станут ярлыками(labels) для этих значений
types = Enum( inactive = _( "inactive" ),
active = _( "active" ) )
title = models.CharField( max_length = 150 )
type = models.CharField( max_length = 10, choices = types )
И пример использования:
e = Entry.objects.get( name = "Спартак чемпион!", type = Entry.types.inactive )
e.type = Entry.types.active
e.save()
Код стал логичнее и прозрачнее.
Что делать, если у нас уже legacy код со значениями в виде чисел? Не проблема - создадим небольшую модификацию исходного класса энума:
class CustomEnum( Enum ):
def __init__(self, **kwargs ):
self.attrs = dict( kwargs.itervalues() )
for key, pair in kwargs.iteritems():
setattr( self, key, pair[ 0 ] )
И соответственно изменим его создание
types = Enum( inactive = ( 0, _( "inactive" ) ),
active = ( 1, _( "active" ) ) )
Я этой идиомой уже некоторое время пользуюсь и пока очень доволен. Альтернативные реализации enum'ов в питоне можно найти например тут и тут. Но они не адаптированы для джанги, хотя более полно реализуют концепцию перечислений.
Надеюсь, вам понравилось:)
16 Комментариев к “Перечисления на службе добра”
-
Иван Сагалаев Май 12, 2008 в 02:22
-
Александр Кошелев Май 12, 2008 в 07:22
-
Максим Май 12, 2008 в 07:26
-
Александр Соловьёв Май 12, 2008 в 13:23
-
Иван Сагалаев Май 12, 2008 в 15:00
-
Yuri Baburov Май 12, 2008 в 20:26
-
Евгений Сизиков Май 13, 2008 в 17:01
-
Александр Кошелев Май 13, 2008 в 19:43
-
Igor Davydenko Май 14, 2008 в 12:23
-
anonymous Май 14, 2008 в 16:48
-
Igor Davydenko Май 14, 2008 в 17:17
-
anonymous Май 14, 2008 в 18:31
-
Александр Кошелев Май 15, 2008 в 13:24
-
Александр Кошелев Май 15, 2008 в 13:26
-
udaw Июнь 26, 2008 в 18:19
-
Александр Кошелев Июнь 27, 2008 в 01:01
