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

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

    Реализация


    Сначала необходимо разочаровать: в Питоне нельзя наследовать не от классов, т.е. нельзя передавать конструктору классов type параметр bases, в котором есть неклассы. Однако нигде не оговорено, что вы будете включать в определение класса.
    Ничего сложно в реализации кода выше нет, для этого просто потребуется создать метакласс, в котором будут отфильтровываться неклассы из bases (которые можно дальше использовать по своему усмотрению). Думаю, пример все объяснит, copy&run:
    Copy Source | Copy HTML
    1. # coding: utf-8
    2.  
    3. class Metaclass(type):
    4.  
    5.     '''<br/>    Metaclass печатает все объекты, от которых наследует класс<br/>    и которые не являеются классами.<br/>    '''
    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.     '''<br/>    GlassMetaclass создает либо класс EmptyGlass, либо FullGlass,<br/>    в зависимости от того, было ли указано True или False в определении<br/>    класса.<br/>    '''
    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
    Метки:
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 33
    • +7
      Скажите, а как/где сделали такой красивый скриншот?
      • +15
        Это gEdit, стандартная тема Cobalt, стандартная подстветка для Питона + шрифт Consolas от Микрософта. Дальше просто вырезал нужный фрагмент да сделал тень.
        • НЛО прилетело и опубликовало эту надпись здесь
        • +7
          тоже только из-за него зашел )))
        • +9
          Это для меня открытие года, сколько не кодил на питоне — такое впервые увидел.
          • НЛО прилетело и опубликовало эту надпись здесь
            • +1
              Боже, Python такой клёвый! Пожалуйста, сделай так чтобы он был еще очень быстрым!
            • +1
              Вот за такое и люблю Питон — при желании можно сделать удобные и красивые вещи под конкретные задачи.
              • –1
                Не разделяю общих восторгов, хоть и являюсь фанатом python. Это скорее «не документированная» возможность, чем какая-то реальная вещь. А то что python милашка это и так ясно :D
                • +1
                  Что значит не документированные? Есть документированный инструмент, а насколько ты умеешь им владеть зависит от тебя.
                  • –1
                    «не документированная» != не документированная
                  • –5
                    Открываем исходники Django и не порем чушь.
                    • 0
                      автор поста не представил ни одного прикладного примера, примеры в статье носят сугубо академический характер. И на джангу ссылок тоже не было.
                      • –2
                        И что? Это отменяет активное использование метаклассов в python?
                        • 0
                          Конечно питон-гуру могут меня поправить, но я считал метаклассами классы, создающие классы. Когда я говорил про «не документированную возможность» я говорил про использование именованых аргументов при создании класса(сабж статьи что тут сверху страницы). Так что научитесь читать и не порите чушь.
                          • –2
                            Ах, да я уже прочитал ваш коммент внизу, я вас раскусил, вы не читали статью, чукча-писатель решил похвастаться тем что открывал исходники джанги.
                            • 0
                              Да, вы правы.
                    • 0
                      вот именно за такие извращения я и люблю python )
                      но что-то я не могу представить где это может понадобится. примеры неубедительны совсем…
                      • 0
                        Я тоже сначала статью закончил словами, что в реальности это практически нигде не пригодится. Только как-то мрачно получалось, поэтому убрал. Думаю, что стоит расценивать только как демонстрацию гибкости Питона.
                      • 0
                        class Artist(MyRe, '/artist/\d+'): pass

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

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

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

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

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

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

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

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

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