четверг, 26 июня 2014 г.

python copy/deepcopy

Всем привет!

Продолжаем ломать копья об питоновскую парадигму изменяемых/неизменяемых объектов.
Разберем поверхностное и глубокое копирование словаря.

>>>s = {"str": "1", "list": [1, 2], "dict": {"first": "test1"}}
>>>f = s
>>>f['second'] = '2'

>>>print s

Надеюсь понятно какой вывод будет в консоли. Если нет, то смотрим ссылки.
Для того чтобы не ссылаться на один объект, нужно скопировать словарь s.

Но метод copy обладает неприятной особенностью, он поверхностно копирует словарь.
Смотрим на пример (s из предыдущего примера).

>>>f = s.copy()
>>>f['second'] = '3'
>>>f['dict']['third'] = '3'
>>>print s

{'dict': {'third': '3', 'first': 'test1'}, 'list': [1, 2], 'str': '1'}

Проблема решается использованием модуля copy.

>>>import copy

>>>f = copy.deepcopy(s)
>>>f['second'] = '3'
>>>f['dict']['third'] = '3'
>>>print s

>>>print f

Как работает copy.deepcopy

Начнем с аргументов

deepcopy(x, memo=None, _nil=[])

x - копируемый объект. Копируемым объектом может быть любой стандартный тип данных, а также кастомных объектов.
memo - словарь, в котором будут сопоставлены id созданных объектов и их значения. По-умолчанию None(если не понятно почему, то смотрим в ссылки [3]). При желании можно реализовать собственный "копир", если есть потребность в специфичном поведении копирования.
_nil - вспомогательный атрибут, используемый во время копирования

Пара примеров для понимания работы deepcopy

>>>s = {"i": 1, "s": "2", "l": [1, 2, 3]}
>>>memo = {}
>>>d = copy.deepcopy(s, memo)

>>>print id(s)
44501728

>>>print id(d)
44502304

>>>print d
{'i': 1, 's': '2', 'l': [1, 2, 3]}

>>>print id(memo)
44502016

>>>print memo

{44501728: {'i': 1, 's': '2', 'l': [1, 2, 3]}, 31711104: '2', 31565540: 2, 44502016: [1, 'i', '2', 's', 2, 3, [1, 2, 3], 'l', {'i': 1, 's': '2', 'l': [1, 2, 3]}], 31266408: 'i', 31565552: 1, 31565528: 3, 31268304: 's', 31708488: 'l', 44497976: [1, 2, 3]}

Словарь memo хранит id всех копируемых объектов, их значения., а также свой id.

>>>dt = copy.deepcopy(s, memo)

>>>print id(dt)
44502304

print dt
{'i': 1, 's': '2', 'l': [1, 2, 3]}

Как видим, deepcopy возвращает уже копировавшийся объект d

>>>s1 = {"i": 1, "s": "2", "l": [1, 2, 3]}
>>>dth = copy.deepcopy(s1, memo)
>>>print id(s1)
44516240

>>>print(id(dth))
44517248

>>>print(dth)
{'i': 1, 's': '2', 'l': [1, 2, 3]}

>>>print(id(memo))
44502016

