Как стать автором
Обновить

Комментарии 27

Пока кинул статью в свой персональный блог. Не знаю достойна ли статейка блога про Питон.
Хотя как пример такого шаблона с OptionParser и UnitTest — может и ничего.
Зачем вы везде global понаставили?
Т.к. переменные глобальные, то доступ к ним будет возможен из любого окружения.

Если их не объявить глобальными, то достучаться до них из тела функции можно будет только объявляя их внутри функции как nonlocal или опять таки global. Проще один раз это сделать во внешнем окружении.
Попробуйте убрать global — всё должно работать.
Да, Вы правы. Действительно заработало. Может это были особенности python 2.5, что приходилось так изгаляться с global объявлениями.
Но в любом случае спасибо за наводку. Нужно будет ещё раз перечитать области видимости переменных.
Вы не правы, в питоне есть замыкания, а global используется для другого и крайне редко.
Про замыкания, не могу ничего припомнить. Можете навести на материал? Заранее спасибо.
Да все просто, тут даже никаких материалов не нужно, чтоб в суть въехать. Глобальные переменные в питоне — это такой вырожденный случай замыкания.

x = 5 # глобальная переменная
def f()
    print x # напечатает x

def my_func1():
    # простой пример замыкания: отличается от предыдущего только тем, что мы все завернули в функцию
    y = 5
    def my_inner_func():
         print y
    my_inner_func() # напечатает 5

def my_func2(y):
    # чуть более сложный пример
    def my_inner_func():
         print y
    my_inner_func() # напечатает параметр y, переданный функции myfunc



Казалось бы, какая ерундовая фича языка. Но на деле выходит, что в языках с замыканиями можно писать гораздо более приятный код, чем в языках без замыканий.

Очень часто это все используется в сочетании с тем фактом, что в питоне функция — тоже объект (ее, например, можно передать как параметр, и вернуть как результат вычислений). Немного надуманный пример: get_incrementer возвращает функцию, которая принимает какое-то значение, добавляет к нему заранее заданное число step и возвращает результат.


def get_incrementer(step):
    def my_inner_func(value):
         return value+step
    return my_inner_func 

inc3 = get_incrementer(3) # создаем функцию, которая добавляет 3 к переданному ей числу
print inc3(7) # напечатает 10. 

inc5 = get_incrementer(5) # создаем функцию, которая добавляет 5 к переданному ей числу
print inc5(7) # напечатает 12. 



Если это все понятно, то можно, например, написать простейший декоратор:

def ya_ne_poyehal(func):
    def inner():
        return func()+u" А я не поехал. Я купил мотыля и пошел на реку."
    return inner

@ya_ne_poyehal
def get_serious_text():
    return u"Кто-то там выступил с пакетом конструктивных предложений."

print get_serious_text() # а текст-то уже не серьезный


@декоратор — это просто синтаксический сахар. Абсолютно то же самое можно было записать вот так:

def get_serious_text():
    return u"Кто-то там выступил с пакетом конструктивных предложений."
get_serious_text = ya_ne_poehal(get_serious_text)

print get_serious_text() # а текст-то уже не серьезный


Если и это все понятно, то дальше хорошо бы позаботиться, чтоб наш декоратор сохранял параметры функции, ее имя, докстринги и тд (см. в сторону functools.wraps), но это меня уже и так чего-то не в ту степь занесло.
Специально для тестирования чисел с плавающей запятой в unittest.TestCase есть методы assertAlmostEqual и assertNotAlmostEqual. Они вычисляют разницу между двумя числами, после округляют до указанного количества знаков после запятой и сравнивают результат с нулем. Конечно, списки с числами придется сравнивать вручную.
Спасибо. Я прочитал об этих двух методах уже после публикации. Думал упомянуть их в статье, но не стал.
Я так понимаю эти методы так же работаю и со списком. При этом приблизительно будут сравниваться элементы списка. Так?
Сами списки так сравнить не получится, только поочередно их элементы. Я бы написал вот так:
  1. # У вас матрица, поэтому берем по каждой строчке
  2. # из обоих обоих списков, после этого сравниваем
  3. # элементы в этих строках попарно.
  4. for x, y in zip(list1, list2):
  5.     map(self.assertAlmostEqual, zip(x, y))

