вторник, 29 апреля 2014 г.

Python: статические переменные в функциях

Реализация на классе

Самый прямой и дубовый путь в питоне для этого, это использовать атрибуты классов. Т.е. хранить состояние в атрибуте класса, в __init__() его инициализировать, а в вызовах методов делать с этим атрибутом то, что нам нужно.

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

1 class A():
 2 
 3     def __init__(self):
 4         self.cnt = 0
 5 
 6     def f(self):
 7         self.cnt += 1
 8         return self.cnt
 9 
10 a = A()
11 
12 print " Class A testing"
13 print a.f()
14 print a.f()
15 print a.f()
16 
17 > Class A testing
18 > 1
19 > 2
20 > 3

Реализация внутри функции

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

Можно!

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

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

    Внутри функции создаем атрибут.
    При первом вызове присваиваем ему начальное значение.
    В следующих вызовах увеличиваем это значение на 1.

Код:
1 def f():
 2     try:
 3         f.a += 1
 4     except AttributeError:
 5         f.a = 0
 6     return f.a
 7 
 8 print " Function f() testing"
 9 print f()
10 print f()
11 print f()
12 
13 > Function f() testing
14 > 1
15 > 2
16 > 3
http://blog.swlogic.eu/2011/06/19/python-staticheskie-peremennyie-v-funktsiyah/ 

Python doctest - интерактивное тестирование кода

Простейший пример тестирования функции

Проще всего пояснить, как это делается на примере. Ниже приведен простейший пример функции вместе с тестом для ее проверки. Как в этом тексте все сделано:

  •     В строках комментариев к функции написан тест. Тесты могут чередоваться с обычным текстом.
  •     Строчки комментария, начинающиеся с '>>>' выполняются
  •     В первой строчке, которая следует за строчками с '>>>', стоит результат, с которым будет сравнен с возвращаемый выражением в последней исполненной строке результатом. Если результаты равны, ничего не произойдет, если не равны, будет выдано сообщение об ошибке теста.
  •     Тестирование вывается вызовом функции doctest.testmod()
  •     Если хотим посмотреть, что происходит, можно вызвать тест в "разговорчивом" режиме: doctest.testmod(verbose=True)
    Для выполнения тестов, запускаем скрипты примеров на выполнение.
#! /usr/bin/python
# -*- coding: utf-8 -*-

def mult(a,b):
    """
    >>> mult(2,2)
    4
    """
    return a*b
    
if __name__ == "__main__":
    import doctest
    doctest.testmod()
    #doctest.testmod(verbose=True)
Пример с тестами в отдельном файле Все напсанное выше замечательно, но когда тестов становится больше, они начинают слишком загромождать код и появляется желание отделить их от кода. Это тоже предусмотрено. Можно вызвать функцию doctest.testfile("имя файла") и она выполнит тесты из внешнего файла. Ниже приведен пример такого подхода. Файл с тестируемым кодом:
#! /usr/bin/python
# -*- coding: utf-8 -*-

def mult(a,b):
    return a*b
    
if __name__ == "__main__":
    import doctest
    doctest.testfile("test.txt")
    #doctest.testfile("test.txt",verbose=True)

Файл тестов:

Тестирование функции mult(a,b)

>>> from test_in_other_file import mult
>>> mult(2,3)
6

понедельник, 28 апреля 2014 г.

воскресенье, 27 апреля 2014 г.

Mail with python

Send email:

>>> import smtplib
>>> s = smtplib.SMTP('localhost', 1025)
>>> s.sendmail('me@example.com', 'you@example.com', "Python is awesome!")
And receive email:

$ python -m smtpd -n -c DebuggingServer localhost:1025
---------- MESSAGE FOLLOWS ----------
Python is awesome!
------------ END MESSAGE ------------

Передача параметра по ссылке (изменение переменной внутри функции)

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

Если переменная связана с неизменяемым значением, например None, int, str, tulpe, ключи словарей, то естественно, это значение не изменится. А вот если переменная связана со списком, словарем или классом, то значение связанного с переменной объекта изменится.

Не изменяется:
1 def a(b):
 2     b = 2
 3 
 4 c = 3
 5 print c
 6 a(c)
 7 print c
 8 
 9 >>> 3
10 >>> 3
Изменяется:
 1 def a(b):
 2     b[0] = 2
 3 
 4 c = [3]
 5 print c
 6 a(c)
 7 print c
 8 
 9 >>> [3]
10 >>> [2]

Генераторные выражения

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

(expr1 expr2)
Где нужно учесть следующее:

Выражение всегда заключено в круглые скобки.
В случае, если выражение используется в качестве единственного аргумента при вызове функции, круглые скобки выражения могут совпадать с круглыми скобками вызова функции.
expr2 - итератор, генерирующий значения.
expr1 - операция, которая производится с сгенрированным expr2 значением.

Таким образом, генераторное выражение работает следующим образом:

При очередном вызове expr2, и генерирует следующее исходное значение.
Сгенерированное исходное значение подставляется в expr1.
Сгенерированное expr1 значение возвращается генераторным выражением.
Генераторное выражение ждет следующего вызова


Простейший пример:

print sum(x*(5+x) for x in xrange(10,20,30))

суббота, 26 апреля 2014 г.

Итераторы

Чтобы получить объект-итератор, нужно создать объект, который будет иметь два метода со специальными именами:

__iter__() - метод, который возвращает сам объект.
next() - метод, который возвращает следующее значение итератора.


 class SimpleIterator(object):
     
     def __init__(self,fname):
         self.fd = open(fname,'r')
         
     def __iter__(self):
         return self

     def next(self):
         l = self.fd.readline()
         if l != '':
             l = l.rstrip('\n')
             num = int(l)
             return num*2
         raise StopIteration

Проверка:

>>> simple_iter = SimpleIterator('test_data.txt')
>>> for i in simple_iter:
...    print i
6
8

Генераторы и Сопрограммы

Генератор, это очень похоже на итератор, только это функция. При вызове этой функции в цикле, она при каждом новом цикле выдает следующее значение. Пишется эта функция с использованием оператора yield

yield работает как return, с одной разницей, что между вызовами функций, все состояния и данные будут при следующем вызове функции такими, какими они были на момент предыдущего исполнения yield.

По сути это ключевое слово создает итератор, просто создание итератора упрощается за счет того, что методы __iter__() и next() создаются автоматически. При выходе из функции не по оператору yield автоматически генерируется StopIteration.

1 def power(start):
2     print "power is called the first time"
3     for i in xrange(start,start+5):
4         yield i*i
5     print "power is called the last time"
Результат:

1 >>> power(5)
2 power() is called the first time
3 4
4 9
5 16
6 25
7 36
8 power() is called the last time