>>>print(memo)
{44501728: {'i': 1, s': '2', 'l': [1, 2, 3]}, 31711104: '2', 44516240: {'i': 1,  's': '2', 'l': [1, 2, 3]}, 31565540: 2, 44502016: [1, 'i', '2', 's', 2, 3, [1, 2, 3], 'l', {'i': 1, 's': '2', 'l': [1, 2, 3]}, [1, 2, 3], {'i': 1, 's': '2', 'l': [1, 2, 3]}], 31266408: 'i', 31565552: 1, 31565528: 3, 44498856: [1, 2, 3], 31268304: 's', 31708488: 'l', 44497976: [1, 2, 3]}

Реализация deepcopy

Сначала происходит инициализация memo, если словарь не задан. Если memo задан тогда проверяется наличие id копируемого объекта, если такой объект уже есть в memo, то происходит его возврат.

d = id(x)
y = memo.get(d, _nil)
if y is not _nil:
    return y

Т.е. если после копирования нужно все время получать новый объект, то тогда поле memo нужно оставлять по-умолчанию (это мы видели на примере выше).

Далее определяется тип копируемого объекта и если он есть в описании стандартных типов, то вызывается функция копирования

copier = _deepcopy_dispatch.get(cls)
if copier:
    y = copier(x, memo)

Например, для словаря вызывается _deepcopy_dict

def _deepcopy_dict(x, memo):
    y = {}
    memo[id(x)] = y
    for key, value in x.iteritems():
        y[deepcopy(key, memo)] = deepcopy(value, memo)
    return y

Код довольно простой, создается новый словарь, в цикле копируются значения. Обратите внимание копирование идет - рекурсивным вызовом deepcopy, сделано это на случай того, если словарь составной и содержит вложенные объекты.
После успешного копирования обновляется словарь memo и вызывается функция _keep_alive, которая записывает в memo, сам копируемый объект, т.е. если вызвать memo[id(memo)], то получим список всех объектов, которые копировались.

Ссылки
[1]https://docs.python.org/2/library/stdtypes.html#dict.copy
[2]https://docs.python.org/2/library/copy.html
[3]http://evgenqa.blogspot.ru/2014/05/blog-post_23.html


serchcode

Для тех кто в танке и не читает хабр прикольный поисковик по коду опен сорс https://searchcode.com/


Нашел 193 результата по запросу putIn и 760 по medveDev.

понедельник, 16 июня 2014 г.

The Architecture of Open Source Applications

http://aosabook.org/en/index.html - неплохие статьи об архитекруре приложений с открытым исходным кодом. git, mercurial, nginx, zeromq и проч. А также есть что почитать для тех кто пишет на питоне - Twisted, SQLAlchemy.

Часть 3. Совсем немного о py.test.exe

py.test.exe это тул призванный упростить запуск тестов. Оговорюсь сразу что в мою задачу не входит перевод документации, поэтому ознакомится с полным набором команд можно, выполнив в командной строке следующую комманду (да и он может различаться в зависимости от того какие установлены плагины) и пройти по сслылкам в конце поста.

>>> py.test -h

Команды формирования вывода

-q - уменьшает детализацию информации о выполнении теста

-v - увеличивает детализацию о выполнении теста

--capture - параметр для управлением захвата вывода stdout/stderr. Принимает одно из значений fd|sys|no. Более подробно в ссылках
-s - маска для --capture=no. Параметр удобен, если нужно убрать мусор (например, дебажные принты) из информации о выполнившихся тестах.

--tb - отвечает за детализацию вывода трейса. Одно из long/short/line/native/no

Команды запуска 

-k <test name> - выполнить лишь заданный тест

--maxfail=<fail count> - задает максимальное число фейлов, тест удобен при дебаге или если заранее известно что результат тестов будет один и тотже
-x - маска дял --maxfaile=1

--pdb - стартует питоновский дебагер для отладки ошибок. Удобен для отладки тестов (подробнее в одном из следующих постов о дебаге тестов)

Комманды для настройки

--color (yes|no|auto) - цветной вывод в консоли или нет

Совсем небольшой пример

# use_tool.py
import py.test

@py.test.mark.parametrize('num', [i for i in range(5)])
def test_func(num):
    print('\ntest_func()')
    assert num % 2

def test_fail():
    print('bla, bla, fail')
    assert False

Выполняем в консоли
>>>py.test use_tool.py -k "test_func" -v -s -x --tb=line

============================= test session starts =============================
platform win32 -- Python 2.7.5 -- py-1.4.20 -- pytest-2.5.2 -- C:\Python27\python.exe
plugins: teamcity-messages
collected 6 items

use_tool.py:37: test_func[0]
test_func()
FAILED

================================== FAILURES ===================================
use_tool.py:40: assert (0 % 2)
!!!!!!!!!!!!!!!!!!! Interrupted: stopping after 1 failures !!!!!!!!!!!!!!!!!!!!
===================== 1 tests deselected by '-ktest_func' =====================
=================== 1 failed, 1 deselected in 0.04 seconds ====================

Ссылки

[1] http://pytest.org/latest/capture.html
[2] http://pytest.org/latest/customize.html?highlight=maxfail
[3] http://pytest.org/latest/usage.html

среда, 11 июня 2014 г.

Часть 2. Фикстуры pytest

Всем привет
В первой части немного познакомились с фреймворком для тестирования py.test, а в этой немного углубимся и посмотрим на дополнительные инструменты py.test.

Что такое test fixtures

В тестировании обычно под этим понимают флоу теста, его контекст.
В py.test предусмотрены следующие типы фикстур: встроенные фикстуры, вспомогательные функции, декораторы


setup/teardown

Часто перед запуском теста нужно провести базовые подготовления. Например, это может быть настройка окружения, запуск тестовых сервисов, подготовка тестовых данных, инициализация соединения с базами и проч. Для выполнения этих операций в py.test входит небольшой набор вспомогательных функций - несколько пар setup/teardown.

setup_module(module)
teardown_module(module)

Это функции которые вызываются перед и после выполнения всех тестов (в прошлой части более подробно об организации тестов)
Данную пару удобно использовать когда подготовка флоу происходит однажды - перед  запуском всех тестов.

setup_function(function)
teardown_function(function)

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

#fixtures.py
def setup_module(module):
    print('\nsetup_module()')


def teardown_module(module):
    print('\nteardown_module()')


def setup_function(function):
    print('\nsetup_function()')


def teardown_function(function):
    print('\nteardown_function()')


def test_first():
    print('\ntest_first()')


def test_second():
    print('\ntest_second()')


class TestMyClass:
    @classmethod 
    def setup_class(cls):
        print ('\nsetup_class()')

    @classmethod 
    def teardown_class(cls):
        print ('teardown_class()')

    def setup_method(self, method):
        print ('\nsetup_method()')

    def teardown_method(self, method):
        print ('\nteardown_method()')

    def test_first(self):
        print('test_first()')

    def test_second(self):
        print('test_second()')



>>>py.test -s -v fixtures.py

============================= test session starts =============================
platform win32 -- Python 2.7.5 -- py-1.4.20 -- pytest-2.5.2 -- C:\Python27\python.exe
collected 4 items

fixtures.py:17: test_first
setup_module()

setup_function()

test_first()
PASSED
teardown_function()

fixtures.py:21: test_second
setup_function()

test_second()
PASSED
teardown_function()

fixtures.py:40: TestMyClass.test_first
setup_class()

setup_method()
test_first()
PASSED
teardown_method()

fixtures.py:43: TestMyClass.test_second
setup_method()
test_second()
PASSED
teardown_method()
teardown_class()

teardown_module()
========================== 4 passed in 0.02 seconds ===========================


Встроенные фикстуры

В py.test предусмотрен обширный набор встроенных фикстур с полным набором можно ознакомиться, выполнив в консоле следующую команду

>>>py.test --fixtures

Для ознакомления в данном посте рассмотри одну из встроенных фикстур tempdir
temdir
Очень полезная вещь, предназначена для работы с темповыми данными. Т.е. в темповой директории можно хранить любые временные файлы. Например, это могут быть логи тестируемых программ или какой-то контекcт теста, который может понадобиться для воспроизведения бага.

Использовать крайне просто

#fixtures.py
def test_file(tmpdir):
    f = tmpdir.mkdir('logs').join('test.log')
    print(tmpdir.strpath)
    f.write('bla bla')

    with open(f.strpath, 'r') as fp:
        print(fp.read())

>>>py.test -s -v fixtures.py
============================= test session starts =============================
platform win32 -- Python 2.7.5 -- py-1.4.20 -- pytest-2.5.2 -- C:\Python27\python.exe
collected 1 items

fixtures.py:161: test_file \temp\pytest-76\test_file0
bla bla
PASSED

========================== 1 passed in 0.03 seconds ===========================

Полезные фикстуры

Для итерации по входным данным можно использовать полезный декоратор py.test.mark.parametrize

#fixtures.py
@py.test.mark.parametrize('num', [i for i in range(2)])
def test(num):
    assert num % 2


py.test -s -v fixtures.py
============================= test session starts =============================
platform win32 -- Python 2.7.5 -- py-1.4.20 -- pytest-2.5.2 -- C:\Python27\python.exe
collected 2 items

fixtures.py:182: test[0] FAILED
fixtures.py:182: test[1] PASSED

================================== FAILURES ===================================
___________________________________ test[0] ___________________________________

num = 0

    @py.test.mark.parametrize('num', [i for i in range(2)])
    def test(num):
>       assert num % 2
E       assert (0 % 2)

fixtures.py:184: AssertionError
===================== 1 failed, 1 passed in 0.02 seconds ======================

Кастомизация

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

Для определения собственных инструментов в py.test предусмотрены 

#fixtures.py
@pytest.fixture(scope="function")
def before(request):
    print('\nbefore()')

    def after():
        print("\nafter()")
    request.addfinalizer(after)


def test_func(before):
    print("\ntest_func()")



>>>py.test -s -v fixtures.py
============================= test session starts =============================
platform win32 -- Python 2.7.5 -- py-1.4.20 -- pytest-2.5.2 -- C:\Python27\python.exe
collected 1 items

fixtures.py:14: test_func
before()

test_func()
PASSED
after()
========================== 1 passed in 0.01 seconds ===========================

Вывод

1. Прежде всего это создано для удобства (как собственно и весь py.test)
2. Сокращает время разработки теста
3. делает тест прозрачнее
4. Уменьшает количество копипаста


Ссылки

http://pytest.org/latest/
http://en.wikipedia.org/wiki/Test_fixture
http://pythontesting.net/

среда, 4 июня 2014 г.

Понимаем UnboundLocalError

Опять же на programmingwats.tumblr.com наткнулся на небольшую особенность питона.

my_str_1 = "1: outside of func"
my_str_2 = "2: outside of func"
def func_1():
    my_str_1 = "1: inside the func"
    my_str_2 = "2: inside the func"
    def func_2():
        print(my_str_1)
        print(my_str_2)
        my_str_1 = "1: inside the class"
    func_2()

func_1()
prints:
Traceback (most recent call last):
  File “<stdin>”, line 1, in <module>
  File “<stdin>”, line 8, in func_1
  File “<stdin>”, line 5, in func_2
UnboundLocalError: local variable ‘my_str_1’ referenced before assignment

В принципе на эту тему написано довольно много постов и погуглив можно спокойно найти объяснение. Но т.к. в свое время эта особенность питона попила у меня много кровушки я решил написать небольшой пост.

В чем причина такого поведения?
Причина заключается в том что питон преобразует переменную my_str_1 в локальную для func_2 и во время вызова функции print получает на вход не инициализированную переменную.
Решения проблемы тривиальные - либо убрать print, либо изменить название переменной, или объявить my_str_1 как глобальную.

Кто хочет более подробно ознакомиться с причинами такого поведения см ссылки, особенно с [2]

Ссылки

[1]https://docs.python.org/2/faq/programming.html#why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value
[2]http://eli.thegreenplace.net/2011/05/15/understanding-unboundlocalerror-in-python/
[3]http://programmingwats.tumblr.com/page/2

вторник, 3 июня 2014 г.

python 2 утечка переменных цикла

Проглядывая programmingwats.tumblr.com нашел небольшую особенность python 2.

Посмотрим на пример:

>>>x = "top"
>>>print(list("a" for x in (1,2)), x)
>>>print(["a" for x in (1,2)], x)

([‘a’, ‘a’], ‘top’)
([‘a’, ‘a’], 2)

Это бага питона, связанная с тем что локальная переменная x, используемая при создании списка становилась доступной в родительском пространстве имен. Думаю понятно чем грозит данная бага.
Данная бага поправлена в python 3.


Ссылки

https://docs.python.org/3/whatsnew/3.0.html#changed-syntax
http://programmingwats.tumblr.com/page/2
http://nbviewer.ipython.org/github/rasbt/python_reference/blob/master/tutorials/key_differences_between_python_2_and_3.ipynb?create=1