Еще для уменьшения количества используемой памяти (вдруг данные большие будут) можно заменить zip на itertools.izip, который возвращает генератор.
Просто отличнейшее решение!
Насколько помню университетский курс по численным методам определение точности вычислений с плавающей точкой определяется через E (эпсилон). Т.е. задается некое E (в зависимости от нужной точности, если до 4 знака после точки, то 0.0001), и полученое значение сверяется с эталонным с точностью до E: если abs(эталонное — полученно) <= E, то считаем что результат получен верно в рамках заданной точности.
незачем привязывать основную функцию к файлу. я бы, все же, ее на 2 (а то и больше) разбил:
извините, раньше отправил…

&nbsp&nbsp for line in open(monitor, 'r'):
заменил бы на
&nbsp&nbsp for line in monitor:
а файл открывал в функции-обертке. тогда и тестировать было бы легче — не пришлось бы писать ваш тестовый список в файл и передавать путь к нему
я так в последствии и сделал, правда получилось как-то кривовато. Приведу код:

Copy Source | Copy HTML
  1. def main():
  2.     with open('test_mon', 'w') as MON:
  3.         MON.write(monitor)
  4.     try:
  5.         unittest.main()
  6.     finally:
  7.         os.remove('test_mon')
  8.         pass
  9.     return  0


Удаление файла сидит в файнале, иначе файл не удаляется. При этом не появляется никакой эксепшн… Не знаю в чём причина. Но с файнал стейтментом — работает.
pass в файнале не нужен — он там случайно оказался.
Совсем мимо.
Необходимо избавится от временного файла

def integration_test():
data = ""«some
data»""
UC.readin_monitor(test_data)

В метод передается список, а значит можно передать файл в main

UC.readin_monitor(open(path))
Простите пожалуйста, я совершенно запутался о чём мы говорим. Перечитал ещё раз комментарий susl'а и понял что я вообще не про то говорил, отвечая на его вопрос… последствия 4х утра.

По порядочку. Я буду повествовать, а Вы поправите меня, где я начну ошибаться.
1) С комментарием susl'a, действительно плохо как-то вышло. Я стараюсь таких конструкций не использовать. После появления with statement в python 2.6 — правильной конструкцией будет являться:

Copy Source | Copy HTML
  1. with open(filename, 'r') as IN:
  2.     for line in IN:
  3.         processing(line)


With statement обеспечивает обязательное закрытие файла при любых, даже самых непредвиденных обстоятельствах.

2) Ему в ответ я нагородил огородов с main функцией. В неё я вынес запись данных из многострочечной переменной monitor в тестовый файл. Именно его мы будет вчитывать для тестов. Проблема оказалась в том, что если попытаться выполнить os.remove('test_mon'), где 'test_mon' — это path к тестовому файлу данных, и поставить эту функцию сразу за unittest.main(), то файл так и остаётся не удалённым после прохождения тестов. Может этот самый unittest.main() после своего выполнения останавливает внешний скрипт тоже. Поэтому только finally можно заставить что-то выполниться в любом случае. Есть конечно методы самого unittest модуля, например, которые выполняются сразу после исполнения какого-либо теста. Можно наверное удаления файла поручить последнему тесту в самом конце.

3) В readin_monitor(monitor) передаётся строка. monitor — это название файла, который потом отрывается дурацким способом (смотри пункт 1) в теле самой функции. Можно в функцию передавать указатель на файл, или это правильнее назвать файловым объектом. Кажется, именно так Вы советовали: readin_monitor(open(filename)). Но я до конца не понимаю что этим можно выиграть. Открываем файл в main — передаём функцию указатель. Кусок кода перетекает из тела функции в main. Я не вижу того, что Вы пытаетесь мне показать. Можно поподробнее пожалуйста?

Спасибо.
Все верно.

Одна из наиболее приятных вещей в TDD это определение интерфейса. В момент написания теста вы определяете как бы хотели увидеть интерфейс. Это очень важно. Не нужно думать о реализации — она сковывает действия.

Тесты это первые пользователи интерфейса. В отличии от кода проекта они чисты — к их коду не примешивается логика приложения.

Что же мы видим в тесте? Создание файла в тесте смотрится убого. Много лишних телодвижений, а главное они никак не относятся к сути решаемой задачи. Итак, тесты первые пользователи интерфейса, следующие — программисты. И они будут решать задачу точно так же!

Всегда стоит писать предполагая что человек поддерживающий код — маньяк знающий ваш адрес :)
Хех… Понравилось про маньяков)))
Теперь я понял про что Вы.
Отличные вопросы.

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

Это не TDD, а довольно многословный интеграционный тест, опирающийся на особенности интерпретатора (assertAlmostEqual как подсказали выше). Все что он может сказать — что-то не работает. Что же именно сломано показывают юнит тесты.

