Заметки о метапрограммировании в Python

    По мере накопления опыта программирования на одном языке, все мы нарабатываем стандартные для себя приемы программирования. Чем выше наш опыт, тем больше количество, более разносторонни методы их работы. При переходе на другой язык, мы стараемся их воспроизвести. Иногда, такое бывает что часть из них неактуальна или неэффективна. Большую часть времени от изучения языка отнимают новые несвойственные прежним нам, приемы. Я хотел бы отметить некоторые особенности Python, которые для меня были не очевидны поначалу освоения.

    По большому счету в питоне все объекты хранятся в словарях(dict).Словарь — есть список объектов, которому противопоставлен ключ, уникальный на весь словарь.

    class A(): i=1; s='string'
    print dir(A)

    > ['__doc__', '__module__', 'i', 's']


    Это свойство, позволяет «вытворять» с классами и экземплярами классов разнообразные вещи. Например:

    class A(): v0=1
    class B(A):
        v1=2
        def __init__(self):
            self.v2 = 3

    b=B()
    print b.v2, b.v1, b.v0

    > 3 2 1


    Когда Python пытался достать v2 все ясно — оно получено из словаря экземпляра b. Далее интересней, свойство v1, уже не содержит элемент b, его содержит словарь B — питон взял его оттуда. И, наконец, при поиске элемента v0 обращаемся к словарю родителю B-A, и достаем его оттуда. Понятно что если определить в пределах b свойство v0, то его значение будет получено оттуда, не поднимаясь на более высокие уровни.

    def __init__(self):
        print self

    class A(): pass
    setattr(A, '__init__', __init__)

    a=A()

    > <__main__.A instance at 0x009E9EB8>



    В этом примере, мы переместили функцию __init__ в пределы класса A(мы могли переместить функцию с любым другим именем, __init__ приведена для наглядности). Декларация класса A эквивалентна:

    class A():
        def __init__(self):
            print self



    Классы в питоне есть экземпляры метаклассов. Это, есть неожиданность, потому как теперь вам позволено «на лету» определять классы, а не только их экземляры.

    def __init__(self):
        print self

    B = type('B', (), {})
    setattr(B, '__init__', __init__)

    b = B()


    > <__main__.B object at 0x009E7CD0>



    Заметьте, что функция определена лишь для экземпляров класса, вызвать её для самого класса A нельзя. С другой стороны, что нам мешает эту возможность добавить.

    def Omd(obj):
        print obj

    B = type('B', (), {})
    setattr(B, 'c', classmethod(Omd))
    setattr(B, 'o', Omd)

    b = B()
    b.c(), b.o()


    > <class '__main__.B'>
    > <__main__.B object at 0x009E7D50>

    Весело правда?! Определили фукнцию, которая печатает первый свой аргумент. Далее создали класс, на основе метакласса (можно было его определить и стандартно, как class A(type): pass). Дальше классу, добавили статическую функцию «с», под которой скрывается наша, и функцию «о» для вызова её от экземпляра класса B. Смотрим, что получается, первым мы видим что печатается класс, а после него экземпляр. Вот такая вот у них любовь.
    Метки:
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 34
    • 0
      Спасибо! Хоть до конца магию метапрограммирования еще не постиг, но пост как раз в тему. + вам. Ну и в тематический блог Язык программирования Python перенесите потом, плиз.
      • +1
        И, наконец, при поиске элемента v3 обращаемся к словарю родителю B-A, и достаем его оттуда

        v3?
        • 0
          Действительно, описка. Исправил. v3, имелось ввиду v0.
        • 0
          > позволено «на лету» определять классы

          в Python'e 3000 доступен также метод __build_class__
          • 0
            С 3.х не разбирался, но я думаю __build_class__, мало чем отличается от type() в 2.х
          • –2
            Что то у меня сомнение вызывает эта фраза — «Классы в питоне есть экземпляры метаклассов». Да и вообще — связка A есть B вышла из употребления в русском языке давным давно. Или автор англоязычный?

            В общем статья весьма низкого уровня, уж извините.
            • –4
              А, нет, насчет экземпляров вы правы, но зачем же все таки так не по русски выражаться?
              • +6
                Что то у меня сомнение вызывает эта фраза — «Классы в питоне есть экземпляры метаклассов».


                Полностью с вами согласен. Правильно она звучала бы как «Классы в питоне суть экземпляры метаклассов» :)
              • 0
                Интересно… Но пугает весёлость :-) Обычно весёлые программы веселят только автора и только в момент написания, а вот если потом надо это фиксить, развивать, адаптировать под новые реалии… вот тут вся весёлость может пропасть. Я бы четырежды подумал, прежде, чем делать что-то весёлое; вот такой я унылый :-)
                • 0
                  На таких веселостях очень классные вещи делаются, к примеру Django.
                  • 0
                    У Django есть смягчающие обстоятельства.

                    1. Она документирована и не заставляет вас разбираться с реализацией. Весёлые программы обычно не обладают таким свойством.
                    2. Там эта технология используется умеренно. Обычно setattr натравливается на self или на что-то ещё внутри объекта. Я не знаю случаев (хотя скорее всего они есть), когда там создаётся новый объект прям с нуля через type+setattr.

                    Одним словом, мне не кажется, что Django писали сильно веселясь. Напротив, люди много думали и уныло документировали :-)
                    • 0
                      Я про метаклассы — думаю вы смотрели код форм и моделей.
                      • 0
                        ORM?
                  • 0
                    Показаны какие-то метаклассы. Не понимаю, какое отношение это имеет к метапрограммированию?
                    • 0
                      Почему:

                      setattr(B, '__init__', __init__)

                      а не:

                      B.__init__ = __init__

                      ?
                      • 0
                        Это идентичный код. Просто setattr акцентирует внимание на том, что класс по большому счету словарь.
                      • +1
                        тема метаклассов не раскрыта, почитайте на том же www.ibm.com/developerworks/ru/library/l-pymeta/index.html
                        куда более полное и предметное описание черной магии
                        • +1
                          Я внимательно читал статьи на IBM. Статья вводная, и предназначена для тех, кто знакомится с интерпретируемыми языками.
                          • –2
                            если там вводная(их 3 части кстати), то тут тогда что?
                            • 0
                              «Вводная», это значит — как бы я рассказал, о метаклассах в питоне программистам, которые его знают. Я не претендую на соревнование со статьями IBM, или их подробный пересказ.
                              • +1
                                извините, не так вас понял комментарием выше
                        • +2
                          а дальше? примеры попрактичнее в студию!
                          • +1
                            Поддерживаю. Расскажите как на практике это все применялось Вами.
                            • +1
                              ок. Следующий пост об этом.
                          • +1
                            Это, конечно, все хорошо. В принципе, работая на Питоне, эти фишки все по крайней мере знают с туториалов или того же IBM DeveloperWorks.

                            Но… Реально, для чего используются метаклассы? Где есть смысл их применять, в какой момент это становится рациональным и не мешает читаемости программ?
                            • 0
                              > Где есть смысл их применять, в какой момент это становится рациональным и не мешает читаемости программ?

                              Если метакласс позволяет в три строки сделать то, на что раньше уходил целый талмуд, то он становится рациональным и не мешает читаемости программ. :)
                            • НЛО прилетело и опубликовало эту надпись здесь
                            • 0
                              можно еще создавать новый классобъект на основе классов.

                              from new import classobj
                              С = classobj('NewClass',(A,B),{})
                              с = С()

                              Иногда полезно когда хочется получить импортируемые классы которые наследуют классы в текущем модуле.
                              Через type помоему нельзя.
                              • 0
                                Можно же.

                                С = type('NewClass', (A,B), {})
                                с = С()
                                • 0
                                  Вы пробовали? У меня не получается, выдает исключение.

                                  >>> class A: pass
                                  ...
                                  >>> class B: pass
                                  ...
                                  >>> C = type('C',(A,B),{})
                                  Traceback (most recent call last):
                                  File "", line 1, in TypeError: a new-style class can't have only classic bases
                                  >>> from new import classobj
                                  >>> C = classobj('C', (A,B),{})
                                  >>> c = C()
                                  >>>
                                  • 0
                                    Конечно пробовал.

                                    Да и в исключении ясным английским языком написано в чем ваша ошибка.

                                    >>> class A(object): pass

                                    >>> class B(object): pass

                                    >>> C = type('C', (A, B), {})
                                    >>> c = C()
                                    >>>

                                    Ну и такой вам тонкий намек: «classic classes» и модуль new — deprecated и в питоне 3 удалены.
                              • –1
                                Конечно оффтопик, но у меня при чтении глаз цеплялся за лишние запятые в тексте.
                                Спасибо за статью, сам сейчас Python изучаю.
                                • +3
                                  Про метапрограммирование в статье как раз мало чего. Она толи о __dict__, толи о том, что все методы не более чем обычные функции с дополнительной обвязкой, толи еще о чем-то…

                                  На мой взгляд автору следовало бы раскрыть какой-нибудь один вопрос, но подробно. А получилось «Ухты, вот так можно. А я еще знаю, что и вот так можно, и вот так».

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