Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Некоторые вероятности Python о которых вы допустимо не знали

Anna | 16.06.2014 | нет комментариев

Вступление

Я дюже полюбил Python позже того, как прочитал книгу Марка Лутца «Постигаем Python». Язык дюже прекрасен, на нем отрадно писать и выражать личные идеи. Огромное число интерпретаторов и компиляторов, растяжений, модулей и фреймворков говорит о том, что сообщество дюже энергично и язык прогрессирует. В процессе постижения языка у меня возникло много вопросов, которые я скрупулезно гуглил и усердствовал осознать всякую непонятую мною конструкцию. Об этом мы и побеседуем с вами в этой статье.

Немножко о терминах

Начну вероятно с терминов, которые Зачастую путают начинающих Python программистов.

List comprehensions либо генераторы списков возвращают список. Я неизменно путал генераторы списков и выражения — генераторы (но не генераторы выражений!). Согласитесь, по русский звучит дюже схоже. Выражения — генераторы это generator expressions, особые выражения, которые возвращают итератор, а не список. Давайте сравним:

f = (x for x in xrange(100)) # выражение - генератор
c = [x for x in xrange(100)] # генератор списков

Это две абсолютно различные конструкции. 1-й возвращает генератор (то есть итератор), 2-й обыкновенный список.

Generators либо генераторы это особые функции, которые возвращают итератор. Что бы получить генератор необходимо возвратить функции значение через yield:

def prime(lst):
    for i in lst:
        if i % 2 == 0:
            yield i

>>> f = prime([1,2,3,4,5,6,7])
>>> list(f)
[2, 4, 6]
>>> next(f)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>

Кстати, в Python 3.3 возникла новая конструкция yield from. Совместное применение yield и for применяется настоль Зачастую, что эти две конструкции решили объединить.

def generator_range(first, last):
    for i in xrange(first, last):
        yield i

def generator_range(first, last):
    yield from range(first, last)

Что такое контекстные администраторы и для чего они необходимы?

Контекстные администраторы это особые конструкции, которые представляют из себя блоки кода, арестанты в инструкцию with. Инструкция with создает блок применяя протокол контекстного администратора, о котором мы побеседуем дальше в этой статье. Примитивной функцией, использующей данный протокол является функция open(). Всякий раз, как мы открываем файл нам нужно его закрыть, что бы вытолкнуть выходные данные на диск (на самом деле Python вызывает способ close() механически, но очевидное его применение является отличным тоном). Скажем:

fp = open("./file.txt", "w")
fp.write("Hello, World")
fp.close()

Что бы всякий раз не вызывать способ close() мы можем воспользоваться контекстным администратором функции open(), тот, что механически закроет файл позже выхода из блока:

with open("./file.txt", "w"):
    fp.write("Hello, World")

Тут нам не необходимо всякий раз вызывать способ close, что бы вытолкнуть данные в файл. Из этого следует, что контекстный администратор применяется для выполнения каких либо действий до входа в блок и позже выхода из него. Но функциональность контекстных администраторов на этом не заканчивается. Во многих языках программирования для сходственных задач применяются деструкторы. Но в Python если объект применяется где то еще то нет ручательства, что деструктор будет вызван, так как способ __del__ вызывается только в том случае, если все ссылки на объект были исчерпаны:

In [4]: class Hello:
   ...:     def __del__(self):
   ...:         print 'destructor'
   ...:

In [5]: f = Hello()

In [6]: c = Hello()

In [7]: e = Hello()

In [8]: del e
destructor

In [9]: del c
destructor

In [10]: c = f

In [11]: e = f

In [12]: del f # <- деструктор не вызывается

Решим эту задачу через контекстные администраторы:

In [1]: class Hello:
   ...:     def __del__(self):
   ...:         print 'деструктор'
   ...:     def __enter__(self):
   ...:         print 'вход в блок'
   ...:     def __exit__(self, exp_type, exp_value, traceback):
   ...:         print 'выход из блока'
   ...:

In [2]: f = Hello()

In [3]: c = f

In [4]: e = f

In [5]: d = f

In [6]: del d

In [7]: del e

In [8]: del c

In [9]: del f # <- деструктор вызвался тогда когда все ссылки на объект были удалены
деструктор

Сейчас испробуем вызвать администратор контекста:

In [10]: with Hello():
   ....:     print 'мой код'
   ....:
вход в блок
мой код
выход из блока
деструктор