Также стоит посмотреть на иные системы тестирования, мне больше нравится nose somethingaboutorange.com/mrl/projects/nose/. А еще больше rspec rspec.info, руби комьюнити уделяют тестированию значительно больше внимание. И конечно обязательно стоит прочесть The RSpec Book.

TDD описывает функциональность. Например «комментарий начинается с решетки»

def test_comment_start_with_sharp():
assert is_comment("# comment")
assert not is_comment(«some data»)

Аналогично поступаем со всеми знаниями — формат строки (разделенный пробелами, три значения), выбрасывание исключения итд.

Это простые тесты. Тест «комментарии не добавляются в выборку» слегка сложнее

class TestMeasure:
def setUp(self):
self.measure = Measure()

def test_add_values(self):
self.measure.add(«some data»)
eq_(1, self.measure.count)

def test_skip_comments(self):
self.measure.add("# comment")
eq_(0, self.measure.count)

Этот тест опирается на наше знание о комментарии. Это знание можно вынести в фабрики def create_comment_line(): return "# comment". Или использовать моки и стабы.

Успехов
Спасибо за НОС =)
Там, кстати, используются одновременно UnitTest и OptionParser. Правда кода очень много. И на мою неподготовленную психику столько кода сразу — вызвало лёгкое недомогание. Но ничего. Всё у нас впереди. Ещё раз спасибо.
Есть еще optmatch
Их стандартный пример(два аргумнета, один флаг и одна опция):
class Example(OptionMatcher):

	@optmatcher(flags='verbose', options='mode')
	def handle(self, file, verbose=False, mode='simple', where=None):
		...	
Еще одна вещь которая может значительно ускорить обучение — знакомство с функциональным программированием. Для начала хватит поверхностного. Покажу на вашем примере набор методов:

* преобразование имени файла в итератор
* его фильтрация по функции is_comment
* преобразование линии в элемент (массив трех значений)

Для полного покрытия приложения достаточно описать эти методы. При этом в первом методе стоит не столько проверять метод open (он уже проверяется тестами реализации языка), сколько выводом данных в verbose режиме.

def test_should_print_data_points_count_and_filename_in_verbose_mode():
# не следует здесь проверять вывод данных
# только проверка интересующей строки по регулярному

RSpec позволяет создавать конкретизировать описание, создать несколько уровней. Не желая затрагивать файловую систему, мы используем стаб, подменяя результат на тестовую строку.

describe Calculation
describe «verbose mode»
subject { Calculation.new(:verbose => true) }
it «should print data points count and filename» do
File.stub!(:open).and_return([«some data»])
subject.process('filename').should match(«Read in 1 data points from monitor file filename»)
end
end
end

В питоне обычно моделирую классами

class TestMeasure:
pass

class TestVerboseMeasure:
pass
Я поверхностно знаком с методами функционального программирования. И именно в питоне я встретился с ними в первый раз. \
Но если по честному, функциональное программирование делает код короче и, может, даже красивее, но далеко не понятнее. Я сужу просто о себе. Когда разбирался с примерами функционального программирования, въезжал в них относительно намного дольше чем с любыми другими конструкциями языка.
Немного на другую тему. Вы, как мне показалось, приверженец языка Ruby. Можно ли поинтересоваться чем именно Вас этот язык притянул сильнее, чем, скажем, питон? Можете в 3х словах дать основные ключевые отличия и достоинства Ruby над Python (одно из которых наверное будет большее уделение внимания юнит тестам).
Немного истории — C++ разработчик открывший для себя питон, как первый динамический язык программирования. Все так же люблю этот язык, иногда пишу на нем, но год назад перешел на Rails, а значит и Ruby.

Итак, причина выбора — Rails. Этого факта достаточно чтобы судить о схожести языков. Вокруг фреймворка сформировалось замечательное комьюнити придерживающиеся KISS, DRY принципов, развивающих TDD подход в BDD и SDD (RSpec & Cucumber). Они стремятся к совершенству, создают все новые и новые средства, но не забывают о новичках, предоставляя неплохие предложения по умолчанию. Очень много качественной литературы, в сети информации тоже хватает.

Ruby очень красивый и выразительный язык. Слова бессильны — нужно посмотреть.

Меня сложно назвать объективным, но это текущий выбор. Теперь изучаю Haskell, Lisp, Erlang… или вернусь к Python, дальше будет видно :)
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории