Blog Trac Lyrics for music

Archive for July, 2007

Забытые возможности Python 2

Как я и обещал, пишу продолжение.

Где используется else?


   if test1:
       statement1
   elif test2:
       statement2
   else:
       statement3
   


   try:
       statement1
   except:
       statement2
   else:
       statement3
   finally:
       statement4
   

Всё? Казалось бы - а что ещё может быть? Оказывается, else ещё можно использовать в циклах. Действует он после того, как закончился собственно цикл:


   >>> for a in xrange(3):
   ...     print a
   ... else:
   ...     print 'else'
   ...
   0
   1
   2
   else
   


   >>> a = 0
   >>> while a < 3:
   ...     print a
   ...     a += 1
   ... else:
   ...     print 'else', a
   ...
   0
   1
   2
   else 3
   


   >>> a = 0
   >>> while a > 3:
   ...     print a
   ... else:
   ...     print 'else'
   ...
   else
   

В чём же, в таком случае, состоит его полезность? Некоторая полезность состоит в том, что если в цикле сработал break, то else не выполняется:


   >>> for a in xrange(3):
   ...     print a
   ...     if a == 1:
   ...         print 'break'
   ...         break
   ... else:
   ...     print 'else'
   ...
   0
   1
   break
   

В принципе, иногда такое нужно, но редко :)

Ну и последнее - строки считаются одной строкой, если между ними только пробельные символы:


   >>> def f(s):
   ...     print s
   ...
   >>> f("first " # first
   ... "second " # second
   ... 'third ' # third
   ... '''fourth
   ...  fifth''') # end
   first second third fourth
    fifth
   >>> "first " 'second'
   'first second'
   

Сделано это для того, чтобы уменьшить количество обратных слэшей в строках - комбинировать можно raw-string и обычные, а можно юникодные и ASCII-строки - результат юникодный, если хотя бы один кусочек юникодный:


   >>> r'begin n ' """end"""
   'begin \n end'
   >>> u'unicode ' 'non-unicode'
   u'unicode non-unicode'
   >>> 'non-unicode ' u'unicode'
   u'non-unicode unicode'
   

Ещё одна приятная особенность состоит в том, что складываются они в момент компиляции, а не выполнения.

Интроспекция в Python

При создании своей history к Django возник вопрос: “А как же получать юзера при сохранении очередной ревизии?”. Первым делом это было решено при помощи грязных хаков - с помощью функции-хелпера во view к объекту формы добавлялись юзер, айпишник, а потом в форме руками приходилось передавать дополнительные параметры в save(), которые хватаются декоратором, ну а он уже выполняет всю работу. Очень некрасиво, много лишних телодвижений, и можно что-нибудь забыть. Хотелось бы переделать это дело на сигналы… Но в сигналах нет информации о том, кто же это выполняет действие - а значит, надо каким-то образом их получить. Интроспекция - вот выход из ситуации!

Внимательный осмотр бранча full-history (который работает на сигналах) из репозитория Django ни к чему не привёл - у них хоть и есть user = models.ForeignKey(User, default="1"), но он нигде не используется и не заполняется. Потом был осмотрен проект django-modelhistory, который фактически является улучшенным и дополненным бранчем full-history, и там был найден код, который, используя интроспекцию, возвращал юзера. Выглядит этот код как Smart Fortwo, попросту говоря - ужасно. Вот он:

def snoop_the_call_chain():
    """
    Currently, a hackish (and a bit naive) way of walking up the call chain
    to determine the user (if any) that initiated the change in the system.
    """

    from django.contrib.auth.models         import User

    cur = inspect.currentframe()
    desiredFrame = None
    desiredFrameCount = 0
    f = None

    try:
        ancestors = inspect.getouterframes(cur)
        count = 0
        for frame in ancestors:
            if frame[3] == "save":
                desiredFrameCount = count
            count = count+1

        # Ensure that we have a callee for the save methods
        # which we're auditing.
        if debug_mode: print "Desired Frame: (%d)\n" % (desiredFrameCount+1)
        if desiredFrameCount >= len(ancestors):
            if debug_mode: print "No callee in call chain for save method. Bailing ..."
            return None

        desiredFrame = ancestors[desiredFrameCount+1][0]
        if inspect.isframe(desiredFrame):
            for name,value in inspect.getmembers(desiredFrame):
                if name == "f_locals":

                    dictionary = dict(value)
                    count = 1
                    if debug_mode: print "=========== Begin Frame Objects ==============="
                    for key,val in dictionary.items():
                        if debug_mode: print "   (%d) %s: %s" % (count,key,val)
                        count = count + 1
                    if debug_mode: print "============ End Frame Objects ================\n"

                    if 'request' in dictionary.keys() and\
                           dictionary['request'].user:

                        if debug_mode: print "*** Found Calling User ***"
                        return dictionary['request'].user

    finally:
        del cur

Гммм… Не самый очевидный код, плюс в нём есть несколько багов. При помощи мануала по Python и дополнительного мозга код был понят. А потом написан заново, адекватный и рабочий:

def obtain_request():
    """
    True hackish way of walking up the call chain to get the HttpRequest object
    that initiated the change in the system.
    """
    ancestors = inspect.getouterframes(inspect.currentframe())
    for frame_record in ancestors:
        frame = frame_record[0]
        if 'request' in frame.f_locals:
            req = frame.f_locals['request']
            if isinstance(req, HttpRequest):
                return req

Работает отлично. Какие есть замечания, предложения, пожелания, может вопросы?

Забытые возможности Python

У всех на слуху и часто используются такие возможности Python, как генераторы, декораторы, list comprehensions, оператор with… Но есть в Питоне и некоторые возможности, которые достаточно мало кому известны.

Например, то что в list comprehensions можно использовать “вложенные” циклы:


   >>> [a + b for a in 'abc' for b in 'de']
   ['ad', 'ae', 'bd', 'be', 'cd', 'ce']
   

Или вот другая возможность - сравнения цепочкой Питон трактует в математическом смысле, а не в программистском. То есть, x < y <= z интерпретируется как x < y and y <= z, не учитывая того, что в первом варианте y вычисляется единожды, а во втором - дважды. Примеры использования:


   >>> 'a' in 'ab' in 'abc'
   True
   >>> 5 in [5] in [[5]]
   True
   >>> 'a' in 'abcd' in ['abcd', 'adf']
   True
   >>> a = 5
   >>> a == 5 != 234
   True
   

Понятное дело, что использовать можно любые комбинации операций, как бы они неприглядно не выглядели:


   >>> 5 < 20 in (20,30)
   True
   >>> 5 > 1 < 5
   True
   >>> 5 is not None < 20
   True
   >>> 1 < 2 < 3 < 4 < 5 < 6 < 7 < 8
   True
   

P.S.Есть ещё пару вещей, про них я чуть позже расскажу.