Мы увидели, что случился гарантированный выход из блока позже выполнения нашего кода.

Протокол контекстного администратора

Мы теснее коротко разглядели протокол контекстного администратора написав маленький класс Hello. Давайте сейчас разберемся в протоколе больше детально. Что бы объект стал контекстным администратором в его класс непременно необходимо включить два способа: __enter__ и __exit__. 1-й способ выполняется до входа в блок. Способу дозволено возвратить нынешний экземпляр класса, что бы к нему дозволено было обращаться через инструкцию as.

Способ __exit__ выполняется позже выхода из блока with, и он содержит три параметра — exp_type, exp_value и exp_tr. Контекстный администратор может вылавливать исключения, которые были возбуждены в блоке with. Мы можем вылавливать только надобные нам исключения либо подавлять непотребные.

class Open(object):
    def __init__(self, file, flag):
        self.file = file
        self.flag = flag

    def __enter__(self):
        try:
            self.fp = open(self.file, self.flag)
        except IOError:
            self.fp = open(self.file, "w")
        return self.fp

    def __exit__(self, exp_type, exp_value, exp_tr):
        """ подавляем все исключения IOError """
        if exp_type is IOError:
            self.fp.close() # закрываем файл
            return True
        self.fp.close() # закрываем файл

with Open("asd.txt", "w") as fp:
    fp.write("Hello, Worldn")

Переменная exp_type содержит в себе класс исключения, которое было возбуждено, exp_value — сообщение исключения. В примере мы закрываем файл и подавляем исключение IOError посредством возврата True способу __exit__. Все остальные исключения в блоке мы разрешаем. Как только наш код подходит к концу и блок заканчивается вызывается способ self.fp.close(), не зависимо от того, какое исключение было возбуждено. Кстати, внутри блока with дозволено подавлять и такие исключения как NameError, SyntaxError, но этого делать не стоит.

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

Пакет contextlib

Создание контекстных администраторов традиционным методом, то есть написанием классов с способами __enter__ и __exit__ не одна из трудных задач. Но для банального кода написание сходственных классов требует огромнее возьни. Для этих целей был придуман декоратор contextmanager(), входящий в состав пакета contextlib. Применяя декоратор contextmanager() мы можем из обыкновенной функции сделать контекстный администратор:

import contextlib
@contextlib.contextmanager
def context():
    print 'вход в блок'
    try:
        yield {}
    except RuntimeError, err:
        print 'error: ', err
    finally:
        print 'выход из блока'

Проверим работоспособность кода:

In [8]: with context() as fp:
   ...:     print 'блок'
   ...:
вход в блок
блок
выход из блока

Испробуем возбудить исключение внутри блока.

In [14]: with context() as value:
   ....:     raise RuntimeError, 'Error'
   ....:
вход в блок
error:  Error
выход из блока

In [15]:

Как видно из примера, реализация с применением классов фактически ничем не отличается по функциональности от реализации с применением декоратора contextmanager(), но применение декоратора гораздо упрощает наш код.

Еще один увлекательный пример применения декоратора contextmanager():

import contextlib
@contextlib.contextmanager
def bold_text():
    print '<b>'
    yield # код из блока with выполнится здесь
    print '</b>'

with bold_text():
    print "Hello, World"

Итог:

<b>Hello, World</b>

Схоже на блоки в руби не так ли?

И напоследок побеседуем о вложенных контекстах. Вложенные контексты разрешают руководить несколькими контекстами единовременно. Скажем:

import contextlib
@contextlib.contextmanager
def context(name):
    print 'вход в контекст %s' % (name)
    yield name # наш блок
    print 'выход из контекста %s' % (name)

with contextlib.nested(context('first'), context('second')) as (first, second):
    print 'внутри блока %s %s' % (first, second)

Итог:

вход в контекст first
вход в контекст second
внутри блока first second
выход из контекста second
выход из контекста first

Подобный код без применения функции nested:

first, second = context('first'), context('second')
with first as first:
    with second as second:
        print 'внутри блока %s %s' % (first, second)

Данный код хоть и схож на предшествующий, в некоторых обстановках он будет трудиться не так как нам хотелось бы. Объекты context(‘first’) и context(‘second’) вызываются до входа в блок, следственно мы не сумеем перехватывать исключения, которые были возбуждены в этих объектах. Согласитесь, 1-й вариант гораздо суперкомпактнее и выглядит прекраснее. А вот в Python 2.7 и 3.1 функция nested устарела и была добавлена новая синтаксическая конструкция для вложенных контекстов:

with context('first') as first, context('second') as second:
    print 'внутри блока %s %s' % (first, second)

range и xrange в Python 2.7 и Python 3

Вестимо, что Python 2.7 range возвращает список. Думаю все согласятся, что беречь крупные объемы данных в памяти нецелесообразно, следственно мы используем функцию xrange, возвращающий объект xrange тот, что ведет себя примерно так же как и список, но не хранит в памяти все выдаваемые элементы. Но меня немножко поразило поведение xrange в Python 2.x, когда функции передаются крупные значения. Давайте посмотрим на пример:

>>> f = xrange(1000000000000000000000)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OverflowError: Python int too large to convert to C long
>>>

Python нам говорит о том, что int слишком длинный и он не может быть переконвертирован в C long. Оказывается у Python 2.x есть ограничения на целое число, в этом мы можем удостовериться просмотрев константу sys.maxsize:

>>> import sys
>>> sys.maxsize
9223372036854775807
>>>

Вот оно наивысшее значение целого числа:

>>> import sys
>>> sys.maxsize 1
9223372036854775808L
>>>

Python старательно переконвертировал наше число в long int. Не изумляйтесь, если xrange в Python 2.x будет вести себя напротив при крупных значениях.

В Python 3.3 целое число может быть беспредельно огромным, давайте проверим:

>>> import sys
>>> sys.maxsize
9223372036854775807
>>> range(sys.maxsize 1)
range(0, 9223372036854775808)
>>>

Конвертирование в long int не случилось. Вот еще пример:

>>> import sys
>>> sys.maxsize   1
9223372036854775808
>>> f = sys.maxsize   1
>>> type(f)
<class 'int'>
>>>

В Python 2.7

>>> import sys
>>> type(sys.maxsize   1)
<type 'long'>
>>>

Не явственное поведение некоторых конструкций

Думаю все согласятся, что простота питона заключена не в легкости его постижении, а в простоте самого языка. Питон прекрасен, эластичен и на нем дозволено писать не только в объектно ориентированном жанре, но и в функциональном. Но о поведении некоторых конструкций, тот, что на 1-й взор кажутся необычными нужно знать. Для начала разглядим 1-й пример.

>>> f = [[]] * 3
>>> f[0].append('a')
>>> f[1].append('b')
>>> f[2].append('c')
>>>

Каков будет итог выполнения данной конструкции? Неподготовленный разработчик известит о итоге: [['a'], [b'], [c']]. Но на самом деле мы получаем:

>>> print f
[['a', 'b', 'c'], ['a', 'b', 'c'], ['a', 'b', 'c']]
>>>

Отчего в всяком списке итог дублируется? Дело в том, что оператор умножения создает ссылки внутри нашего списка на один и тот же список. В этом легко удостовериться немножко дополнив наш пример:

>>> c = [[], [], []]
>>> hex(id(c[0])), hex(id(c[1])), hex(id(c[2]))
('0x104ede7e8', '0x104ede7a0', '0x104ede908')
>>>

>>> hex(id(f[0])), hex(id(f[1])), hex(id(f[2]))
('0x104ede710', '0x104ede710', '0x104ede710')
>>>

В первом случае все типично и ссылки на списки различные, а во втором примере мы ссылаемся на один и тот же объект. Из этого следует, что метаморфоза в первом списке повлечет за собой метаморфоза в последующих, так что будьте внимательны.

2-й пример теснее рассматривался на прогре, но мне захотелось включить его в статью. Посмотрим на lambda — функцию, которую мы будет прогонять через цикл for, и помещать всякую функцию в словарь:

>>> tmp = {}
>>> for i in range(10):
...     tmp[i] = lambda: i
>>> tmp[0]()
9
>>> tmp[1]()
9
>>>

В пределах lambda функции переменная i замыкается и как бы создается экземпляр еще одной переменной i в блоке lambda — функции, которая является ссылкой на переменную i в цикле for. Всякий раз когда счетчик цикла for меняется, меняются и значения во всех lambda функциях, следственно мы получаем значение i-1 во всех функциях. Поправить это легко, очевидно передав lambda функции в качестве первого параметра значение по умолчанию — переменную i:

>>> tmp = {}
>>> for i in range(10):
...     tmp[i] = lambda i = i: i
>>> tmp[0]()
0

>>> tmp[1]()
1
>>>
Источник: programmingmaster.ru
Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB