Python

индекс
250,37

«Наследование» не от классов

image
В Питоне совсем не важно, что вы поместите в определение класса. Это могут быть строки, числа, объекты, переменные. В третьем Питоне можно даже передавать именованные аргументы.

Реализация


Сначала необходимо разочаровать: в Питоне нельзя наследовать не от классов, т.е. нельзя передавать конструктору классов type параметр bases, в котором есть неклассы. Однако нигде не оговорено, что вы будете включать в определение класса.
Ничего сложно в реализации кода выше нет, для этого просто потребуется создать метакласс, в котором будут отфильтровываться неклассы из bases (которые можно дальше использовать по своему усмотрению). Думаю, пример все объяснит, copy&run:
Copy Source | Copy HTML
  1. # coding: utf-8
  2.  
  3. class Metaclass(type):
  4.  
  5.     '''
        Metaclass печатает все объекты, от которых наследует класс
        и которые не являеются классами.
        '''
  6.  
  7.     def __new__(cls, name, bases, dict):
  8.         import inspect
  9.  
  10.         new_bases = []
  11.         for base in bases:
  12.             # отфильтруем классы,
  13.             # остальное просто напечатаем
  14.             if inspect.isclass(base):
  15.                 new_bases.append(base)
  16.             else:
  17.                 print base
  18.  
  19.         return type.__new__(cls, name, tuple(new_bases), dict)
  20.  
  21.  
  22. class Print(object):
  23.  
  24.     __metaclass__ = Metaclass
  25.  
  26.  
  27. class String(Print, 'Programming', 'is', 'all', 'about', 'architecture.', ''):
  28.     pass
  29.  
  30. class Numbers(Print,  0, 1, 2.718, 3.1459, ''):
  31.     pass
  32.  
  33. class More(Print, None, True, 'or', False, ['to be', 'or', 'not to be'], ''):
  34.     pass
  35.  
  36. the_end = 'The end.'
  37. class End(Print, the_end):
  38.     pass

Использование именованных аргументов в определении класса в третьем Питоне, copy&run:
Copy Source | Copy HTML
  1. # Python 3+
  2.  
  3. def metaclass(name, bases, dict, private=True):
  4.     print('Private:', private)
  5.     return type(name, bases, dict)
  6.  
  7.  
  8. class MyClass(object, metaclass=metaclass, private=False):
  9.  
  10.     pass


Практическая целесообразность


В большинстве случаев эта возможность вообще не требуется. Более того, код становится сложным и «магическим», что совсем не приветствуется. Однако в некоторых случаях она оказывается незаменима, как и метаклассы в целом. По сути, можно выделить два преимущества:
  1. Управление метаклассами и
  2. Упрощение API.

Управление конструкцией классов (и вообще метаклассами)

Передача параметров для метакласса непосредственно в определении класса. Конечно, можно передать те же самые параметры через атрибуты обычного класса, но это засоряет класс и не всегда выглядит читабельно. Например, пусть необходимо наследовать либо полный бокал, либо пустой (пример притянут за уши, простого и понятного не придумал, а взять из готового кода не получилось). Copy&run:
Copy Source | Copy HTML
  1. #coding: utf-8
  2.  
  3. class EmptyGlass: pass
  4. class FullGlass: pass
  5.  
  6.  
  7. class GlassMetaclass(type):
  8.  
  9.     '''
        GlassMetaclass создает либо класс EmptyGlass, либо FullGlass,
        в зависимости от того, было ли указано True или False в определении
        класса.
        '''
  10.  
  11.     empty_glass = EmptyGlass
  12.     full_glass = FullGlass
  13.  
  14.     def __new__(cls, name, bases, dict):
  15.         if bases[-1] is True: # is True, потому что нам надо именно объект True
  16.             # полный бокал
  17.             bases = [cls.full_glass] + list(bases[:-1])
  18.         elif bases[-1] is False:
  19.             # пустой
  20.             bases = [cls.empty_glass] + list(bases[:-1])
  21.  
  22.         return super(GlassMetaclass, cls).__new__(cls, name, tuple(bases), dict)
  23.  
  24.  
  25. class Glass(object):
  26.  
  27.     __metaclass__ = GlassMetaclass
  28.  
  29.  
  30. if __name__ == '__main__':
  31.     class MyGlass(Glass, True): pass # полный стакан
  32.     class YourGlass(Glass, False): pass # пустой стакан
  33.  
  34.     print issubclass(MyGlass, FullGlass) # True
  35.     print issubclass(YourGlass, EmptyGlass) # True 

Упрощение API

Допустим, нам требуется создать дерево из классов (не структуру данных, а просто иерархию). Пусть это будет иерархические регулярные выражения. Каждому классу в этом случае требуется передать строку, которая будет скомпилирована в регулярное выражение. В классическом виде этом будет выглядеть примерно так:
Copy Source | Copy HTML
  1. class Music(MyRe):
  2.  
  3.     pattern = '/music'
  4.  
  5.     class Artist(MyRe):
  6.  
  7.         pattern = '/artist/\d+'
  8.  
  9.     class Song(MyRe):
  10.  
  11.         pattern = '/song/\d+'
  12.  
  13.     class Album(MyRe):
  14.  
  15.         pattern = '/album/\d+'

Включение строк прямо в определение классов позволяет получить более красивый и понятный код:
Copy Source | Copy HTML
  1. class Music(MyRe, '/music'):
  2.  
  3.     class Artist(MyRe, '/artist/\d+'): pass
  4.     class Song(MyRe, '/song/\d+'): pass
  5.     class Album(MyRe, '/album/\d+'): pass

Заключение


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

Дополнительная литература по метаклассам


  1. Официальная документация, Customizing class creation. docs.python.org/3.1/reference/datamodel.html#customizing-class-creation
  2. Unifying types and classes in Python 2.2 #Metaclasses, Guido van Rossum. www.python.org/download/releases/2.2/descrintro/#metaclasses
  3. PEP-3115, Metaclasses in Python 3000. www.python.org/dev/peps/pep-3115/
  4. Python Metaclasess: Who? Why? When?, Mike C. Fletcher at PyCon 2004, презентация. www.vrplumber.com/programming/metaclasses-pycon.pdf
+41
22 декабря 2009, 10:50
54

комментарии (33)

+7
oe24 #
Скажите, а как/где сделали такой красивый скриншот?
+15
drJonnie #
Это gEdit, стандартная тема Cobalt, стандартная подстветка для Питона + шрифт Consolas от Микрософта. Дальше просто вырезал нужный фрагмент да сделал тень.
+2
Troytft #
Отлично выглядит, себе пришлось так же сделать)
+7
MadJest #
тоже только из-за него зашел )))
+9
postback #
Это для меня открытие года, сколько не кодил на питоне — такое впервые увидел.
+6
bobry #
аве Python :)
я от него такого не ожидал
+1
krylatij #
Боже, Python такой клёвый! Пожалуйста, сделай так чтобы он был еще очень быстрым!
+1
susl #
0
krylatij #
Слышал, но пока не пробовал.
Меня code.google.com/p/unladen-swallow/ вдохновляет
0
susl #
да. но он пока еще только начинает развиваться… еще можно на PyPy посмотреть. они вроде как раз в процессе прикручивания JIT-компилятора
+1
Regis #
Вот за такое и люблю Питон — при желании можно сделать удобные и красивые вещи под конкретные задачи.
–1
Zubchick #
Не разделяю общих восторгов, хоть и являюсь фанатом python. Это скорее «не документированная» возможность, чем какая-то реальная вещь. А то что python милашка это и так ясно :D
+1
krylatij #
Что значит не документированные? Есть документированный инструмент, а насколько ты умеешь им владеть зависит от тебя.
–1
Zubchick #
«не документированная» != не документированная
–5
Deepwalker #
Открываем исходники Django и не порем чушь.
0
Zubchick #
автор поста не представил ни одного прикладного примера, примеры в статье носят сугубо академический характер. И на джангу ссылок тоже не было.
–2
Deepwalker #
И что? Это отменяет активное использование метаклассов в python?
0
Zubchick #
Конечно питон-гуру могут меня поправить, но я считал метаклассами классы, создающие классы. Когда я говорил про «не документированную возможность» я говорил про использование именованых аргументов при создании класса(сабж статьи что тут сверху страницы). Так что научитесь читать и не порите чушь.
–2
Zubchick #
Ах, да я уже прочитал ваш коммент внизу, я вас раскусил, вы не читали статью, чукча-писатель решил похвастаться тем что открывал исходники джанги.
0
drJonnie #
Да, вы правы.
0
susl #
вот именно за такие извращения я и люблю python )
но что-то я не могу представить где это может понадобится. примеры неубедительны совсем…
0
drJonnie #
Я тоже сначала статью закончил словами, что в реальности это практически нигде не пригодится. Только как-то мрачно получалось, поэтому убрал. Думаю, что стоит расценивать только как демонстрацию гибкости Питона.
0
m4spam #
class Artist(MyRe, '/artist/\d+'): pass

Предлагаю заменить на что-то вроде makeRe('Artist', '/artist/\d+'), которое будет возвращать класс.
Или недостаточно красиво?
0
susl #
тогда вложенные классы (как в примере) не получится так красиво сделать
0
m4spam #
А так?

class Music(MyRe):
pattern = '/music'

Artist = '/artist/\d+'
Song = '/song/\d+'
Album = '/album\d+'

Правда, в данном случае мета-классу MyRe нужно будет озадачиться конвертацией аттрибутов.
Может быть, со мной что-то не так, но мне такой вариант действительно кажется более симпатичным, чем «class Artist(MyRe, '/artist/\d+'): pass».
0
susl #
я с вами в чем-то согласен. не то что симпатичнее, скорее привычней :)

я потому и спросил у автора применение :) примеры не маштабируются: если таких атрибутов как pattern не 1, а 3-4, то передавать их в качестве базовых классов уж совсем некрасиво выходит…
0
drJonnie #
А наследование?
0
m4spam #
Поясняю.
Мне кажется, что запись «makeRe('Artist', '/artist/\d+')», возвращающая новый полноценный класс, менее магична, более проста и понятна, чем «class Artist(MyRe, '/artist/\d+'): pass».
Не вижу никаких проблем для наследования, либо не понимаю вашего вопроса.
0
drJonnie #
Конечно, это менее магично. Но наследования все равно не получится:
class Artist(MyClass, '/artist/\d+'):
    pass

class RockArtist(Artist):
    pass
0
susl #
почему же?

Artist = makeRe('Artist', '/artist/\d+')

class RockArtist(Artist): pass
0
m4spam #
Или даже так:
Artist = makeRe('/artist/\d+')
class RockArtist(Artist): pass

Только зачем? О.о
0
susl #
мне когда-то самому нужно было что-то похожее. в итоге я воспользовался namedtuple :)
–2
Deepwalker #
Автору спасибо за статью, жаль примеры у вас слабые, да и изначальный посыл не вдохновляет: )
А вот за ссылочки в конце статьи благодарен особенно. Всем, кто решил, что метаклассы это недокументированная магия, советую пройти по этим ссылкам.
А также традиционный совет — открываем django/db/models/base.py и смотрим на первый же класс, который на самом деле метакласс.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.