├── .gitignore ├── Makefile ├── README ├── notsores_styles.txt ├── notsorest2html.py ├── nsr_lexer.py ├── posts ├── abstraction_layers.txt ├── attribute_access.txt ├── cgroups.txt ├── code │ ├── api.py │ ├── attribute_access.py │ ├── cgroups.py │ ├── clear_cgroups │ ├── incontainer │ ├── lxc_start_time.py │ └── python_match.py ├── func_style_python.txt ├── function_overloading_how_its_made.txt ├── function_overloading_solution.txt ├── gc_road_to_nowhere.txt ├── icc_in_python.txt ├── lxc.txt ├── media │ ├── attribute.dot │ ├── attribute.jpg │ └── attribute.svg ├── metaclasses.txt ├── my_mod.c ├── python_code_afinity.txt ├── python_code_afinity_en.txt ├── python_install.txt ├── python_interfaces.txt ├── python_pattern_matching.txt ├── python_why_use_with.txt ├── pythonic_api_generator.rst ├── tiny_cloud.txt ├── tiny_cloud2.txt ├── tiny_cloud3.txt ├── underunder_madness.txt ├── vm_ut.txt └── with_statement.txt ├── py_struct.py ├── pylintrc └── tests ├── test_nsr_lexer.py └── test_pm.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.pyc 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | metaclasses.html:metaclasses.txt 2 | python notsorest2html.py metaclasses.txt 3 | 4 | attribute_access.html:attribute_access.txt 5 | python notsorest2html.py attribute_access.txt 6 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koder-ua/python-lectures/fffe7388fafc7b8a0b87947a37cc2f1c9c737f35/README -------------------------------------------------------------------------------- /notsores_styles.txt: -------------------------------------------------------------------------------- 1 | Raw = raw[font-size:120%] 2 | RAW = raw[font-size:140%] 3 | -------------------------------------------------------------------------------- /nsr_lexer.py: -------------------------------------------------------------------------------- 1 | import re 2 | from py_struct import Struct 3 | 4 | def indent_level(line): 5 | return len(line) - len(line.lstrip()) 6 | 7 | block_begin_re = re.compile(r'(?P[-a-zA-Z_.]*)(?:\[(?P.*?)\])?:\s*$') 8 | block_sline_re = re.compile(r'(?P[-a-zA-Z_.]*)(?:\[(?P.*?)\])?:\s*(?P.+)$') 9 | block_cut_re = re.compile(r'<---*>\s*$') 10 | 11 | LINE = 'line' 12 | BLOCK_BEGIN = 'block_begin' 13 | DEINDENT = 'deindent' 14 | LIST_ITEM_BEGIN = 'list_item' 15 | EMPTY_LINE = 'empty_line' 16 | 17 | 18 | class NotSoRESTSyntaxError(ValueError): 19 | pass 20 | 21 | 22 | def debug_prn(block_tp, block): 23 | print 24 | if isinstance(block, basestring): 25 | print block_tp 26 | print 27 | print block.encode('utf8') 28 | else: 29 | print block_tp 30 | print 31 | print repr(block) 32 | print 33 | print '~' * 50 34 | 35 | def split_opts(opts): 36 | if opts is None: 37 | return {} 38 | else: 39 | res = {} 40 | for opt in opts.split(','): 41 | if '=' in opt: 42 | opt, val = opt.split('=', 1) 43 | else: 44 | val = True 45 | 46 | if opt in res: 47 | raise NotSoRESTSyntaxError("Multiply opt {0!r}.") 48 | 49 | res[opt] = val 50 | 51 | return res 52 | 53 | class LexLine(Struct): 54 | attrs = 'tp, line, opts, data[, btype]' 55 | 56 | class LexBlock(Struct): 57 | attrs = 'tp, line, opts, data' 58 | 59 | class Block(Struct): 60 | attrs = 'tp, line, opts, data[, style]' 61 | 62 | def lex(fc): 63 | fc = fc.replace('\t', ' ' * 4) 64 | in_block = False 65 | 66 | for line_num, line in enumerate(fc.split('\n')): 67 | try: 68 | 69 | if line.strip().startswith('##'): 70 | continue 71 | 72 | if in_block: 73 | if line.strip() == "": 74 | yield LexLine(EMPTY_LINE, line_num, {}, line) 75 | continue 76 | elif indent_level(line) == 0: 77 | in_block = False 78 | yield LexLine(DEINDENT, line_num, {}, None) 79 | # continue execution to process current line 80 | else: 81 | yield LexLine(LINE, line_num, {}, line) 82 | continue 83 | 84 | # begin of block 85 | bbre = block_begin_re.match(line) 86 | 87 | if bbre: 88 | in_block = True 89 | 90 | opts = split_opts(bbre.group('opts')) 91 | yield LexLine(BLOCK_BEGIN, line_num, opts, bbre.group('btype')) 92 | continue 93 | 94 | # list item begin 95 | if line.strip().startswith('* '): 96 | in_block = True 97 | yield LexLine(LIST_ITEM_BEGIN, line_num, {}, line[2:].strip()) 98 | continue 99 | 100 | if line.strip() == "": 101 | yield LexLine(EMPTY_LINE, line_num, {}, None) 102 | else: 103 | # simple line 104 | yield LexLine(LINE, line_num, {}, line) 105 | except NotSoRESTSyntaxError as exc: 106 | exc.message += " In line num {0} - {1!r}".format(line_num, line) 107 | exc.lineno = line_num 108 | exc.line = line 109 | raise exc 110 | except Exception as exc: 111 | exc.message += "While parse line num {0} - {1!r}".format(line_num, 112 | line) 113 | 114 | 115 | LIST_ITEM = 'list_item' 116 | TEXT_PARA = 'text' 117 | LIST = 'list' 118 | 119 | TEXT_H1 = 'text_h1' 120 | TEXT_H2 = 'text_h2' 121 | TEXT_H3 = 'text_h3' 122 | TEXT_H4 = 'text_h4' 123 | CUT = 'cut' 124 | 125 | 126 | def classify_para(data): 127 | "make paragraph additional classification" 128 | if data.count('\n') == 2: 129 | f, s, t = data.split('\n') 130 | 131 | f = f.rstrip() 132 | s = s.rstrip() 133 | t = t.rstrip() 134 | 135 | if len(f) == len(s) and \ 136 | len(s) == len(t) and \ 137 | len(f) == f.count('=') and \ 138 | len(t) == t.count('='): 139 | return TEXT_H1, s 140 | 141 | elif data.count('\n') == 1: 142 | f, s = data.split('\n') 143 | f = f.rstrip() 144 | s = s.rstrip() 145 | 146 | if len(f) == len(s): 147 | if len(s) == s.count('='): 148 | return TEXT_H2, f 149 | if len(s) == s.count('-'): 150 | return TEXT_H3, f 151 | if len(s) == s.count('~'): 152 | return TEXT_H4, f 153 | elif data.count('\n') == 0: 154 | if block_cut_re.match(data): 155 | return CUT, None 156 | return TEXT_PARA, data 157 | 158 | def _parse(fc): 159 | curr_block = None 160 | lines_for_next_block = [] 161 | 162 | for line in lex(fc): 163 | 164 | #debug_prn(line_tp, data) 165 | 166 | if curr_block is not None: 167 | if line.tp == DEINDENT: 168 | # fix for next problem 169 | # python: 170 | # x = 1 171 | # 172 | # New text begin here with para 173 | # we should do...... 174 | 175 | if len(curr_block.data) >= 3 and \ 176 | curr_block.data[-1] != '' and \ 177 | curr_block.data[-2] == '': 178 | 179 | lines_for_next_block = [curr_block.data[-1]] 180 | curr_block.data = curr_block.data[:-1] 181 | else: 182 | lines_for_next_block = [] 183 | 184 | yield curr_block 185 | 186 | curr_block = None 187 | 188 | elif line.tp == LINE: 189 | curr_block.data.append(line.data) 190 | elif line.tp == EMPTY_LINE: 191 | # empty line is an end of paragraph 192 | if curr_block.tp == TEXT_PARA: 193 | 194 | yield curr_block 195 | curr_block = None 196 | 197 | else: 198 | curr_block.data.append("") 199 | else: 200 | raise NotSoRESTSyntaxError( 201 | ("At line {0}. Item type {1!r} should not happened " + \ 202 | "inside the block").format(line.line, line.tp)) 203 | else: 204 | if line.tp == EMPTY_LINE: 205 | pass 206 | elif line.tp == LINE: 207 | curr_block = LexBlock(TEXT_PARA, line.line, line.opts, 208 | lines_for_next_block + [line.data]) 209 | lines_for_next_block = [] 210 | 211 | elif line.tp == LIST_ITEM_BEGIN: 212 | curr_block = LexBlock(LIST_ITEM, 213 | line.line, 214 | line.opts, 215 | lines_for_next_block + [line.data]) 216 | lines_for_next_block = [] 217 | 218 | elif line.tp == BLOCK_BEGIN: 219 | curr_block = LexBlock(line.data, 220 | line.line + 1, 221 | line.opts, 222 | lines_for_next_block) 223 | lines_for_next_block = [] 224 | 225 | else: 226 | raise ValueError(("Item type {0!r} should not happened " + \ 227 | "outside the block").format(line_tp)) 228 | 229 | if curr_block is not None and curr_block.data != []: 230 | yield curr_block 231 | 232 | OUTPUT_TYPES = \ 233 | [ 234 | TEXT_PARA, 235 | LIST, 236 | TEXT_H1, 237 | TEXT_H2, 238 | TEXT_H3, 239 | TEXT_H4, 240 | CUT 241 | ] 242 | 243 | def parse(fc): 244 | list_items = [] 245 | list_starts = None 246 | 247 | for block in _parse(fc): 248 | 249 | if len(list_items) != 0 and LIST_ITEM != block.tp: 250 | yield Block(LIST, list_starts, {}, list_items) 251 | list_items = [] 252 | list_starts = None 253 | if TEXT_PARA == block.tp: 254 | new_tp, new_data = classify_para("\n".join(block.data)) 255 | 256 | if isinstance(new_data, basestring): 257 | new_data = new_data.rstrip() 258 | 259 | yield Block(new_tp, block.line, block.opts, new_data) 260 | elif LIST_ITEM == block.tp: 261 | list_items.append("\n".join(block.data).rstrip()) 262 | 263 | if list_starts is None: 264 | list_starts = block.line 265 | else: 266 | yield Block(block.tp, block.line, block.opts, 267 | "\n".join(block.data).rstrip()) 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | -------------------------------------------------------------------------------- /posts/abstraction_layers.txt: -------------------------------------------------------------------------------- 1 | ========================================= 2 | Об уровнях абстракций - The Very Last API 3 | ========================================= 4 | 5 | При написании не тривиальных приложений возникает вопрос: 6 | над какими библиотеками делать еще один абстрактный слой, а над какими - нет? 7 | Какие абстракции делать? 8 | 9 | Стоит ли делать прослойку над, например, [SQLAlchemy]? Это же и так 10 | прослойка над SQL и [DBAPI]. Имеет ли смысл делать уровни абстракций над такими 11 | достаточно хорошими и отточенными в смысле интерфейсов библиотеками? 12 | 13 | Ответ очень простой - библиотеки представляют API который должен быть 14 | применим для широкого спектра приложений. Они отображают 15 | низкоуровневые (с точки зрения их API ) вызовы на более высокоуровневый, 16 | но абстрактный интерфейс. Характерный пример - библиотеки передачи сообщений. 17 | Они позволяют не думать о сокетах, упаковке/распаковке 'float'/'int' и т.п., а 18 | просто передавать структуры данных. 19 | 20 | <------------------------------------------------------------------------------> 21 | 22 | Типичный API системы пересылки сообщений выглядит как: 23 | 24 | python: 25 | class Messaging(object): 26 | def send_message_async(self, dest, message_tp, message_data): 27 | pass 28 | 29 | def send_message_sync(self, dest, message_tp, message_data): 30 | pass 31 | 32 | def get_message(self): 33 | pass 34 | 35 | Но программе не нужно посылать никакие сообщения! Ей нужно выполнить 36 | действия - показать пользователю GUI, узнать завершился ли удаленный процесс, 37 | etc. API сообщений, которое было-бы идеально для некоторой программы 38 | выглядит примерно так: 39 | 40 | python: 41 | class MyAPI(object): 42 | 43 | @exception_on_false 44 | def show_ui_message(self, level, text): 45 | return self.messanger.send_message_async(self.UI_PROC_ID, 46 | SHOW_DIALOG, 47 | dict(level=level, text=text)) 48 | 49 | @exception_on_false 50 | def reboot_vm(self, ip): 51 | return self.messanger.send_message_async( 52 | self.get_remote_agent_id(ip), 53 | REBOOT_VM, 54 | None) 55 | 56 | def ls_remote(self, ip, remote_path): 57 | tp, res = self.messanger.send_message_sync( 58 | self.get_remote_agent_id(ip), 59 | EXEC_CMD, 60 | 'ls -l {0}'.format(remote_path)) 61 | if tp == EXECUTION_FINISHED_OK: 62 | return res 63 | else: 64 | raise RuntimeError("Cmd ... finished with error code {0}".\ 65 | format(res)) 66 | 67 | Очень принципиальный момент - конечный API должен отражать задачи, 68 | стоящие перед программой. Четкое отделение основной логики программы от деталей 69 | реализации имеет минимум два очень важных плюса - позволяет сделать главный код 70 | легче для чтения (убирает лишние абстракции) и максимально 71 | отвязать программу от API библиотек (локализовать привязку). 72 | 73 | Это особенная прослойка, это "последняя линия". Если остальные API 74 | предоставляют нам абстракции, то эта прослойка не должна добавлять ничего 75 | лишнего, она избавляет нас от более не нужных абстракций и говорит языком 76 | предметной области программы. 77 | 78 | Вам нужно хранить в базе список фруктов? Сделайте функцию 79 | 'store_fruits'. Такая функция позволить вам перейти от PostgreSQL к 80 | Cassandra, а потом к текстовым файлам (маловероятная ситуация, но не суть) 81 | без влияния на остальную программу. Потому что программе все равно где лежат 82 | данные. Программу интересует только что они сохраняются и восстанавливаются. 83 | 84 | Мы никак не может защититься от изменения требований к программе и вместе 85 | с изменениями требований будет меняться и API, который предоставляет 86 | наш слой абстракции. Но вот изменения в типе базы/структуре базы/ORM 87 | не будет приводить к изменению кода. Если смена БД или ORM - маловероятная 88 | ситуация, то вот добавление нового поля вида 'deleted', означающего, что запись 89 | вроде как удалена и почти нигде не должна использоваться - весьма частый 90 | случай. 91 | 92 | python: 93 | # почти реальный запрос прямо из функции, отвечающей за логики программы 94 | 95 | services = session.query(Service).\ 96 | filter(Service.zone_id == zone_id).\ 97 | filter(Service.service_id == service_id).\ 98 | with_lockmode('update').\ 99 | limit(10).all() 100 | # Чего-чего????? 101 | # комментарий к запросу немного бы спас ситуацию 102 | # но вместо решения проблем с помощью комментирования их лучше не создавать 103 | # Этот код не требует комментариев 104 | 105 | for service in db.get_10_services(zone_id, service_id): 106 | pass 107 | 108 | 109 | # в файле db.py 110 | def get_10_services(self, zone_id, service_id): 111 | return self.session.query(Service).\ 112 | filter(Service.zone_id == zone_id).\ 113 | filter(Service.service_id == service_id).\ 114 | with_lockmode('update').\ 115 | limit(10).all() 116 | 117 | 118 | Еще одна ошибка - попытка сэкономить на таком API и сделать в этом духе: 119 | 120 | python: 121 | import sqlalchemy as sa 122 | 123 | # one Funсtion to rule them all!! 124 | def get_user(session, *opts): 125 | return session.query(User).filter(sa.and_(*opts)).all() 126 | 127 | # этот код уже требует знания что там у нас за sqlalchemy такая 128 | # и на mongo его уже не переписать так-же легко 129 | 130 | # тут "торчат уши" sqlalchemy. Да, мы съекономили 10-20 нажатий клавишь 131 | # на каждый вызов, но это не "the very last API" 132 | 133 | for user in get_user(User.name == 'vasya', User.age > datetime.now()): 134 | pass 135 | 136 | Безусловно у любого абстрагирования есть минимум один существенный минус - 137 | пользуясь им люди хуже понимают что происходит на уровнях ниже. При 138 | возникновении проблем внутри абстракций или еще ниже (например могут быть 139 | проблемы с сетью) на их решение может уйти много времени - а проблемы 140 | возникают постоянно. Во-вторых исчезает контроль над ситуацией. Любая сложная 141 | библиотека несет свои неожиданности в добавок к особенностям нижнего уровня. 142 | В итоге вопрос "почему функция выборки из базы зависает" может стать не 143 | решаемым. 144 | 145 | Я за последнее время видел некоторое количество достаточно опытных 146 | программистов, которые почти ничего не знают про сокеты и TCP, потому что 147 | RabbitMQ и про потоки, потому что фреймфорк подставьтет-тут-свой-фреймфорк. 148 | Нет, это не проявления вселенского зла. Отлично что программирование упрощается, 149 | но эта категория программистов - клиенты обеих проблем сверху. 150 | 151 | Впрочем это уже другой вопрос. А наш вопрос - абстракции :). 152 | 153 | Уровни абстракции должны быть Надежными и легкими для изучения. А ваши 154 | абстракции "последнего рубежа" должны отражать проблемную область программы и 155 | закрывать ими я бы стал почти все нетривиальные внешние зависимости, которые 156 | используются в значительной части кода программы и привносят свои абстракции. 157 | 158 | P.S. Обычно такой подход хорошо работает, но как и все обобщенные рассуждения 159 | эти мысли стоит материализовывать без фанатизма - случаи то разные бывают. 160 | 161 | linklist: 162 | SQLAlchemy http://www.sqlalchemy.org/ 163 | DBAPI http://www.python.org/dev/peps/pep-0249/ 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /posts/cgroups.txt: -------------------------------------------------------------------------------- 1 | Запуск процессов в linux с ограниченнием ресурсов 2 | ================================================= 3 | 4 | Иногда хочется ограничить максимальное количество ресурсов 5 | доступных процессу. Последней пинком стали юнит-тесты из текущего 6 | проекта - из-за ошибок они несколько раз съели все 8Gb ОЗУ и 7 | отправили систему в глубокий своп, откуда она со скрипом 8 | доставалась минут 15. 9 | Полная виртуализация таких случаях избыточна - нужно 10 | что-то по легче. В linux для этого есть [cgroups] (control groups) - 11 | они позволяют поместить процесс (вместе со всеми его потомками) 12 | в контейнер, имеющий доступ только к части ресурсов системы. 13 | На самом деле cgroups умеют больше, чем просто ограничение 14 | ресурсов - тут и счетчики производительности и другая статистика. 15 | 16 | cgroups можно манипулировать вручную или с помощью 17 | [libcgroup]. Последний способ значительно удобнее и по нему есть 18 | отличная [redhat_cgroups_docs|документация] от redhat. Она 19 | обязательна к прочтению - есть несколько 20 | не очевидных моментов (для пользователей ubuntu - в этом дистрибутиве 21 | по умолчанию cgroups монтируются в /sys/fs/cgroups). 22 | 23 | <------------------------------------------------------------------------> 24 | 25 | Краткий пересказ документации на примере ubuntu/libcgroup. 26 | 27 | сgroups основываются на контроллерах и иерархии групп. Процессы 28 | входят в группы, группы организовываются в деревья, где 29 | подгруппы получают часть ресурсов родительских групп, 30 | а контроллеры управляют ресурсами. Контроллер может быть привязан 31 | или только к одной иерархии или к нескольким если он в 32 | каждой единственный контроллер, а каждый процесс может быть привязан 33 | только к одной группе в каждой иерархии, но может быть привязан 34 | к другим группам в других иерархиях. Деревья групп отображаются 35 | на файловую систему и все работа с ними идет через специальные файлы. 36 | 37 | Основные контроллеры позволяют управлять привязкой процессов 38 | к ядрам, ограничивать максимальную долю процессорного 39 | времени, объем используемой памяти и свопа и использование пропускной 40 | способности сети, блочных устройств и другое. 41 | 42 | shell: 43 | # apt-get install cgroup-bin 44 | 45 | После установки в системе появится сервис cgconfig, который 46 | автоматически создает иерархию групп и контроллеров по описанию 47 | из файла /etc/cgconfig.cfg. После правки файла сервис необходимо 48 | перезапустить. Иногда он не может корректно 49 | перезапуститься из-за не отмонтированных при останове 50 | контроллеров. Лечится это ручным отмонтированием 51 | контроллеров: 52 | 53 | shell: 54 | $ sudo service cgconfig restart 55 | [sudo] password for koder: 56 | stop: Job failed while stopping 57 | $ lscgroups 58 | cpuset:/ 59 | cpuset:/sysdefault 60 | $ rm -rf /sys/fs/cgroup/cpuset/sysdefault 61 | $ umount /sys/fs/cgroup/cpuset 62 | $ umount /sys/fs/cgroup 63 | # # теперь можно перезапускать 64 | 65 | Второй интересный сервис - cgred. Он умеет автоматически 66 | распределять процессы по группам в зависимости от правил, описанных 67 | в /etc/cgrules.conf. По умолчанию не запущен. 68 | 69 | После установки cgroup-bin в /sys/fs/cgroup появится группы 70 | из стартового конфига: 71 | 72 | shell: 73 | $ ls /sys/fs/cgroup 74 | cpu cpuacct devices freezer memory 75 | 76 | К каждой иерархии привязан один контроллер с соответствующим именем. 77 | Что-бы поместаить процесс под управление одного из контроллеров нужно 78 | выполнить 3 шага: 79 | 80 | * Сделать группу. sudo mkdir /sys/fs/cgroup/memory/test создаст группу 81 | test под управлением контроллера memory. В папке /sys/fs/cgroup/memory/test 82 | тут же появятся специальные файлы для управления и мониторинга группы. 83 | 84 | * Настроить контроллер. Некоторые контроллеры готовы к работе сразу, 85 | а некотрые требуют предварительной настройки. Правила настройки контроллеров не 86 | очень очевидны и описаны в документации. Для memory минимально необходимо 87 | записать ограничение в файл limit_in_bytes. 88 | 89 | shell: 90 | $ sudo echo 4M > /sys/fs/cgroup/memory/test/limit_in_bytes 91 | 92 | Теперь процессы в группе могут использовать не больше 4M ОЗУ на всех. 93 | 94 | * Перенести процесс в группу - для этого нужно записать его pid в 95 | файл /sys/fs/cgroup/memory/test/tasks. 96 | 97 | Когда группа станет не нужно ее можно удалить через ''rm -rf''. 98 | Что-бы создать подгруппу нужно сделать новую папку в папке группы. 99 | 100 | cgroups-bin предлагает чуть более удобный интерфейс для управления 101 | группами: 102 | 103 | shell: 104 | # cgcreate -g memory:/test # создать группу tt под управлением контроллера memory 105 | 106 | # lscgroup memory:/ 107 | memory:/// 108 | memory:///test 109 | memory:///sysdefault 110 | 111 | 112 | # cgset -r memory.limit_in_bytes=4M memory:test # устанавливаем limit 113 | # cgexec -g memory:test python -c "range(10000000000)" # пытаемся сделать всем плохо 114 | Killed 115 | 116 | # cgdelete memory:test # удалить группу 117 | 118 | # lscgroup memory:/ 119 | memory:/// 120 | memory:///sysdefault 121 | 122 | Чтобы не делать это каждый раз руками можно настроить 123 | cgred или написать автоматизирующий скрипт; я сделал себе такой: 124 | 125 | hide.bash: 126 | #!/bin/bash 127 | #set -x 128 | USAGE="Usage: `basename $0` [-h] [-m MEM_LIMIT_IN_MEGS] [-c CPUS] [-i IOLIMIT] [-u USER] CMD" 129 | 130 | cgroup_mount=/sys/fs/cgroup 131 | 132 | GROUP=app_cell_1 133 | while [ true ] ; do 134 | 135 | OK=1 136 | for cgroup in `lscgroup | awk -F: '{print $1}' | uniq`; do 137 | if [ -d $cgroup_mount/$cgroup/$GROUP ] ; then 138 | OK=0 139 | break 140 | fi 141 | done 142 | 143 | if (( $OK==1 )) ; then 144 | break 145 | fi 146 | 147 | GROUP=app_cell_$RANDOM 148 | done 149 | 150 | MEMLIMIT= 151 | CPUS= 152 | USER= 153 | IOLIMIT= 154 | 155 | while getopts hm:c:i:u: OPT; do 156 | case "$OPT" in 157 | h) 158 | echo $USAGE 159 | exit 0 160 | ;; 161 | c) 162 | CPUS=$OPTARG 163 | echo "CPUS = $CPUS" 164 | ;; 165 | i) 166 | IOLIMIT=$OPTARG 167 | ;; 168 | m) 169 | MEMLIMIT=$OPTARG 170 | ;; 171 | u) 172 | USER=$OPTARG 173 | ;; 174 | \?) 175 | echo $USAGE >&2 176 | exit 1 177 | ;; 178 | esac 179 | done 180 | 181 | shift $(($OPTIND - 1)) 182 | 183 | if [ $# -eq 0 ]; then 184 | echo $USAGE >&2 185 | exit 1 186 | fi 187 | 188 | CMD=$@ 189 | 190 | #cgdelete memory:/$GROUP 2>/dev/null 191 | #cgdelete cpuset:/$GROUP 2>/dev/null 192 | #cgdelete blkio:/$GROUP 2>/dev/null 193 | 194 | CGEXEC_OPT= 195 | LIMITS=0 196 | 197 | if [ -n "$MEMLIMIT" ] ; then 198 | LIMITS=1 199 | cgcreate -g memory:/$GROUP 200 | cgset -r memory.limit_in_bytes=$MEMLIMIT $GROUP 201 | cgset -r memory.memsw.limit_in_bytes=$MEMLIMIT $GROUP 202 | CGEXEC_OPT="$CGEXEC_OPT -g memory:$GROUP" 203 | fi 204 | 205 | if [ -n "$CPUS" ] ; then 206 | LIMITS=1 207 | cgcreate -g cpuset:/$GROUP 208 | cgset -r cpuset.cpus=$CPUS $GROUP 209 | cgset -r cpuset.mems=0 $GROUP 210 | CGEXEC_OPT="$CGEXEC_OPT -g cpuset:$GROUP" 211 | fi 212 | 213 | if [ -n "$IOLIMIT" ] ; then 214 | echo "IO limits not supported yet" >&2 215 | fi 216 | 217 | if (( $LIMITS==0 )) ; then 218 | echo "At least one limit should be set" >&2 219 | echo $USAGE >&2 220 | exit 1 221 | fi 222 | 223 | if [ -e "$USER" ] ; then 224 | cgexec $CGEXEC_OPT $CMD 225 | else 226 | cgexec $CGEXEC_OPT su "$USER" -c "$CMD" 227 | fi 228 | 229 | Используется так: 230 | 231 | shell: 232 | $ sudo ./incontainer -u koder -m 1G -c 0-1 nosetests 233 | # #nosetests ограничен 1 гигабайтом ОЗУ и только двумя ядрами. 234 | 235 | Скрипт каждый раз создает новую группу вида app_cell_случайное_число, 236 | которые нужно периодически удалять, когда в них не останется ни одного процесса. 237 | Например, так: 238 | 239 | hide.bash: 240 | #!/bin/bash 241 | cgroup_mount=/sys/fs/cgroup 242 | 243 | for cgroup in `lscgroup | awk -F: '{print $1}' | uniq` ; do 244 | for group in `ls -1d $cgroup_mount/$cgroup/app_cell_* 2>/dev/null` ; do 245 | #group=$cgroup_mount/$cgroup/$_group 246 | if [ -d $group ] ; then 247 | TC=`cat $group/tasks | wc -l` 248 | if (( $TC==0 )) ; then 249 | gname=$cgroup:/`basename $group` 250 | echo "Group $gname is empty - clear it" 251 | cgdelete -r $gname 252 | fi 253 | fi 254 | done 255 | done 256 | 257 | 258 | P.S. Ковыряясь в cgroup наткнулся на очень интересный системный [prctl|вызов] 259 | 260 | c: 261 | prctl(PR_SET_SECCOMP, 0, 0, 0, 0) 262 | 263 | После него текущий процесс не может делать никакие системные вызовы, 264 | кроме записи/чтения в уже открытые файлы, _exit и sigreturn. Появился в 2.6.33, 265 | вместе с cgroups отличная идея для интерпретации/валидации потенциально 266 | опасных данных, например для реализации интерпретаторов, получающих скрипты из 267 | не доверенных источников. 268 | 269 | linklist: 270 | cgroups http://www.mjmwired.net/kernel/Documentation/cgroups.txt 271 | libcgroup http://libcg.sourceforge.net/ 272 | redhat_cgroups_docs http://docs.redhat.com/docs/en-US/Red_Hat_Enterprise_Linux/6/html/Resource_Management_Guide/ 273 | prctl http://www.kernel.org/doc/man-pages/online/pages/man2/prctl.2.html -------------------------------------------------------------------------------- /posts/code/api.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding:utf8 -*- 3 | 4 | import re 5 | import os 6 | import sys 7 | import json 8 | import urllib 9 | import urllib2 10 | import argparse 11 | import subprocess 12 | 13 | class DataType(object): 14 | 15 | def validate(self, val): 16 | return True 17 | 18 | def from_cli(self, val): 19 | return None 20 | 21 | def arg_parser_opts(self): 22 | return {} 23 | 24 | 25 | class TupleType(DataType): 26 | def __init__(self, *dtypes): 27 | self.dtypes = map(get_data_type, dtypes) 28 | 29 | def validate(self, val): 30 | if not isinstance(val, (list, tuple)): 31 | return False 32 | 33 | if len(self.dtypes) != len(val): 34 | return False 35 | 36 | for dtype, citem in zip(self.dtypes, val): 37 | if not dtype.valid(citem): 38 | return False 39 | 40 | return True 41 | 42 | def from_cli(self, val): 43 | return [dtype.from_cli(citem) for dtype, citem in zip(self.dtypes, val)] 44 | 45 | def arg_parser_opts(self): 46 | return {'nargs': len(self.dtypes), 'type': str} 47 | 48 | 49 | class ListType(DataType): 50 | def __init__(self, dtype): 51 | self.dtype = get_data_type(dtype) 52 | 53 | def validate(self, val): 54 | if not isinstance(val, (list, tuple)): 55 | return False 56 | 57 | for curr_item in val: 58 | if not self.dtype.valid(curr_item): 59 | return False 60 | 61 | return True 62 | 63 | def from_cli(self, val): 64 | return [self.dtype.from_cli(curr_item) for curr_item in val] 65 | 66 | def arg_parser_opts(self): 67 | opts = self.dtype.arg_parser_opts() 68 | opts['nargs'] = '*' 69 | return opts 70 | 71 | 72 | class IntType(DataType): 73 | 74 | def validate(self, val): 75 | return isinstance(val, int) 76 | 77 | def from_cli(self, val): 78 | return int(val) 79 | 80 | def arg_parser_opts(self): 81 | return {'type': int} 82 | 83 | 84 | class StrType(DataType): 85 | 86 | def validate(self, val): 87 | return isinstance(val, basestring) 88 | 89 | def from_cli(self, val): 90 | return val 91 | 92 | def arg_parser_opts(self): 93 | return {'type': str} 94 | 95 | 96 | class IPAddr(DataType): 97 | 98 | def validate(self, val): 99 | if not isinstance(val, basestring): 100 | return False 101 | 102 | ip_vals = val.split('.') 103 | 104 | if len(vals) != 4: 105 | return False 106 | 107 | for ip_val in vals: 108 | try: 109 | vl = int(ip_val) 110 | except ValueError: 111 | return False 112 | 113 | if vl > 255 or vl < 0: 114 | return False 115 | 116 | return True 117 | 118 | def from_cli(self, val): 119 | if not cls.validate(val): 120 | raise ValueError("") 121 | return val 122 | 123 | def arg_parser_opts(self): 124 | return {'type': str} 125 | 126 | 127 | types_map = { 128 | int : IntType, 129 | str : StrType 130 | } 131 | 132 | 133 | def get_data_type(obj): 134 | if isinstance(obj, DataType): 135 | return obj 136 | 137 | if isinstance(obj, list): 138 | assert len(obj) == 1 139 | return ListType(obj[0]) 140 | 141 | if isinstance(obj, tuple): 142 | return TupleType(*obj) 143 | 144 | if issubclass(obj, DataType): 145 | return obj() 146 | 147 | return types_map[obj]() 148 | 149 | 150 | #------------------------------------------------------------------------------- 151 | 152 | class _NoDef(object): 153 | pass 154 | 155 | 156 | class Param(object): 157 | def __init__(self, tp, help, default=_NoDef): 158 | self.name = None 159 | self.tp = get_data_type(tp) 160 | self.help = help 161 | self.default = default 162 | 163 | def validate(self, val): 164 | if val == self.default: 165 | return True 166 | self.tp.validate(val) 167 | 168 | def from_cli(self, val): 169 | return self.tp.from_cli(val) 170 | 171 | def arg_parser_opts(self): 172 | return self.tp.arg_parser_opts() 173 | 174 | def required(self): 175 | return self.default is _NoDef 176 | 177 | 178 | def get_meta_base(cls, meta_cls): 179 | meta = getattr(cls, '__metaclass__', type) 180 | if not issubclass(meta, meta_cls): 181 | raise ValueError("No metaclasses derived from" + \ 182 | " {0} found in {1} bases".\ 183 | format(meta_cls.__name__, cls.__name__)) 184 | 185 | for pos, cbase in enumerate(cls.mro()[1:]): 186 | meta = getattr(cbase, '__metaclass__', type) 187 | #print "issubclass({}, {}) == {}".format(meta, meta_cls, issubclass(meta, meta_cls)) 188 | if not issubclass(meta, meta_cls): 189 | return cls.mro()[pos] 190 | 191 | print cls, meta_cls, cls.mro() 192 | 193 | api_classes = {} 194 | 195 | class APIMeta(type): 196 | def __new__(cls, name, bases, clsdict): 197 | global api_classes 198 | new_cls = super(APIMeta, cls).__new__(cls, name, bases, clsdict) 199 | meta_base = get_meta_base(new_cls, cls) 200 | 201 | if new_cls is not meta_base: 202 | api_classes[meta_base].append(new_cls) 203 | else: 204 | api_classes[meta_base] = [] 205 | 206 | for name, param in new_cls.class_only_params(): 207 | param.name = name 208 | 209 | return new_cls 210 | 211 | @classmethod 212 | def all_classes(cls, cls_base): 213 | return api_classes[cls_base] 214 | 215 | class APICallBase(object): 216 | __metaclass__ = APIMeta 217 | 218 | def __init__(self, **dt): 219 | self._consume(dt) 220 | 221 | @classmethod 222 | def name(cls): 223 | return cls.__name__.lower() 224 | 225 | @classmethod 226 | def all_derived(cls): 227 | return api_classes[get_meta_base(cls, APIMeta)] 228 | 229 | @classmethod 230 | def class_only_params(cls): 231 | param_cls = getattr(cls, 'Params', None) 232 | if param_cls is not None: 233 | for name, param in param_cls.__dict__.items(): 234 | if isinstance(param, Param): 235 | yield name, param 236 | 237 | @classmethod 238 | def all_params(cls): 239 | all_names = set() 240 | for currcls in cls.mro(): 241 | if hasattr(currcls, 'class_only_params'): 242 | for name, param in currcls.class_only_params(): 243 | if name not in all_names: 244 | all_names.add(name) 245 | yield param 246 | 247 | def rest_url(self): 248 | return '/{0}'.format(self.name()) 249 | 250 | @classmethod 251 | def from_dict(cls, data): 252 | obj = cls.__new__(cls) 253 | obj._consume(data) 254 | return obj 255 | 256 | def _consume(self, data): 257 | required_param_names = set() 258 | all_param_names = set() 259 | for param in self.all_params(): 260 | if param.required(): 261 | required_param_names.add(param.name) 262 | all_param_names.add(param.name) 263 | 264 | extra_params = set(data.keys()) - all_param_names 265 | if extra_params != set(): 266 | raise ValueError("Extra parameters {0} for cmd {1}".format( 267 | ','.join(extra_params), self.__class__.__name__)) 268 | 269 | missed_params = required_param_names - set(data.keys()) 270 | if missed_params != set(): 271 | raise ValueError("Missed parameters {0} for cmd {1}".format( 272 | ','.join(missed_params), self.__class__.__name__)) 273 | 274 | parsed_data = {} 275 | for param in self.all_params(): 276 | try: 277 | parsed_data[param.name] = param.from_cli(data[param.name]) 278 | except KeyError: 279 | parsed_data[param.name] = param.default 280 | 281 | self.__dict__.update(parsed_data) 282 | return self 283 | 284 | def to_dict(self): 285 | res = {} 286 | for param in self.all_params(): 287 | res[param.name] = getattr(self, param.name) 288 | return res 289 | 290 | def update_cli(self, subcomand): 291 | pass 292 | 293 | def execute(self): 294 | pass 295 | 296 | def __str__(self): 297 | res = "{0}({{0}})".format(self.__class__.__name__) 298 | params = ["{0}={1!r}".format(param.name, getattr(self, param.name)) 299 | for param in self.all_params()] 300 | return res.format(', '.join(params)) 301 | 302 | def __repr__(self): 303 | return str(self) 304 | 305 | 306 | class Add(APICallBase): 307 | "Add two integers" 308 | class Params(object): 309 | params = Param([int], "list of integers to make a sum") 310 | 311 | def execute(self): 312 | return sum(self.params) 313 | 314 | 315 | class Sub(APICallBase): 316 | "Substitute two integers" 317 | class Params(object): 318 | params = Param((int, int), "substitute second int from first") 319 | 320 | def execute(self): 321 | return self.params[0] - self.params[1] 322 | 323 | 324 | class Ping(APICallBase): 325 | "Ping host" 326 | class Params(object): 327 | ip = Param(IPAddr, "ip addr to ping") 328 | num_pings = Param(int, "number of pings", default=3) 329 | 330 | def execute(self): 331 | res = subprocess.check_stdout('ping -c {0} {1}'.format(self.num_pings, 332 | self.ip)) 333 | return sum(map(float, re.findall(r'time=(\d+\.?\d*)', out))) / \ 334 | self.num_pings 335 | 336 | 337 | def get_arg_parser(): 338 | parser = argparse.ArgumentParser() 339 | subparsers = parser.add_subparsers() 340 | for call in APIMeta.all_classes(APICallBase): 341 | sub_parser = subparsers.add_parser(call.name(), 342 | help=call.__doc__) 343 | sub_parser.set_defaults(cmd_class=call) 344 | for param in call.all_params(): 345 | opts = {'help':param.help} 346 | 347 | if param.default is not _NoDef: 348 | opts['default'] = param.default 349 | 350 | opts.update(param.arg_parser_opts()) 351 | sub_parser.add_argument('--' + param.name.replace('_', '-'), 352 | **opts) 353 | return parser, subparsers 354 | 355 | 356 | import cherrypy as cp 357 | def get_cherrypy_server(): 358 | 359 | class Server(object): 360 | pass 361 | 362 | def call_me(cmd_class): 363 | @cp.tools.json_out() 364 | def do_call(self, opts): 365 | cmd = cmd_class.from_dict(json.loads(opts)) 366 | return cmd.execute() 367 | return do_call 368 | 369 | for call in APIMeta.all_classes(APICallBase): 370 | setattr(Server, 371 | call.name(), 372 | cp.expose(call_me(call))) 373 | 374 | return Server 375 | 376 | 377 | def main(argv=None): 378 | argv = argv if argv is not None else sys.argv 379 | parser, subparsers = get_arg_parser() 380 | 381 | sub_parser = subparsers.add_parser('start-server', 382 | help="Start REST server") 383 | sub_parser.set_defaults(cmd_class='start-server') 384 | 385 | rest_url = os.environ.get('REST_SERVER_URL', None) 386 | 387 | res = parser.parse_args(argv) 388 | cmd_cls = res.cmd_class 389 | 390 | if cmd_cls == 'start-server': 391 | rest_server = get_cherrypy_server() 392 | cp.quickstart(rest_server()) 393 | else: 394 | for opt in cmd_cls.all_params(): 395 | data = {} 396 | try: 397 | data[opt.name] = getattr(res, opt.name.replace('_', '-')) 398 | except AttributeError: 399 | pass 400 | cmd = cmd_cls.from_dict(data) 401 | 402 | if rest_url is None: 403 | print "Execute locally" 404 | print "Res =", cmd.execute() 405 | else: 406 | print "Remote execution" 407 | params = urllib.urlencode({'opts': json.dumps(cmd.to_dict())}) 408 | res = urllib2.urlopen("http://{0}{1}?{2}".format(rest_url, 409 | cmd.rest_url(), 410 | params)).read() 411 | print "Res =", json.loads(res) 412 | 413 | 414 | return 0 415 | 416 | 417 | if __name__ == "__main__": 418 | sys.exit(main(sys.argv[1:])) 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | -------------------------------------------------------------------------------- /posts/code/attribute_access.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import timeit 4 | 5 | NUM = 1024 #1 6 | 7 | prep = """ 8 | class WithGetattribute(object): 9 | def __getattribute__(self, name): 10 | return None 11 | 12 | w_getattribute = WithGetattribute() 13 | 14 | class WithGetattr(object): 15 | def __getattr__(self, name): 16 | return None 17 | 18 | w_getattr = WithGetattr() 19 | 20 | class DProp(object): 21 | def a_get(self): 22 | return None 23 | 24 | def a_set(self): 25 | pass 26 | 27 | a = property(a_get, a_set) 28 | 29 | d_prop = DProp() 30 | 31 | class Prop(object): 32 | def a_get(self): 33 | return None 34 | a = property(a_get) 35 | 36 | prop = Prop() 37 | 38 | class Test(object): 39 | pass 40 | 41 | def get_nested_attr_obj(nested_level): 42 | robj = Test() 43 | obj = robj 44 | for _ in range(nested_level): 45 | obj.attr = Test() 46 | obj = obj.attr 47 | return robj 48 | 49 | def get_nested_attr_class(nested_level): 50 | curr = object 51 | for num in range(nested_level)[::-1]: 52 | curr = type("Ctest", (curr,), {"attr{0}".format(num):None}) 53 | return curr 54 | 55 | a = get_nested_attr_obj(128) 56 | c = get_nested_attr_class(128) 57 | 58 | def func_empty(): 59 | pass 60 | 61 | def func_zero(x = 1): 62 | y = 1 63 | """ 64 | #---------------------------------------------------------------------------------- 65 | attr_access = {} 66 | for num in (1, 2, 4, 16, 32, 64, 128): 67 | attr_access[num] = "a" + '.attr' * num 68 | 69 | cattr1 = "c.attr0" 70 | cattr127 = "c.attr127" 71 | #---------------------------------------------------------------------------------- 72 | func_param_prep = """ 73 | def func_param(x = 1): 74 | y = 1 75 | """ + " x\n" * NUM 76 | func_param_work = "func_param()" 77 | #---------------------------------------------------------------------------------- 78 | func_local_prep = """ 79 | def func_local(x = 1): 80 | y = 1 81 | """ + " y\n" * NUM 82 | func_local_work = "func_local()" 83 | #---------------------------------------------------------------------------------- 84 | func_global_prep = """ 85 | y = 1 86 | def func_global(x = 1): 87 | x = 1 88 | """ + " y\n" * NUM 89 | func_global_work = "func_global()" 90 | #---------------------------------------------------------------------------------- 91 | class TipaCenter(object): 92 | def __init__(self): 93 | self.sz = None 94 | def __mod__(self, sz): 95 | if self.sz is None: 96 | self.sz = sz 97 | return self 98 | else: 99 | return sz.center(self.sz) 100 | 101 | class Formatter(object): 102 | @staticmethod 103 | def center_dot(val, coef, after_dot=1): 104 | val *= coef 105 | v1 = int(val) 106 | v2 = int((val - v1) * (10 ** after_dot)) 107 | return "{0}.{1!s:>0{2}}".format(v1, v2, after_dot) 108 | 109 | @staticmethod 110 | def select_scale(val, scales): 111 | if val < 0: 112 | val = -val 113 | sign = '-' 114 | else: 115 | sign = '' 116 | 117 | for units, coef in scales: 118 | if val * coef > 100.0: 119 | return "{2}{0}{1}".format(int(val * coef), units, sign) 120 | elif val * coef > 10.0: 121 | return "{2}{0:.1f}{1}".format(val * coef, units, sign) 122 | if val * coef > 1.0: 123 | return "{2}{0:.2f}{1}".format(val * coef, units, sign) 124 | 125 | return "{0:.2e}".format(val) 126 | 127 | @staticmethod 128 | def scales(name): 129 | iscales = (('' , 1), 130 | ('m' , 1000), 131 | ('mk', 1000 * 1000), 132 | ('n' , 1000 * 1000 * 1000), 133 | ('p' , 1000 * 1000 * 1000 * 1000), 134 | ('f' , 1000 * 1000 * 1000 * 1000 * 1000)) 135 | for pref, scale in iscales: 136 | yield pref + name, scale 137 | 138 | @classmethod 139 | def to_time(cls, val): 140 | return cls.select_scale(val, cls.scales('s')) 141 | 142 | @classmethod 143 | def format_func(cls, formats): 144 | "helper for print_table" 145 | 146 | def closure(name, val): 147 | "closure" 148 | 149 | frmt = formats.get(name, "%s") 150 | #print name, "->", frmt, val 151 | if isinstance(frmt, str): 152 | return frmt % (val, ) 153 | return frmt(val) 154 | return closure 155 | 156 | @classmethod 157 | def format_table(cls, table, names, formats=None, allign=None): 158 | """pretty-print for tables""" 159 | 160 | max_column_sizes = [0] * len(names) 161 | ffunc = cls.format_func(formats or {}) 162 | 163 | for pos, val in enumerate(names): 164 | max_column_sizes[pos] = max(max_column_sizes[pos], len(val)) 165 | 166 | for line in table: 167 | for pos, val in enumerate(line): 168 | max_column_sizes[pos] = max(max_column_sizes[pos], 169 | len(ffunc(names[pos], val))) 170 | 171 | super_formats = [] 172 | 173 | for pos, size in enumerate(max_column_sizes): 174 | if allign is None: 175 | sft = "%%-%ss" 176 | elif allign[pos] == '<': 177 | sft = "%%-%ss" 178 | elif allign[pos] == '>': 179 | sft = "%%%ss" 180 | elif allign[pos] == 'c': 181 | sft = TipaCenter() 182 | 183 | super_formats.append(sft % size) 184 | 185 | sep = '-' + '-' * (sum(max_column_sizes) + (len(names) - 1) * 3) + '-' 186 | 187 | res = [] 188 | res.append( "\n+" + sep + "+\n") 189 | 190 | line = [] 191 | for frmt, name in zip(super_formats, names): 192 | line.append(frmt % name) 193 | 194 | res.append("| " + " | ".join(line) + " |") 195 | 196 | res.append( "\n|" + sep + "|\n") 197 | 198 | for line in table: 199 | res_line = [] 200 | for frmt, name, val in zip(super_formats, names, line): 201 | res_line.append(frmt % (ffunc(name, val))) 202 | res.append("| " + " | ".join(res_line) + " |\n") 203 | 204 | res.append("+" + sep + "+\n") 205 | 206 | return "".join(res) 207 | 208 | class ADVTimeit(object): 209 | 210 | TIMEIT_TIME = 1 211 | NUM_CALLS_TO_DELTA = 1 212 | 213 | timeit_overhead = None 214 | 215 | def __init__(self, timer=time.time, exp_time=None, num_call_cycles=None ): 216 | self.timer = timer 217 | 218 | if exp_time is None: 219 | exp_time = self.TIMEIT_TIME 220 | self.exp_time = float(exp_time) 221 | 222 | if num_call_cycles is None: 223 | num_call_cycles = self.NUM_CALLS_TO_DELTA 224 | self.num_call_cycles = num_call_cycles 225 | 226 | #self.find_timeit_overhead() 227 | 228 | def find_timeit_overhead(self): 229 | if self.timeit_overhead is None: 230 | number = 1000 * 1000 * 1000 231 | self.__class__.timeit_overhead = timeit.timeit('pass', '', 232 | number=number, 233 | timer=self.timer) / number 234 | sys.stdout.write("self.timeit_overhead =" + str(self.timeit_overhead) + '\n') 235 | 236 | def timeit(self, work, prep, number=None): 237 | number = 1 238 | t = timeit.timeit(work, prep, number=number, timer=self.timer) 239 | 240 | while t < self.exp_time * 0.01: 241 | number *= 10 242 | t = timeit.timeit(work, prep, number=number, timer=self.timer) 243 | 244 | number *= self.exp_time / t 245 | number = int(number) 246 | t = timeit.timeit(work, prep, number=number, timer=self.timer) 247 | 248 | if self.timeit_overhead is not None: 249 | return t / number - self.timeit_overhead 250 | else: 251 | return t / number 252 | 253 | def timeit_with_stat(self, work, prep): 254 | times = [] 255 | 256 | for _ in range(self.num_call_cycles): 257 | times.append(self.timeit(work, prep)) 258 | 259 | times.sort() 260 | 261 | drop = int(round(self.num_call_cycles * 0.1)) 262 | 263 | while drop * 2 >= len(times): 264 | drop -= 1 265 | 266 | if drop != 0: 267 | times = times[drop:-drop] 268 | 269 | mid_time = sum(times) / len(times) 270 | delta = max(mid_time - times[0], times[-1] - mid_time) 271 | 272 | return mid_time, delta 273 | 274 | def timeit_range_scan(self, work, prep, max_sz=14): 275 | 276 | times = [] 277 | 278 | for i in range(max_sz): 279 | number = 2 ** i 280 | if number > 1: 281 | cwork = (work + ';' ) * number 282 | else: 283 | cwork = work 284 | yield number, self.timeit(cwork, prep) / number 285 | 286 | #print "{0:>2} => {1:.2e}".format(i, tm) 287 | 288 | def get_time(work, prep, num1=1, num2=None, zero=0): 289 | atime = ADVTimeit() 290 | 291 | if num1 > 1: 292 | work = (work + ';') * num1 293 | 294 | tm, diff = atime.timeit_with_stat(work, prep) 295 | 296 | tm -= zero 297 | 298 | if num2 is None: 299 | tm /= num1 300 | diff /= num1 301 | else: 302 | tm /= num2 303 | diff /= num2 304 | 305 | return tm, diff 306 | 307 | def show_time(msg, work, prep, num1=1, num2=None, zero=0): 308 | tm, diff = get_time(work, prep, num1, num2, zero) 309 | print "{0:.1e}".format(tm ) 310 | sys.stdout.write("{0:<25} => {1:>6} {2}%\n".format(msg, 311 | Formatter.center_dot(tm, 1000 * 1000 * 1000), 312 | int((diff / tm) * 100))) 313 | 314 | def range_scan(work, prep): 315 | scanner = ADVTimeit(exp_time=1) 316 | for num, tm in scanner.timeit_range_scan(work, prep): 317 | sys.stdout.write("{0:>6} => {1:>8}\n".format(num, Formatter.to_time(tm))) 318 | 319 | class TimeMe(object): 320 | def __init__(self, msg, work, prep, num1=1, num2=None, zero=0): 321 | self.work = work 322 | self.prep = prep 323 | self.num1 = num1 324 | self.num2 = num2 325 | self.zero = zero 326 | self.msg = msg 327 | self.tm = None 328 | self.diff = None 329 | 330 | def get_time(self): 331 | self.tm, diff = get_time(self.work, self.prep, self.num1, self.num2, self.zero) 332 | self.diff = diff / self.tm 333 | return self 334 | 335 | def main(): 336 | var = TimeMe("Global var access", "x", "x=1", NUM) 337 | empty_func = TimeMe("Empty func call", "func_empty()", prep, NUM) 338 | zero_func = TimeMe("", "func_zero()", prep, NUM) 339 | zero_func.get_time() 340 | zero = zero_func.tm 341 | 342 | func_glob_var = TimeMe("Global var from func", func_global_work, func_global_prep, num2=NUM, zero=zero) 343 | func_local_var = TimeMe("Local var from func", func_local_work, func_local_prep, num2=NUM, zero=zero) 344 | int_plus_int = TimeMe("int + int", "a + b", "a=1;b=1", NUM) 345 | getattribute = TimeMe("A.__getattribute__(a, 'b')", "w_getattribute.x", prep, NUM) 346 | data_prop = TimeMe("A.b.__get__(a) data property", "d_prop.a", prep, NUM) 347 | from_obj_dict = TimeMe("a.__dict__['b']", attr_access[1], prep, NUM) 348 | prop = TimeMe("A.b.__get__(a) property", "prop.a ", prep, NUM) 349 | cattr1_time = TimeMe("A.__dict__['b'] with deep " + cattr1, cattr1, prep, NUM) 350 | cattr127_time = TimeMe("A.__dict__['b'] with deep " + cattr127, cattr127, prep, NUM) 351 | getattr_tm = TimeMe("A.__getattr__(a, 'b')", "w_getattr.x", prep, NUM) 352 | attr2_tm = TimeMe("a.b.b", attr_access[2], prep, NUM) 353 | attr4_tm = TimeMe("a.b.b.b.b", attr_access[4], prep, NUM) 354 | attr128_tm = TimeMe("a....b (128)", attr_access[128], prep, NUM) 355 | 356 | all_list = [var, empty_func, func_glob_var, func_local_var, int_plus_int]#, getattribute, data_prop, 357 | #from_obj_dict, prop, cattr1_time, cattr127_time, getattr_tm, attr2_tm, attr4_tm, attr128_tm] 358 | 359 | from_obj_dict.get_time() 360 | for obj in all_list: 361 | obj.get_time() 362 | 363 | table = [] 364 | formatters = { 365 | 'time ns' : lambda x : Formatter.center_dot(x, 1000 * 1000 * 1000), 366 | 'time/(a.b time)' : lambda x : "{0:.1f}".format(x), 367 | "proc ticks" : lambda x : str(int(x)), 368 | "diff %" : lambda x : str(int(x * 100)) 369 | } 370 | 371 | names = ["Operation", "time ns", "diff %", "time/(a.b time)", "proc ticks"] 372 | allign = ["c", ">", ">", ">", ">"] 373 | 374 | for obj in all_list: 375 | obj.get_time() 376 | table.append([obj.msg, obj.tm , obj.diff, obj.tm / from_obj_dict.tm, obj.tm * 2 * 1000 * 1000 * 1000]) 377 | 378 | sys.stdout.write(Formatter.format_table( 379 | table, names, formats=formatters, allign=allign) + "\n") 380 | 381 | 382 | if __name__ == "__main__": 383 | #main() 384 | #range_scan("pass", "") 385 | show_time("c func call", "cadd(1,1)", "import python_imp\nfrom my_mod import add as cadd", num1=1024) 386 | show_time("int add", "1 + 1", "", num1=1024) 387 | 388 | 389 | 390 | -------------------------------------------------------------------------------- /posts/code/cgroups.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | import os.path 5 | 6 | from ctypes import CDLL,c_int,c_void_p,c_char_p,POINTER,Structure,py_object,\ 7 | c_float, c_ulonglong, c_ushort, c_uint8,c_uint 8 | import pycparser 9 | 10 | class DLLWrapper(object): 11 | typesmap = { 12 | 'int': c_int, 13 | 'void': None, 14 | 'const char *': c_char_p, 15 | 'char **': POINTER(c_char_p) 16 | } 17 | 18 | def __init__(self, name): 19 | self.name = name 20 | self.dll = CDLL(self.name) 21 | self.parser = pycparser.CParser() 22 | 23 | def param_type(self, param): 24 | res = param.quals[:] 25 | 26 | pdecl = "" 27 | 28 | while isinstance(param.type, pycparser.c_ast.PtrDecl): 29 | pdecl += '*' 30 | param = param.type 31 | 32 | tp = param.type.type.names[0] 33 | 34 | if pdecl != "": 35 | return " ".join(res + [tp, pdecl]) 36 | 37 | return " ".join(res + [tp]) 38 | 39 | 40 | def __lshift__(self, text): 41 | assert isinstance(text, basestring) 42 | if not text.endswith(';'): 43 | text += ';' 44 | 45 | ccode = self.parser.parse(text).ext[0] 46 | assert isinstance(ccode.type, pycparser.c_ast.FuncDecl) 47 | 48 | fdecl = ccode.type 49 | name = fdecl.type.declname 50 | result = fdecl.type.type.names[0] 51 | 52 | params = [] 53 | for param in fdecl.args.params: 54 | params.append((param.name, self.param_type(param))) 55 | 56 | func = getattr(self.dll, name) 57 | func.restype = self.typesmap[result] 58 | func.params = [self.typesmap[tp] for _, tp in params] 59 | 60 | setattr(self, name, func) 61 | print "{0}({2}) => {1}".format(name, result, ','.join("{1} {0}".format(*i) for i in params)) 62 | 63 | 64 | cgroups = DLLWrapper('/lib/libcgroup.so.1') 65 | 66 | cgroups << "int cgroup_init(void)" 67 | cgroups << "int cgroup_get_subsys_mount_point(const char *controller, char ** mount_point)" 68 | 69 | print cgroups.cgroup_init() 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /posts/code/clear_cgroups: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cgroup_mount=/sys/fs/cgroup 3 | 4 | for cgroup in `lscgroup | awk -F: '{print $1}' | uniq` ; do 5 | for group in `ls -1d $cgroup_mount/$cgroup/app_cell_* 2>/dev/null` ; do 6 | #group=$cgroup_mount/$cgroup/$_group 7 | if [ -d $group ] ; then 8 | TC=`cat $group/tasks | wc -l` 9 | if (( $TC==0 )) ; then 10 | gname=$cgroup:/`basename $group` 11 | echo "Group $gname is empty - clear it" 12 | cgdelete -r $gname 13 | fi 14 | fi 15 | done 16 | done 17 | -------------------------------------------------------------------------------- /posts/code/incontainer: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #set -x 3 | USAGE="Usage: `basename $0` [-h] [-m MEM_LIMIT_IN_MEGS] [-c CPUS] [-i IOLIMIT] [-u USER] CMD" 4 | 5 | cgroup_mount=/sys/fs/cgroup 6 | 7 | GROUP=app_cell_1 8 | while [ true ] ; do 9 | 10 | OK=1 11 | for cgroup in `lscgroup | awk -F: '{print $1}' | uniq`; do 12 | if [ -d $cgroup_mount/$cgroup/$GROUP ] ; then 13 | OK=0 14 | break 15 | fi 16 | done 17 | 18 | if (( $OK==1 )) ; then 19 | break 20 | fi 21 | 22 | GROUP=app_cell_$RANDOM 23 | done 24 | 25 | MEMLIMIT= 26 | CPUS= 27 | USER= 28 | IOLIMIT= 29 | 30 | while getopts hm:c:i:u: OPT; do 31 | case "$OPT" in 32 | h) 33 | echo $USAGE 34 | exit 0 35 | ;; 36 | c) 37 | CPUS=$OPTARG 38 | echo "CPUS = $CPUS" 39 | ;; 40 | i) 41 | IOLIMIT=$OPTARG 42 | ;; 43 | m) 44 | MEMLIMIT=$OPTARG 45 | ;; 46 | u) 47 | USER=$OPTARG 48 | ;; 49 | \?) 50 | echo $USAGE >&2 51 | exit 1 52 | ;; 53 | esac 54 | done 55 | 56 | shift $(($OPTIND - 1)) 57 | 58 | if [ $# -eq 0 ]; then 59 | echo $USAGE >&2 60 | exit 1 61 | fi 62 | 63 | CMD=$@ 64 | 65 | #cgdelete memory:/$GROUP 2>/dev/null 66 | #cgdelete cpuset:/$GROUP 2>/dev/null 67 | #cgdelete blkio:/$GROUP 2>/dev/null 68 | 69 | CGEXEC_OPT= 70 | LIMITS=0 71 | 72 | if [ -n "$MEMLIMIT" ] ; then 73 | LIMITS=1 74 | cgcreate -g memory:/$GROUP 75 | cgset -r memory.limit_in_bytes=$MEMLIMIT $GROUP 76 | cgset -r memory.memsw.limit_in_bytes=$MEMLIMIT $GROUP 77 | CGEXEC_OPT="$CGEXEC_OPT -g memory:$GROUP" 78 | fi 79 | 80 | if [ -n "$CPUS" ] ; then 81 | LIMITS=1 82 | cgcreate -g cpuset:/$GROUP 83 | cgset -r cpuset.cpus=$CPUS $GROUP 84 | cgset -r cpuset.mems=0 $GROUP 85 | CGEXEC_OPT="$CGEXEC_OPT -g cpuset:$GROUP" 86 | fi 87 | 88 | if [ -n "$IOLIMIT" ] ; then 89 | echo "IO limits not supported yet" >&2 90 | fi 91 | 92 | if (( $LIMITS==0 )) ; then 93 | echo "At least one limit should be set" >&2 94 | echo $USAGE >&2 95 | exit 1 96 | fi 97 | 98 | if [ -e "$USER" ] ; then 99 | cgexec $CGEXEC_OPT $CMD 100 | else 101 | cgexec $CGEXEC_OPT su "$USER" -c "$CMD" 102 | fi 103 | 104 | 105 | -------------------------------------------------------------------------------- /posts/code/lxc_start_time.py: -------------------------------------------------------------------------------- 1 | #!/bin/env python 2 | # -*- coding:utf8 -*- 3 | 4 | import time 5 | import socket 6 | import libvirt 7 | 8 | c = libvirt.open("lxc:///") 9 | dom = open("lxc.xml").read() 10 | 11 | t = time.time() 12 | c.createXML(dom, 0) 13 | print "Time 1", time.time() - t 14 | try: 15 | while True: 16 | try: 17 | socket.socket().connect(("192.168.122.190", 22)) 18 | dt = time.time() - t 19 | print "Time 2", dt 20 | 21 | break 22 | except: 23 | # на самм деле будет спать больше 24 | time.sleep(0.001) 25 | 26 | finally: 27 | vm = c.lookupByName('test11') 28 | vm.destroy() 29 | 30 | print "SSH available after", dt, "seconds" 31 | -------------------------------------------------------------------------------- /posts/code/python_match.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | import ast 4 | import inspect 5 | import contextlib 6 | from codegen import to_source 7 | 8 | class VAR(object): 9 | def __init__(self, name): 10 | self.name = name 11 | 12 | def __repr__(self): 13 | return "VAR({0!r}, {1})".format(self.name, hex(id(self))) 14 | 15 | class Match(object): 16 | def __init__(self, otype, **attrs): 17 | 18 | self.otype = otype 19 | self.attrs = {} 20 | 21 | name_to_var = {} 22 | for k,v in attrs.items(): 23 | if isinstance(v, VAR): 24 | if v.name not in name_to_var: 25 | name_to_var[v.name] = v 26 | self.attrs[k] = name_to_var[v.name] 27 | else: 28 | self.attrs[k] = v 29 | 30 | def match_val(self, obj): 31 | assert self.attrs == {} 32 | if self.otype != obj: 33 | return None 34 | else: 35 | return {} 36 | 37 | def __repr__(self): 38 | return str(self) 39 | 40 | def __str__(self): 41 | return "~{0}{1}".format( 42 | self.otype.__name__, 43 | ", ".join("{1}={2!r}".format(k,v) 44 | for k,v in self.attrs.items())) 45 | 46 | def match(self, obj): 47 | if not isinstance(self.otype, type): 48 | return self.match_val(obj) 49 | else: 50 | if not isinstance(obj, self.otype): 51 | return None 52 | return self.match_attrs(obj) 53 | 54 | def match_attrs(self, obj): 55 | result = {} 56 | 57 | for key, val in self.attrs.items(): 58 | try: 59 | rval = getattr(obj, key) 60 | except AttributeError: 61 | return None 62 | 63 | if isinstance(val, VAR): 64 | if hasattr(val, 'val'): 65 | if val.val != rval: 66 | return None 67 | result[val.name] = rval 68 | val.val = rval 69 | 70 | elif isinstance(val, Match): 71 | res = val.match(rval) 72 | if res is None: 73 | return None 74 | 75 | for res_k, res_v in res.items(): 76 | if res_k in result: 77 | if res_v != result[res_k]: 78 | return None 79 | result.update(res) 80 | else: 81 | if isinstance(val, (tuple, list, dict, set, frozenset)): 82 | if not self.match_container(val, result): 83 | return None 84 | else: 85 | if val != rval: 86 | return None 87 | 88 | return result 89 | 90 | def match_container(self, val, result): 91 | if isinstance(val, (list, tuple)): 92 | # unroll/compare 93 | pass 94 | elif isinstance(val, dict): 95 | # dict unroll/compare 96 | pass 97 | elif isinstance(val, (set, frozenset)): 98 | pass 99 | return True 100 | 101 | 102 | def do_match(val, otype, **attrs): 103 | m = Match(otype, **attrs) 104 | return m.match(val) 105 | 106 | class MatchReplacer(ast.NodeTransformer): 107 | def visit_With(self, node): 108 | if isinstance(node.context_expr, ast.Call): 109 | obj = node.context_expr 110 | if isinstance(obj.func, ast.Attribute) and \ 111 | isinstance(obj.func.value, ast.Name) and \ 112 | obj.func.value.id == 'python_match' and \ 113 | obj.func.attr == 'match': 114 | return compile_with(node) 115 | return node 116 | 117 | var_re = re.compile(r"V_([\w_\d]+)$") 118 | 119 | def is_const_node(node): 120 | return isinstance(node, (ast.Num, ast.Str)) 121 | 122 | def build_matcher(node, match_var): 123 | add_exprs = [] 124 | 125 | if is_const_node(node): 126 | mval = ast.Compare( 127 | left=ast.Name(id=match_var, ctx=ast.Load()), 128 | ops=[ast.Eq()], 129 | comparators=[node]) 130 | elif isinstance(node, (ast.Name, ast.Attribute)): 131 | mval = ast.Call( 132 | func=ast.Name(id='isinstance', ctx=ast.Load()), 133 | args=[ast.Name(id=match_var, ctx=ast.Load()), 134 | node], 135 | keywords=[], starargs=None, kwargs=None) 136 | elif isinstance(node, ast.Call): 137 | assert node.args == [] 138 | rkeywords = [] 139 | 140 | args = [ast.Name(id=match_var, ctx=ast.Load()), node.func] 141 | 142 | for keyword in node.keywords: 143 | if isinstance(keyword.value, ast.Name): 144 | vmatch = var_re.match(keyword.value.id) 145 | if vmatch: 146 | var_val = ast.Call(func=ast.Attribute( 147 | value=ast.Name(id="python_match", ctx=ast.Load()), 148 | attr='VAR', 149 | ctx=ast.Load()), 150 | args=[ast.Str(vmatch.group(1))], 151 | keywords=[], starargs=None, kwargs=None) 152 | rkeywords.append(ast.keyword(arg=keyword.arg, 153 | value=var_val)) 154 | continue 155 | rkeywords.append(keyword) 156 | 157 | mval = ast.Call( 158 | func=ast.Attribute( 159 | value=ast.Name(id="python_match", ctx=ast.Load()), 160 | attr='do_match', 161 | ctx=ast.Load()), 162 | args=args, 163 | keywords=rkeywords, 164 | starargs=None, kwargs=None 165 | ) 166 | 167 | else: 168 | raise ValueError("Can't make matcher from " + ast.dump(node)) 169 | return mval 170 | 171 | class VarTransformer(ast.NodeTransformer): 172 | def __init__(self, name_re, callback): 173 | super(VarTransformer, self).__init__() 174 | 175 | self.name_re = name_re 176 | self.callback = callback 177 | 178 | def visit_Name(self, node): 179 | rr = self.name_re.match(node.id) 180 | if rr: 181 | return self.callback(rr) 182 | return node 183 | 184 | def compile_with(node): 185 | body = [] 186 | matched_var_name = node.context_expr.args[0].id 187 | 188 | for expr in node.body: 189 | assert isinstance(expr.value, ast.BinOp) 190 | assert isinstance(expr.value.op, ast.RShift) 191 | 192 | processor = expr.value.right 193 | 194 | check_node = build_matcher(expr.value.left, matched_var_name) 195 | # val = do_match(.....) 196 | match_node = ast.Assign(targets=[ast.Name(id='__vals', ctx=ast.Store())], 197 | value=check_node) 198 | 199 | processor_call = VarTransformer(var_re, 200 | lambda x : ast.Subscript( 201 | value=ast.Name(id='__vals', ctx=ast.Load()), 202 | slice=ast.Index(value=ast.Str(s=x.group(1))), 203 | ctx=ast.Load()) 204 | ).visit(processor) 205 | 206 | raise_result = ast.Raise(type=ast.Call( 207 | func=ast.Attribute( 208 | value=ast.Name(id="python_match", ctx=ast.Load()), 209 | attr='Value', 210 | ctx=ast.Load()), 211 | args=[processor_call], 212 | keywords=[], starargs=None, kwargs=None), 213 | inst=None, tback=None 214 | ) 215 | 216 | 217 | if_node = ast.If(test=ast.Compare( 218 | left=ast.Name(id='__vals', ctx=ast.Load()), 219 | ops=[ast.NotIn()], 220 | comparators=[ast.Tuple( 221 | elts=[ast.Name(id='None', ctx=ast.Load()), 222 | ast.Name(id='False', ctx=ast.Load())], 223 | ctx=ast.Load())] 224 | ), 225 | body=[raise_result], 226 | orelse=[]) 227 | 228 | for new_node in (match_node, if_node): 229 | ast.copy_location(new_node, expr) 230 | ast.fix_missing_locations(new_node) 231 | body.append(new_node) 232 | 233 | node_body = ast.Call( 234 | func=ast.Attribute( 235 | value=ast.Name(id="python_match", ctx=ast.Load()), 236 | attr='marked_match__', 237 | ctx=ast.Load()), 238 | args=node.context_expr.args, 239 | keywords=node.context_expr.keywords, 240 | starargs=node.context_expr.starargs, 241 | kwargs=node.context_expr.kwargs 242 | ) 243 | 244 | new_node = ast.With(body=body, 245 | context_expr=node_body, 246 | optional_vars=node.optional_vars) 247 | 248 | ast.copy_location(new_node, node) 249 | ast.fix_missing_locations(new_node) 250 | 251 | #print ast.dump(node) 252 | return new_node 253 | 254 | class Value(Exception): 255 | def __init__(self, val): 256 | self.val = val 257 | 258 | def update(func): 259 | func_ast = ast.parse(inspect.getsource(func).split("\n", 1)[1]) 260 | new_func = MatchReplacer().visit(func_ast) 261 | #print to_source(new_func) 262 | code = compile(new_func, inspect.getfile(func), 'exec') 263 | fr = sys._getframe(1) 264 | l = fr.f_locals 265 | g = fr.f_globals 266 | eval(code, g, l) 267 | return l[func.__name__] 268 | 269 | def match(x): 270 | raise RuntimeError("Function, which use match should be decorated with update") 271 | 272 | class Res(object): 273 | pass 274 | 275 | @contextlib.contextmanager 276 | def marked_match__(val): 277 | try: 278 | res = Res() 279 | yield res 280 | except Value as val: 281 | res.res = val.val 282 | else: 283 | raise ValueError("Can't found approprite match for {0!r}".format(val)) 284 | 285 | -------------------------------------------------------------------------------- /posts/func_style_python.txt: -------------------------------------------------------------------------------- 1 | ============================= 2 | Функциональный стиль в питоне 3 | ============================= 4 | 5 | *Пост чисто философский* 6 | 7 | Периодическое чтение кусков кода, написанных при обострении хаскеля 8 | головного мозга, выработало у меня четкую ассоциацию: функциональный стиль - 9 | это нечитаемо. Точнее стиль с множеством map/filter/zip. Вот немного 10 | облагороженный пример такого кода (автор считает, что с кодом все ок): 11 | 12 | <-----------------------------------------------------------------------------> 13 | 14 | python: 15 | some_res = ", ".join( 16 | map(str, 17 | filter(None, 18 | map(lambda x: getattr(obj.zip, x, None), 19 | ['a', 'b', 'c', 'd'])))) 20 | 21 | Без переписывания в многострочный вариант ориентироваться в нем вообще 22 | сложно: 23 | 24 | python: 25 | attrs = ['a', 'b', 'c', 'd'] 26 | attr_vals = map(lambda x: getattr(obj.zip, x, None), attrs) 27 | non_none_attrs = filter(None, attr_vals) 28 | some_res = ", ".join(map(str, non_none_attrs)) 29 | 30 | Этот вариант читается лучше уже потому, что понизилась концентрация логики, 31 | и добавились переменные, имена которых служат документацией промежуточным 32 | результатам. Но, IMO, это не главные причины. 33 | 34 | Первом меня на эту мысль натолкнуло наблюдение за девушкой, которая только 35 | начал учить питон и не программировала серьезно до этого. У нее вызывало 36 | затруднение определить какие параметры относятся к какой функции даже в 37 | достаточно простых выражениях, например: 38 | 39 | python: 40 | x = some_func1(a, b, some_func2(c, d), e) 41 | 42 | Понятно, что со временем это прошло, но осадок остался - в выражения где 43 | много вложенных вызовов и скобок сложно быстро соотнести параметры, функции и 44 | удерживать это в голове, пока его анализируешь. Если код не форматирован 45 | построчно, как пример выше, то совсем тяжело. 46 | 47 | Следующий случай - это функциональный стиль в скале. Его чтение у меня 48 | не вызывает того чувства трясины, какое вызывал аналогичный код в 49 | python/haskell. Тот же пример на 'скалапитоне' выглядел бы так: 50 | 51 | python: 52 | some_res = ['a', 'b', 'c', 'd'].map(getattr(obj.zip, _, None))\ 53 | .filter(None).map(str).join(",") 54 | 55 | Если отвлечься от более удобной формы записи [лямбды], то он все равно 56 | читается гораздо проще. Мне кажется дело в том, что он читается линейно слева 57 | направо, а не "вообще изнутри наружу, но местами слева направо", как читается 58 | код в питоне. 59 | 60 | Это относится скорее не к функциональному стилю, а к процедурный vs 61 | ООП, но именно функциональный стиль провоцирует избавление от переменных и 62 | написание множества вложенных вызовов функций. Он как лакмусовая бумажка 63 | вскрывает плохую масштабируемость читаемости процедурных выражений: 64 | 65 | python: 66 | a(x, b(c(), 1, 4), 'd') 67 | # vs 68 | c().b(1, 4).a(x, 'd') 69 | 70 | К сожалению питон чаще всего не позволяет писать сцепленными методами, 71 | поскольку бОльщая часть методов возвращает None вместо self (а именно все, 72 | которые модифицируют объект на месте), а map/filter - функции, а не методы. 73 | 74 | Итого я для себя сменил идею с "функциональный код нечитаем" на 75 | "функциональный код, написанный в процедурном стиле, нечитаем". 76 | 77 | linklist: 78 | лямбды http://ananthakumaran.in/2010/03/29/scala-underscore-magic.html 79 | 80 | 81 | -------------------------------------------------------------------------------- /posts/function_overloading_how_its_made.txt: -------------------------------------------------------------------------------- 1 | ================ 2 | Как это сделано? 3 | ================ 4 | 5 | Отсутствие перегрузки функций - это то что мне всегда 6 | не нравилось в python. Не то что бы без них невозможно было жить, да 7 | и виртуальные методы типа '__len__' сглаживают проблему, но все-таки. 8 | И пусть есть [PEAK.rules], но его синтаксис всегда раздражал. Ну вот как можно 9 | без боли смотреть на это: 10 | 11 | python: 12 | from peak.rules import abstract, when 13 | 14 | @abstract() 15 | def pprint(ob): 16 | """A pretty-printing generic function""" 17 | 18 | @when(pprint, (list,)) 19 | def pprint_list(ob): 20 | print "pretty-printing a list" 21 | 22 | @when(pprint, (int,)) 23 | def pprint_int(ob): 24 | print "pretty-printing an integer" 25 | 26 | #...... 27 | 28 | Во-первых опять нужно придумывать для каждого типа свои 29 | имена функций, во-вторых не по-питоновски много лишних нажатий 30 | клавиш, даже в С++ это - лишнее: ''@when(pprint, ('' :). 31 | 32 | <------------------------------------------------------------------------------> 33 | 34 | Но как-то ничего принципиально лучше придумать не удавалось. 35 | В python 3+ можно будет в конце концов сделать отличную перегрузку 36 | методов через метаклассы, но до его массового использования в 37 | продакшене пока далековато. И вот недавно, при написании статьи 38 | про метаклассы в python 3 и находясь под влияние пересмотра одного 39 | видео с последнего [pycon-videos], пришла в голову мысль которая 40 | оказалась рабочей ( впрочем я бы три раза подумал перед тем как положить 41 | такой код в файл, который будет использовать кто-то другой). 42 | 43 | Ну собственно угадайте как работает написанное ниже (какая магия зашита 44 | в 'method_overloader.overloadable'): 45 | 46 | python: 47 | from method_overloader import overloadable 48 | 49 | @overloadable() 50 | class A(object): 51 | def overloaded_func(self, x): 52 | "int" 53 | return "Integer func called {0}".format(x) 54 | 55 | def overloaded_func(self, x): 56 | "str" 57 | return "String func called {0!r}".format(x) 58 | 59 | def overloaded_func(self, x): 60 | "float" 61 | return "Float func called {0!r}".format(x) 62 | 63 | def overloaded_func(self, x): 64 | "list" 65 | return "List func called {0!r}".format(x) 66 | 67 | t = A() 68 | 69 | print "t.overloaded_func(1) =", t.overloaded_func(1) 70 | print "t.overloaded_func('asas') =", t.overloaded_func("asas") 71 | print "t.overloaded_func(1.1) =", t.overloaded_func(1.1) 72 | print "t.overloaded_func([1, 2, 3]]) =", t.overloaded_func([1, 2, 3]) 73 | 74 | Запускаем - 75 | 76 | raw: 77 | .........$ python tracer.py 78 | t.overloaded_func(1) = Integer func called 1 79 | t.overloaded_func('asas') = String func called 'asas' 80 | t.overloaded_func(1.1) = Float func called 1.1 81 | t.overloaded_func([1, 2, 3]]) = List func called [1, 2, 3] 82 | 83 | 84 | Все это на обычном python без подмены механизма импорта, без ковыряния в [ast] 85 | и т.п. Ответы можно на koder.mail@gmail.com. 86 | 87 | P.S. Если что - в python2.X метаклассе невозможно узнать что происходило в 88 | теле класса, можно только узнать что вышло в итоге, т.е.: 89 | 90 | python: 91 | class M(object): 92 | s = 1 93 | s = 2 94 | 95 | в метакласс класса прийдет в качестве словаря класса {'s' : 2} и узнать 96 | что еще было 's = 1' в метаклассе нельзя. 97 | 98 | linklist: 99 | PEAK.rules http://pypi.python.org/pypi/PEAK-Rules 100 | pycon-videos http://blip.tv/pycon-us-videos-2009-2010-2011 101 | ast http://docs.python.org/library/ast.html 102 | -------------------------------------------------------------------------------- /posts/function_overloading_solution.txt: -------------------------------------------------------------------------------- 1 | ========================= 2 | Решение предыдущего поста 3 | ========================= 4 | 5 | Если вы не читали предыдущий пост - начните с него. 6 | 7 | <--------------------------------------------------------------------> 8 | 9 | 'overloadable' включает трассировку и следит за исполнением тела класса. 10 | Если обнаруживает, что значение исполняемой переменной было изменено - подменяет 11 | ее на объект, управляющий вызовом соответствующей функции в зависимости от параметров. 12 | 13 | python: 14 | # -*- coding:utf8 -*- 15 | import sys 16 | 17 | def overloadable(): 18 | sys.settrace(OverloadTracer().on_event) 19 | def closure(cls): 20 | # удаляем трассировку по выходу из класса 21 | # вообще говоря это можно сделать по 'return' 22 | # но семантика декоратора заставит использовать 23 | # код по назначению 24 | sys.settrace(None) 25 | return cls 26 | return closure 27 | 28 | class OverloadTracer(object): 29 | 30 | def __init__(self): 31 | self.in_class = False 32 | self.prev_locals = {} 33 | 34 | def on_event(self, frame, event, _): 35 | #вызывается при каждом событии трассировки 36 | if event == 'return': 37 | # возврат из блока 38 | self.update_locals(frame.f_locals) 39 | elif event == 'call': 40 | # вызов - открытие блока 41 | # не заходим во вложенные вызовы 42 | if self.in_class: 43 | return None 44 | self.in_class = True 45 | else: 46 | self.update_locals(frame.f_locals) 47 | return self.on_event 48 | 49 | def update_locals(self, loc): 50 | # сравниваем loc с self.prev_locals 51 | for name, prev_val in self.prev_locals.items(): 52 | if name in loc: 53 | # если находим перекрытие функции - подменяем ее на объект, 54 | # управляющий перегрузкой 55 | if loc[name] is not prev_val and \ 56 | ( callable(prev_val) or \ 57 | isinstance(prev_val, OverloadableFunc) )and \ 58 | callable(loc[name]): 59 | loc[name] = self.overload(prev_val, loc[name]) 60 | 61 | # делаем копию текущего состояния тела класса 62 | self.prev_locals = loc.copy() 63 | 64 | @staticmethod 65 | def overload(prev_val, curr_val): 66 | 67 | if not isinstance(prev_val, OverloadableFunc): 68 | overld = OverloadableFunc() 69 | overld.add(prev_val) 70 | prev_val = overld 71 | 72 | prev_val.add(curr_val) 73 | return prev_val 74 | 75 | # класс имитирует функцию с одним аргументом 76 | # и по его типу выбирает соответствующий зарегистрированный обработчик 77 | class OverloadableFunc(object): 78 | def __init__(self): 79 | self.funcs = {} 80 | 81 | def __get__(self, obj, cls): 82 | # для имитации метода объект этого типа 83 | # должен быть свойством 84 | def closure(val): 85 | return self.funcs[type(val).__name__](obj, val) 86 | return closure 87 | 88 | def add(self, func): 89 | # добавляем новую функцию-обработчик 90 | tp_name = func.__doc__.strip().split('\n')[0] 91 | self.funcs[tp_name] = func 92 | 93 | # использование в предыдущем посте 94 | 95 | Поскольку тело класса исполняется то трассировщик может "видеть" как в него добавляются 96 | новые методы или подменяются старые и использовать эту информацию что-бы произвести 97 | перегрузку функций. На всякий случай - код класса исполняется только один раз - при 98 | импорте текущего модуля. По выходу из создания класса трассировка выключается и не 99 | оказывает никакого влияния на его инстанцирование или вызов методов. 100 | 101 | Этот код можно расширить до перегрузки функций (не методов) и добавить поддержку 102 | нескольких параметров. 103 | 104 | Идея с использованием трассировки для расширения синтаксиса была реализована 105 | как минимум в одной широко известной библиотеке - [PEAK.util.decorator]. Она позволяет использовать 106 | декораторы в python до 2.4. 107 | 108 | linklist: 109 | PEAK.util.decorator http://pypi.python.org/pypi/DecoratorTools 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /posts/gc_road_to_nowhere.txt: -------------------------------------------------------------------------------- 1 | 2 | О сборке мусора, деструкторах и разных питонах 3 | ============================================== 4 | 5 | В [этом] посте я писал почему работа с файлами и другими объектами, 6 | требующими гарантированного закрытия должна должна производиться через with. 7 | Однако кроме минуса в виде добавления в код лишнего 8 | уровеня вложенности with еще и решает только часть проблемы - 9 | если код обработки файла не локален (нужно возвращать дескриптор 10 | в вызывающий код или хранить неопределенное время) with не может помочь. 11 | И собственно никто вообще не может помочь - суровая реальность состоит в том, что 12 | python не гарантирует вызов деструктора объекта. Т.е. если вы работаете на CPython, 13 | и не создаете циклических ссылок, то за крайне редкими исключениями деструктор 14 | будет вызываться вовремя. Но если вы используете ironpython/jython/pypy 15 | то ситуация становится совсем печальна. 16 | 17 | <------------------------------------------------------------------> 18 | 19 | Для тех кто, вдруг, не в курсе - немного про деструкторы в С++, как пример 20 | удобных для программиста деструкторов. С++ гарантирует 21 | вызов деструктора у объекта по его уничтожению что бы не произошло 22 | (за исключением полного краха программы/пропадания питания/конца света/etc). 23 | Уничтожение наступает либо по выходу из блока в котором определена 24 | переменная, либо по удалению объекта с помощью оператора 25 | delete при выделении объекта в куче. 26 | 27 | raw: 28 | // C++ 29 | { 30 | // open file 31 | std::fstream fs(fname, std::ios_base::out); 32 | process_file(fs); 33 | } // file is closed before we get beyong this line, no matter what happened 34 | 35 | Гарантированный вызов деструктора - великое программистское благо, позволяющее 36 | не заботится о освобождении некоторых ресурсов, не загромождать код всякими 37 | with/using/try-finally & Co, и даже для объектов в куче есть всякие 38 | unique_ptr. Но все это хорошо работает только в том случае, если 39 | объект имел некую локальную область жизни(впрочем unique_ptr/shared_ptr могут и больше). 40 | Если же объект выделяется из кучи в одном 41 | месте, а освобождается в другом то можно забыть сделать ему 42 | delete - получаем утечку памяти. Не смотря на множество способов 43 | значительно сократить вероятность такого исхода (например [арены]) полностью исключить 44 | его нельзя. 45 | 46 | В качестве решения этой проблемы в современных языках используются сборщики мусора. 47 | Периодически они проходятся по памяти, и тем или иным способом удаляют неиспользуемые объекты. 48 | Все бы хорошо, но у сборщиков 49 | мусора возникают небольшие или большие проблемы с вызовами деструкторов у объектов. 50 | Во-первых все сборщики мусора бессильны перед циклическими ссылками. Если объект a ссылается на b, 51 | а b ссылается на a, 52 | то не понятно в каком порядке вызывать деструкторы. Сначала у a или сначала у b. Если сначала у 53 | a, то при вызове деструктора b его ссылка на a будет не валидна и наоборот. Отсутствие информации 54 | о смысле взаимосвязей между объектами не дает сборщику мусора вызвать деструкторы в 55 | корректном порядке. Копирующие сборщики мусора пошли еще дальше. Они вообще ничего не вызывают, 56 | оставляя программиста один на один с этой проблемой и гордо подписываясь 57 | "современный сборщик мусора". 58 | 59 | Проблема, очевидно, состоит в том что оперативная память это не единственный ресурс 60 | требующий освобождения. Еще, как минимум, есть объекты OS, мютексы, транзакции БД, 61 | и много, много другого. Часть из таких объектов будет через время прикрыта другим кодом 62 | (например транзакции БД - но в любом случае они будут создавать лишнюю нагрузку на 63 | базу все это время), но объекты OS останутся с процессом до его смерти. А ведь бОльшая часть 64 | таких объектов имею локальную область видимости и деструктор был бы 65 | идеальным местом для их освобождения. Таким образом переходя 66 | от С++ к языкам со сборкой мусора, но с недетерменированным вызовом деструкторов мы выигрываем в коде 67 | освобождения памяти, но проигрываем на других объектах. 68 | Теперь вместо delete "где-то там" вы должны написать dispose/using/with/try-finally 69 | прямо тут на каждый объект. Впрочем если файл, например, является не локальным, 70 | то и это не поможет. Для примера можно открыть Экслера и глянуть как в яве правильно 71 | работать с файлами. Страница ужасного кода ради одного маленького файлика. Не уверен, что такая 72 | сборка мусора того стоила. 73 | 74 | Итак посмотрим как себя ведут с деструкторами разные варианты питона. 75 | В качестве примера возьмем вот такую программу: 76 | 77 | python: 78 | import gc 79 | import sys 80 | 81 | class DestTest(object): 82 | def __init__(self, val): 83 | self.val = val 84 | 85 | def __del__(self): 86 | sys.stdout.write(str(self) + '.__del__()\n') 87 | 88 | def __str__(self): 89 | return "DestTest({})".format(self.val) 90 | 91 | def __repr__(self): 92 | return "DestTest({})".format(self.val) 93 | 94 | def simple_var(): 95 | d = DestTest("simple var") 96 | 97 | def mdeleted_var(): 98 | d = DestTest("manyally deleted var") 99 | del d 100 | 101 | def simple_list(): 102 | a = [DestTest("simple list")] 103 | 104 | def internal_exc(): 105 | try: 106 | d = DestTest("exception_func") 107 | raise IndexError() 108 | except: 109 | pass 110 | 111 | def exception_func(): 112 | d = DestTest("exception_func") 113 | raise IndexError() 114 | 115 | def self_ref_list(): 116 | a = [DestTest("self-ref list")] 117 | a.append(a) 118 | 119 | def cycle_refs(): 120 | d1 = DestTest("cycle_ref_obj1") 121 | d2 = DestTest("cycle_ref_obj2") 122 | d3 = DestTest("free_obj") 123 | 124 | d1.ref = d2 125 | d2.ref = d1 126 | d2.ref2 = d3 127 | 128 | simple_var() 129 | sys.stdout.write("after simple var\n") 130 | sys.stdout.write("\n") 131 | 132 | simple_list() 133 | sys.stdout.write("after simple list\n") 134 | sys.stdout.write("\n") 135 | 136 | mdeleted_var() 137 | sys.stdout.write("after manually deleted var\n") 138 | sys.stdout.write("\n") 139 | 140 | self_ref_list() 141 | sys.stdout.write("after self ref list\n") 142 | gc.collect() 143 | sys.stdout.write("after gc.collect()\n") 144 | sys.stdout.write("\n") 145 | 146 | internal_exc() 147 | sys.stdout.write("after internal exc\n") 148 | 149 | try: 150 | exception_func() 151 | except Exception as x: 152 | pass 153 | sys.stdout.write("after exception func\n") 154 | gc.collect() 155 | sys.stdout.write("after gc.collect()\n") 156 | try: 157 | sys.exc_clear() 158 | except AttributeError: 159 | pass 160 | else: 161 | sys.stdout.write("after sys.exc_clear()\n") 162 | sys.stdout.write("\n") 163 | 164 | cycle_refs() 165 | sys.stdout.write("after cycle refs\n") 166 | gc.collect() 167 | sys.stdout.write("after gc.collect()\n") 168 | sys.stdout.write("\n") 169 | 170 | d = DestTest("module var") 171 | 172 | 173 | В идеальном мире вызов деструктора у объектов класса 174 | DestTest во всех этих функциях должен происходить до выхода из соответствующей функции. 175 | Итак что получается: 176 | 177 | python2.7 178 | 179 | raw: 180 | DestTest(simple var).__del__() 181 | after simple var 182 | 183 | DestTest(simple list).__del__() 184 | after simple list 185 | 186 | DestTest(manyally deleted var).__del__() 187 | after manually deleted var 188 | 189 | after self ref list 190 | DestTest(self-ref list).__del__() 191 | after gc.collect() 192 | 193 | after exception func 194 | after gc.collect() 195 | DestTest(exception_func).__del__() 196 | after sys.exc_clear() 197 | 198 | after cycle refs 199 | after gc.collect() 200 | 201 | Exception AttributeError: "'NoneType' object has no attribute 'stdout'" in 202 | ignored 203 | 204 | Более-менее. Деструктор у циклических ссылок не был вызван, как и ожидалось. 205 | Для уничтожения объектов, связанных с исключением, дошедшем до уровня модуля 206 | приходится вызывать sys.clear_exc(), в остальных случаях с исключениями все ок. 207 | Интересно, что питон освободил sys.stdout раньше, чем переменную d, в итоге чего ее деструктор 208 | отработал не корректно (впрочем это поведение не всегда повторяется). 209 | 210 | python3.3 211 | 212 | raw: 213 | DestTest(simple var).__del__() 214 | after simple var 215 | 216 | DestTest(simple list).__del__() 217 | after simple list 218 | 219 | DestTest(manyally deleted var).__del__() 220 | after manually deleted var 221 | 222 | after self ref list 223 | DestTest(self-ref list).__del__() 224 | after gc.collect() 225 | 226 | DestTest(exception_func).__del__() 227 | after exception func 228 | after gc.collect() 229 | 230 | after cycle refs 231 | after gc.collect() 232 | 233 | Exception AttributeError: "'NoneType' object has no attribute 'stdout'" in 234 | ignore 235 | 236 | Почти то-же самое, что и 2.7, только sys.exc_clear() больше не нужно. 237 | 238 | ironpython2.7 239 | 240 | raw: 241 | after simple var 242 | 243 | after simple list 244 | 245 | after manually deleted var 246 | 247 | after self ref list 248 | DestTest(self-ref list).__del__() 249 | DestTest(manyally deleted var).__del__() 250 | DestTest(simple list).__del__() 251 | DestTest(simple var).__del__() 252 | after gc.collect() 253 | 254 | after exception func 255 | DestTest(exception_func).__del__() 256 | after gc.collect() 257 | after sys.exc_clear() 258 | 259 | after cycle refs 260 | DestTest(free_obj).__del__() 261 | DestTest(cycle_ref_obj2).__del__() 262 | DestTest(cycle_ref_obj1).__del__() 263 | after gc.collect() 264 | 265 | DestTest(module var).__del__() 266 | 267 | Удаляет все, но только при сборке мусора, до тех пор все объекты живут где-то в памяти. 268 | 269 | И - встречаем победителя. Сама лаконичность или 270 | мир всем вашим деструкторам от нашей Java и копирующего сборщика мусора: 271 | jython2.7a2 - Java HotSpot(TM) Client VM (Oracle Corporation) on java1.7.0_07 272 | 273 | raw: 274 | after simple var 275 | 276 | after simple list 277 | 278 | after manually deleted var 279 | 280 | after self ref list 281 | after gc.collect() 282 | 283 | after exception func 284 | after gc.collect() 285 | after sys.exc_clear() 286 | 287 | after cycle refs 288 | after gc.collect() 289 | 290 | Правда в ява есть и другие варианты сборщика мусора, может там чуть по-лучше. 291 | 292 | А вывод все тот-же. По возможности - используйте with. По невозможности - 293 | попробуйте поправить код, так что-бы можно было использовать with. Иначе - 294 | аккуратно вызывайте деструкторы руками 295 | 296 | P.S. Как я люблю, когда мне предлагают делать какую-то рутинную работу в коде 297 | "аккуратно". 298 | 299 | linklist: 300 | этом http://koder-ua.blogspot.com/2012/10/python-with.html 301 | арены http://en.wikipedia.org/wiki/Region-based_memory_management 302 | -------------------------------------------------------------------------------- /posts/media/attribute.dot: -------------------------------------------------------------------------------- 1 | digraph AttrAccess{ 2 | fontsize = 24; 3 | start [label="a.b\nres = NULL"]; 4 | is_special [label="b - special method?\n__some_builtin_method__"]; 5 | get_from_pyobject [label="res = PyObject->some_field"]; 6 | start_getattribute [shape="diamond", label="__getattribute__\noverloaded?"]; 7 | user_getattribute [label="res = A.__getattribute__(a, 'b')"]; 8 | is_data_descr [shape="diamond", label="'b' in A\nand\nA.b is a data descriptor?"]; 9 | data_descr [label="res = A.b(a)"]; 10 | is_in_dict [shape="diamond", label="'b' in a.__dict__?"]; 11 | in_dict [label="res = a.__dict__['b']"]; 12 | is_any_descr [shape="diamond", label="'b' in A\nand\nA.b is a descriptor?"]; 13 | any_descr [label="res = A.b(a)"]; 14 | is_A_has_b [shape="diamond", label="hasattr(A, 'b')?"]; 15 | A_has_b [label="res = A.b"]; 16 | getattr [label="ClearException()\nres = A.__getattr__(a, 'b')"]; 17 | is_getattr [shape="diamond", label="hasattr(A,'__getattr__')\nand\n( ExceptionTp == AttributeError\nor\nres == NULL)"]; 18 | 19 | start -> is_special; 20 | 21 | is_special -> get_from_pyobject [label="yes"]; 22 | is_special -> start_getattribute [label="no"]; 23 | 24 | get_from_pyobject -> end; 25 | 26 | start_getattribute -> user_getattribute [label="yes"]; 27 | start_getattribute -> is_data_descr [label="no"]; 28 | 29 | user_getattribute -> is_getattr; 30 | 31 | is_data_descr -> data_descr [label="yes"]; 32 | is_data_descr -> is_in_dict [label="no"]; 33 | data_descr -> is_getattr; 34 | 35 | is_in_dict -> in_dict [label="yes"]; 36 | is_in_dict -> is_any_descr [label="no"]; 37 | 38 | in_dict -> end; 39 | 40 | is_any_descr -> any_descr [label="yes"]; 41 | is_any_descr -> is_A_has_b [label="no"]; 42 | 43 | any_descr -> is_getattr; 44 | 45 | is_A_has_b -> is_getattr [label="no"]; 46 | is_A_has_b -> A_has_b [label="yes"]; 47 | 48 | A_has_b -> end; 49 | 50 | is_getattr -> getattr [label="yes"]; 51 | is_getattr -> end [label="no"]; 52 | 53 | getattr -> end; 54 | } 55 | -------------------------------------------------------------------------------- /posts/media/attribute.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koder-ua/python-lectures/fffe7388fafc7b8a0b87947a37cc2f1c9c737f35/posts/media/attribute.jpg -------------------------------------------------------------------------------- /posts/my_mod.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* 4 | EXT_BUILD 5 | 6 | #set ADD_SOURCE_FILES= 7 | #set ADD_LIBS= 8 | #set INCLUDE_DIRS= 9 | #set LIB_DIRS= 10 | #set COMPILER_FLAGS= 11 | 12 | #build_commands: 13 | # g++ -shared -fPIC {source_files} -o {so_name} 14 | 15 | export int add(int, int) 16 | export int sub(int, int) 17 | 18 | END_EXT_BUILD 19 | */ 20 | 21 | extern "C" int add(int x, int y) 22 | { 23 | return x + y; 24 | } 25 | 26 | extern "C" int sub(int x, int y) 27 | { 28 | return x - y; 29 | } 30 | 31 | -------------------------------------------------------------------------------- /posts/python_code_afinity.txt: -------------------------------------------------------------------------------- 1 | ============================================================================================ 2 | Python, processor affinity или как существенно ускорить некоторые программы, ничего не делая 3 | ============================================================================================ 4 | 5 | ~Всем, кто увидел первую версию поста - цифры были кривые из-за turbo boost~ 6 | 7 | Возьмем простой пример tcp клиента и сервера на python. 8 | Сервер создает пул из N потоков и ждет соединений с клиентом. Получив 9 | соединение передает его на обработку в пул. На каждом соединении сервер ждет от 10 | клиента строку данных и имитирует некую обработку. При получении 'bye\n' сервер 11 | завершает обработку клиента. 12 | 13 | Клиент открывает N соединений с сервером и генерирует на них нагрузку. 14 | Общий объем нагрузки за один запуск клиента фиксирован. 15 | 16 | <-----------------------------------------------------------------------------> 17 | 18 | python: 19 | data = ' ' * 100 + '\x0A' 20 | def client(th_count): 21 | sockets = [] 22 | for i in range(th_count): 23 | sock = socket.socket() 24 | 25 | for cnt in range(3): 26 | try: 27 | sock.connect(host_port) 28 | break 29 | except socket.error: 30 | if cnt == 2: 31 | raise 32 | time.sleep(0.1) 33 | 34 | sockets.append(sock) 35 | 36 | for i in range(NUM_PACKETS): 37 | sock = random.choice(sockets) 38 | sock.send(data) 39 | 40 | for sock in sockets: 41 | sock.send('bye\x0A') 42 | 43 | python: 44 | def server(th_count): 45 | def process_client(sock): 46 | num = 0 47 | while True: 48 | msg = "" 49 | while not msg.endswith('\n'): 50 | msg += sock.recv(1) 51 | 52 | if msg == 'bye\n': 53 | break 54 | 55 | for i in range(serv_tout): 56 | pass 57 | 58 | num += 1 59 | 60 | s = socket.socket() 61 | s.bind(host_port) 62 | s.listen(5) 63 | with ThreadPoolExecutor(max_workers=th_count) as pool: 64 | fts = [] 65 | 66 | for i in range(th_count): 67 | sock,_ = s.accept() 68 | fts.append(pool.submit(process_client, sock)) 69 | 70 | for ft in fts: 71 | ft.result() 72 | 73 | 74 | Замеряем сколько времени нужно для одного прогона этой системы с N=4 75 | 76 | shell: 77 | $ python mt_test.py client 4 & time python mt_test.py server 4 78 | 79 | real 0m8.342s 80 | user 0m7.789s 81 | sys 0m6.587s 82 | 83 | А теперь почти то же самое, но разрешим операционной системе исполнять все 84 | потоки сервера только на одном ядре из 8ми доступных 85 | 86 | shell: 87 | $ python mt_test.py client 4 & time taskset 0x00000001 python mt_test.py server 4 88 | 89 | real 0m4.663s 90 | user 0m3.186s 91 | sys 0m0.762s 92 | 93 | Уличная магия в действии - многопоточная программа исполнилась в 2 раза 94 | быстрее, когда мы разрешили использовать только одно ядро процессора. 95 | 96 | Почему такое получилось? Во-первых [GIL] - сколько бы потоков в питоне мы не 97 | создали, они всегда будут исполняться питоновский код только по очереди. Питон 98 | не позволяет двум потокам одного процесса одновременно исполнять свой байтокод. 99 | 100 | Таким образом для этой программы(как и для 99% программ на питоне) никакого 101 | заметного ускорения от использования более одного ядра ожидать и не приходится. 102 | Все чисто питоновские программы [конкурентны, но не параллельны]. А 103 | ~конкурентной~ такой системе от изменения количества ядер в процессоре не 104 | холодно и не жарко (почти). 105 | 106 | Почему же скорость исполнения падает, если использовать более одного ядра? 107 | Причин две: 108 | 109 | * Излишние переключения между потоками 110 | * Постоянная война за кеш с другими потоками в системе и друг с другом 111 | 112 | Итак что происходит: пусть у нас есть два потока, один из которых(первый) 113 | сейчас обрабатывает принятые данные, а второй ожидает данных от сокета. 114 | Наконец второй поток получает данные и ОС готова продолжить его исполнение. 115 | Она смотрит на доступные ядра, видит что первое ядро занято первым потоком 116 | и запускает второй поток на исполнение на втором ядре. Второй поток запускается 117 | и первым делом пытается захватить GIL. Неудача - GIL захвачен первым потоком. 118 | Второй поток снова засыпает, ожидая освобождения GIL. 119 | 120 | В итоге операционная система, которая понятия не имеет ни о каких GIL, сделала 121 | кучу лишней работы (переключение контекста достаточно дорогая операция). Правда 122 | заметная часть этой работы делалась вторым ядром, так что происходила параллельно 123 | и почти не мешала исполняться первому потоку. Почти - потому что второе ядро все 124 | равно занимало шину памяти. Ситуация становится хуже, если в системе есть [HT] - 125 | в этом случае второе ядро может делить с первым исполняемые блоки процессора и 126 | все эти лишние переключения будут серьезно замедлять исполнение первого потока. 127 | 128 | Вторая проблема состоит в том, что второй поток переброшен на исполнение 129 | на второе ядро процессора. Когда первый поток освободит GIL, то второй поток 130 | продолжит исполнение на втором ядре, потому что ОС знает, что кеши первого и 131 | второго уровня у каждого ядра свои и старается без причин не гонять потоки 132 | между ядрами. В итоге все имеющиеся потоки "размазываются" по доступным ядрам. 133 | Съедая в сумме 100% одного ядра, они превращают это в 12.5% на каждом из 8ми ядер. 134 | При этом в промежутках пока питоновские потоки ждут GIL на эти ядра вклиниваются 135 | другие потоки из системы, постоянно вытесняя наши данные из кеша. 136 | 137 | В итоге питоновские потоки постоянно "бегают" по ядрам. Данные копируются 138 | в кеш и из кеша, а каждый кеш-промах стоит до тысяч тактов на обращение к RAM. 139 | Даже по меркам питона - серьезные нагрузки. 140 | 141 | Выставив привязку к одному ядру мы убиваем сразу двух зайцев. Во-первых 142 | сокращаем количество переключений контекста, поскольку ОС будет заметно реже 143 | запустить на исполнение второй поток, если единственное доступное ему ядро 144 | сейчас занято. Во-вторых другие потоки не будут вклиниваться на это ядро, 145 | тем самым мы уменьшим интенсивность обмена данными между кешем и ОЗУ 146 | (питоновские потоки в одном процессе используют заметную часть данных совместно). 147 | 148 | Итоги тестирования. 149 | 150 | * SUM - общее затраченное время 151 | * SYS - время, затраченное операционной системой 152 | * USR - время, затраченное в пользовательском режиме 153 | * XXX_AF - XXX в случае, если выставлена привязка к одному ядру 154 | * DIFF - отличие в процентах между XXX и XXX_AF 155 | 156 | Все измерения сделаны на Core i7-2630QM@800MHz, python 2.7.5, x64, ubuntu 13.10 с 157 | усреднением по 7ми выборкам. Долгая война с turbo boost окончилась принудительным 158 | переводом процессора в режим минимальных частот. 159 | 160 | raw: 161 | ------------------------------------------------------------------------- 162 | | Потоки | SUM SUM_AF %DIFF | SYS SYS_AF %DIFF | USR USR_AF %DIFF | 163 | ------------------------------------------------------------------------- 164 | | 1 | 3.35 3.55 -5 | 0.54 0.52 4 | 2.78 3.03 -8 | 165 | | 2 | 7.26 4.63 36 | 4.91 0.67 86 | 5.10 2.95 42 | 166 | ------------------------------------------------------------------------- 167 | | 4 | 8.28 4.90 41 | 6.58 0.76 88 | 7.37 3.14 57 | 168 | | 8 | 7.96 5.00 37 | 6.49 0.84 87 | 7.32 3.15 57 | 169 | ------------------------------------------------------------------------- 170 | | 16 | 9.77 5.88 40 | 6.53 0.73 89 | 7.01 3.15 55 | 171 | | 32 | 9.73 6.84 30 | 6.54 0.81 88 | 7.06 3.04 57 | 172 | ------------------------------------------------------------------------- 173 | 174 | Прогон теста по VTune показывает, что после выставления привязки количество кеш промахов 175 | уменьшается примерно в 5 раз, а количество переключений контекста - в 40. В ходе экспериментов 176 | обнаружилась еще одна интересная вещь - при выставлении привязки к одному ядру более эффективно 177 | используется turbo boost, что тоже ускорит вашу программу, если больше никто не грузит систему. 178 | Для этого теста turbo boost был заблокирован. 179 | 180 | Будет ли что-то подобное в других случаях? Хотя данная программа и обрабатывает данные, 181 | приходящие из сокета, но данные приходят быстрее, чем она может их обработать. Таким образом 182 | она является [CPU bounded]. Если программа будет в основном занята ожиданием данных, то 183 | выставления привязки к ядру даст меньше ускорения - ОС будет меньше перебрасывать потоки между 184 | ядрами. Чем выше нагрузка на процессор, тем больше будет выигрыш. 185 | 186 | Когда мы можем получить замедление: 187 | 188 | * если в программе есть места, которые действительно параллельны, например 189 | часть работы делается С/С++ библиотекой, которая отпускает GIL 190 | * Или вы используете jython или ironpython 191 | * Если вы используете multiprocessing/ProcessPoolExecutor, которые запускают 192 | отдельные процессы и не имеют проблем с GIL. Привязка в линуксе наследуется 193 | потоками/процессами. Так что для дочерних процессов ее нужно или отменить, или 194 | выделить по своему ядру на каждый процесс. 195 | * В некоторых однопоточных системах, например при использовании gevent 196 | 197 | P.S. В 3.3 поведение все то-же. 198 | 199 | P.S.S. Нашел уже готовую [статью] про тоже самое. 200 | 201 | linklist: 202 | GIL http://habrahabr.ru/post/84629 203 | статью http://habrahabr.ru/post/141181 204 | конкурентны, но не параллельны http://vimeo.com/49718712 205 | CPU bounded http://stackoverflow.com/questions/868568/cpu-bound-and-i-o-bound 206 | HT http://ru.wikipedia.org/wiki/Hyper-threading 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | -------------------------------------------------------------------------------- /posts/python_code_afinity_en.txt: -------------------------------------------------------------------------------- 1 | ========================================= 2 | Processor affinity and python performance 3 | ========================================= 4 | 5 | Возьмем простой пример tcp клиента и сервера на python. 6 | Сервер создает пул из N потоков и ждет соединений с клиентом. Получив 7 | соединение передает его на обработку в пул. На каждом соединении сервер ждет от 8 | клиента строку данных и имитирует некую обработку. При получении 'bye\n' сервер 9 | завершает обработку клиента. 10 | 11 | We would start from example of simple python tcp server and client. 12 | Server creates thread pool and waits for client to connect. When connection 13 | is archiver server passes it to pool for processing. Process function reads 14 | one line from socket and simulate some cpu load. After receiving 'bye\n' server 15 | closes connection. 16 | 17 | Клиент открывает N соединений с сервером и генерирует на них нагрузку. 18 | Общий объем нагрузки за один запуск клиента фиксирован. 19 | 20 | Client creates N connections and generate some load fixed-size load. 21 | 22 | <-----------------------------------------------------------------------------> 23 | 24 | python: 25 | data = ' ' * 100 + '\x0A' 26 | def client(th_count): 27 | sockets = [] 28 | for i in range(th_count): 29 | sock = socket.socket() 30 | 31 | for cnt in range(3): 32 | try: 33 | sock.connect(host_port) 34 | break 35 | except socket.error: 36 | if cnt == 2: 37 | raise 38 | time.sleep(0.1) 39 | 40 | sockets.append(sock) 41 | 42 | for i in range(NUM_PACKETS): 43 | sock = random.choice(sockets) 44 | sock.send(data) 45 | 46 | for sock in sockets: 47 | sock.send('bye\x0A') 48 | 49 | python: 50 | def server(th_count): 51 | def process_client(sock): 52 | num = 0 53 | while True: 54 | msg = "" 55 | while not msg.endswith('\n'): 56 | msg += sock.recv(1) 57 | 58 | if msg == 'bye\n': 59 | break 60 | 61 | for i in range(serv_tout): 62 | pass 63 | 64 | num += 1 65 | 66 | s = socket.socket() 67 | s.bind(host_port) 68 | s.listen(5) 69 | with ThreadPoolExecutor(max_workers=th_count) as pool: 70 | fts = [] 71 | 72 | for i in range(th_count): 73 | sock,_ = s.accept() 74 | fts.append(pool.submit(process_client, sock)) 75 | 76 | for ft in fts: 77 | ft.result() 78 | 79 | 80 | Замеряем сколько времени нужно для одного прогона этой системы с N=4 81 | 82 | Timings for 4 threads: 83 | 84 | shell: 85 | $ python mt_test.py client 4 & time python mt_test.py server 4 86 | 87 | real 0m8.342s 88 | user 0m7.789s 89 | sys 0m6.587s 90 | 91 | А теперь почти то же самое, но разрешим операционной системе исполнять все 92 | потоки сервера только на одном ядре из 8ми доступных 93 | 94 | Same timings, but now OS allowed to run all python threads on only 1 core 95 | (from 8 available): 96 | 97 | shell: 98 | $ python mt_test.py client 4 & time taskset 0x00000001 python mt_test.py server 4 99 | 100 | real 0m4.663s 101 | user 0m3.186s 102 | sys 0m0.762s 103 | 104 | Уличная магия в действии - многопоточная программа исполнилась в 2 раза 105 | быстрее, когда мы разрешили использовать только одно ядро процессора. 106 | 107 | Results don't really looks obvious - 4 thread program gets faster, when 108 | executed on only 1 core, enstead of 8. 109 | 110 | Почему такое получилось? Во-первых [GIL] - сколько бы потоков в питоне мы не 111 | создали, они всегда будут исполняться питоновский код только по очереди. Питон 112 | не позволяет двум потокам одного процесса одновременно исполнять свой байтокод. 113 | 114 | There two main reasons, which leads to such result - first of all we have 115 | the GIL. Don't matter how many python threads are ready to run - only one of 116 | them would be allowed to execute python code at any particular moment. 117 | 118 | Таким образом для этой программы(как и для 99% программ на питоне) никакого 119 | заметного ускорения от использования более одного ядра ожидать и не приходится. 120 | Все чисто питоновские программы [конкурентны, но не параллельны]. А 121 | ~конкурентной~ такой системе от изменения количества ядер в процессоре не 122 | холодно и не жарко (почти). 123 | 124 | As result we sould not expect any performance improvement just by running 125 | this code on multycode computer. All pure-python programs are [concurrent, but 126 | not parallel]. And performance mostly not depend on how many codes you have. 127 | 128 | Почему же скорость исполнения падает, если использовать более одного ядра? 129 | Причин две: 130 | 131 | This explains why performance don't degradate. Why it improves? There two 132 | main reasons: 133 | 134 | * Излишние переключения между потоками 135 | * Постоянная война за кеш с другими потоками в системе и друг с другом 136 | 137 | * Extra thread context switch 138 | * Continuous fight for the CPU cache with other threads 139 | 140 | 141 | Итак что происходит: пусть у нас есть два потока, один из которых(первый) 142 | сейчас обрабатывает принятые данные, а второй ожидает данных от сокета. 143 | Наконец второй поток получает данные и ОС готова продолжить его исполнение. 144 | Она смотрит на доступные ядра, видит что первое ядро занято первым потоком 145 | и запускает второй поток на исполнение на втором ядре. Второй поток запускается 146 | и первым делом пытается захватить GIL. Неудача - GIL захвачен первым потоком. 147 | Второй поток снова засыпает, ожидая освобождения GIL. 148 | 149 | Lets take a close look what happend on two threads example. First thread 150 | processes data at the moment and second wait for data from socket. At last 151 | second thread socket gets some data and ready to continus execution. OS take 152 | a look on available CPU cores and found, that first core is busy processing 153 | first thread. So it schedules second thread to second core. Second threads 154 | starts and first of all try to acquire GIL and fails - GIL holds by first 155 | thread. So it sleeps again, now waiting for GIL to be released. 156 | 157 | В итоге операционная система, которая понятия не имеет ни о каких GIL, сделала 158 | кучу лишней работы (переключение контекста достаточно дорогая операция). Правда 159 | заметная часть этой работы делалась вторым ядром, так что происходила параллельно 160 | и почти не мешала исполняться первому потоку. Почти - потому что второе ядро все 161 | равно занимало шину памяти. Ситуация становится хуже, если в системе есть [HT] - 162 | в этом случае второе ядро может делить с первым исполняемые блоки процессора и 163 | все эти лишние переключения будут серьезно замедлять исполнение первого потока. 164 | 165 | As result OS, which has no clue about GIL semantics, doing a lot of extra 166 | works. Part of this work is done by second CPU core and should not really 167 | slowes down first thread. But in any case it creates extra load on memory bus 168 | anf cache. In case if processor has [HT] situation may be worse. 169 | 170 | Вторая проблема состоит в том, что второй поток переброшен на исполнение 171 | на второе ядро процессора. Когда первый поток освободит GIL, то второй поток 172 | продолжит исполнение на втором ядре, потому что ОС знает, что кеши первого и 173 | второго уровня у каждого ядра свои и старается без причин не гонять потоки 174 | между ядрами. В итоге все имеющиеся потоки "размазываются" по доступным ядрам. 175 | Съедая в сумме 100% одного ядра, они превращают это в 12.5% на каждом из 8ми ядер. 176 | При этом в промежутках пока питоновские потоки ждут GIL на эти ядра вклиниваются 177 | другие потоки из системы, постоянно вытесняя наши данные из кеша. 178 | 179 | But the real problem is that second thread is now scheduled for execution 180 | on second core. When GIL would be released OS would not migrate this thread 181 | on first core, because it knews about caches and try to not move thread to ither 182 | code without real reason. As result all python threads, which in sum can 183 | produces 100% load on single core, are creates 12.5% load on each of 8 184 | available cores. 185 | 186 | В итоге питоновские потоки постоянно "бегают" по ядрам. Данные копируются 187 | в кеш и из кеша, а каждый кеш-промах стоит до тысяч тактов на обращение к RAM. 188 | Даже по меркам питона - серьезные нагрузки. 189 | 190 | Python threads are continuesly jumps around all cores. Data are moved in 191 | and out if L1/L2 caches and LLC/RAM. While each cache miss consts up to 192 | thousands CPU cycles for memore access. 193 | 194 | Выставив привязку к одному ядру мы убиваем сразу двух зайцев. Во-первых 195 | сокращаем количество переключений контекста, поскольку ОС будет заметно реже 196 | запустить на исполнение второй поток, если единственное доступное ему ядро 197 | сейчас занято. Во-вторых другие потоки не будут вклиниваться на это ядро, 198 | тем самым мы уменьшим интенсивность обмена данными между кешем и ОЗУ 199 | (питоновские потоки в одном процессе используют заметную часть данных совместно). 200 | 201 | By restricting OS to shedule all server python threads on single code 202 | we eliminates a most of context switches. Also in this case other threads would 203 | (mostly) not being scheduled to this core, which also would decreate cache 204 | misses friquency. 205 | 206 | Итоги тестирования. 207 | 208 | Test results: 209 | 210 | * SUM - общее затраченное время 211 | * SUM - execution time, as shown by 'real' field of ouput of 'time' utility 212 | 213 | * SYS - время, затраченное операционной системой 214 | * SYS - OS time, as shown by 'system' field 215 | 216 | 217 | * USR - время, затраченное в пользовательском режиме 218 | * SYS - user time, as shown by 'user' field 219 | 220 | * XXX_AF - XXX в случае, если выставлена привязка к одному ядру 221 | * XXX_AF - XXX in case of CPU affinity turned on 222 | 223 | * DIFF - отличие в процентах между XXX и XXX_AF 224 | * DIFF - % difference of XXX and XXX_AF (positive means - with affinity faster) 225 | 226 | Все измерения сделаны на Core i7-2630QM@800MHz, python 2.7.5, x64, ubuntu 227 | 13.10 с 228 | усреднением по 7ми выборкам. Долгая война с turbo boost окончилась принудительным 229 | переводом процессора в режим минимальных частот. 230 | 231 | All measures taken on Core i7-2630QM@800MHz, python 2.7.5, x64, ubuntu 13.10. 232 | 7 runs mean, to eliminates turbo boost influence CPU runs on lowes available 233 | code clock - 800Mhz. 234 | 235 | raw: 236 | ------------------------------------------------------------------------- 237 | | Потоки | SUM SUM_AF %DIFF | SYS SYS_AF %DIFF | USR USR_AF %DIFF | 238 | ------------------------------------------------------------------------- 239 | | 1 | 3.35 3.55 -5 | 0.54 0.52 4 | 2.78 3.03 -8 | 240 | | 2 | 7.26 4.63 36 | 4.91 0.67 86 | 5.10 2.95 42 | 241 | ------------------------------------------------------------------------- 242 | | 4 | 8.28 4.90 41 | 6.58 0.76 88 | 7.37 3.14 57 | 243 | | 8 | 7.96 5.00 37 | 6.49 0.84 87 | 7.32 3.15 57 | 244 | ------------------------------------------------------------------------- 245 | | 16 | 9.77 5.88 40 | 6.53 0.73 89 | 7.01 3.15 55 | 246 | | 32 | 9.73 6.84 30 | 6.54 0.81 88 | 7.06 3.04 57 | 247 | ------------------------------------------------------------------------- 248 | 249 | Прогон теста по VTune показывает, что после выставления привязки количество кеш промахов 250 | уменьшается примерно в 5 раз, а количество переключений контекста - в 40. В ходе экспериментов 251 | обнаружилась еще одна интересная вещь - при выставлении привязки к одному ядру более эффективно 252 | используется turbo boost, что тоже ускорит вашу программу, если больше никто не грузит систему. 253 | Для этого теста turbo boost был заблокирован. 254 | 255 | 256 | Run under VTune showns, that in case of affinity is turned on amount of 257 | cache misses are decreased on factor of 5 and amount of context switches on 258 | factor of 40. In case if turbo boost would not be disabled it can also speed up 259 | program, which uses only one core by increasing this particular core clock. 260 | 261 | 262 | Будет ли что-то подобное в других случаях? Хотя данная программа и обрабатывает данные, 263 | приходящие из сокета, но данные приходят быстрее, чем она может их обработать. Таким образом 264 | она является [CPU bounded]. Если программа будет в основном занята ожиданием данных, то 265 | выставления привязки к ядру даст меньше ускорения - ОС будет меньше перебрасывать потоки между 266 | ядрами. Чем выше нагрузка на процессор, тем больше будет выигрыш. 267 | 268 | In what cases we can expects such speed up? Shown example is actually a CPU 269 | bounded program, because data coming much facter, than it can be processed. 270 | In case of IO bounded program speed up would be smaller. 271 | 272 | Когда мы можем получить замедление: 273 | In next cases we would expect, that affinity would slows down program: 274 | 275 | * если в программе есть места, которые действительно параллельны, например 276 | часть работы делается С/С++ библиотекой, которая отпускает GIL 277 | * If some calculations made in C/C++ library, which release GIL during 278 | execution 279 | 280 | * Или вы используете jython или ironpython 281 | * If you use jython or ironpython 282 | 283 | * Если вы используете multiprocessing/ProcessPoolExecutor, которые запускают 284 | отдельные процессы и не имеют проблем с GIL. Привязка в линуксе наследуется 285 | потоками/процессами. Так что для дочерних процессов ее нужно или отменить, или 286 | выделить по своему ядру на каждый процесс. 287 | 288 | * If you uses multiprocessing/ProcessPoolExecutor, which uses processes instead 289 | of threads and allows python to side-step GIL. As affinity are inherited by 290 | child processes we should either set dedicated CPU core or decline affinity 291 | for child processes. 292 | 293 | * В некоторых однопоточных системах, например при использовании gevent 294 | * In some single-thread programs, like ones, which uses gevent. 295 | 296 | P.S. В 3.3 поведение все то-же. 297 | P.S. Python 3.3 shows the same behaviour. 298 | 299 | 300 | linklist: 301 | GIL http://habrahabr.ru/post/84629 302 | статью http://habrahabr.ru/post/141181 303 | конкурентны, но не параллельны http://vimeo.com/49718712 304 | CPU bounded http://stackoverflow.com/questions/868568/cpu-bound-and-i-o-bound 305 | HT http://ru.wikipedia.org/wiki/Hyper-threading 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | -------------------------------------------------------------------------------- /posts/python_install.txt: -------------------------------------------------------------------------------- 1 | ========================== 2 | Установка питона и пакетов 3 | ========================== 4 | 5 | В этой статье я попытаюсь описать процесс создания готового python окружения 6 | и работу с пакетами на пользовательском уровне. Статья расcчитана 7 | на новичков (в основном для студентов, слушающих мои курсы). 8 | 9 | <-----------------------------------------------------------------------------> 10 | 11 | Задачи обычно возникающие при установки питона и его пакетов: 12 | 13 | * Выбор дистрибутив питона и его установка 14 | * Выбор IDE 15 | * Поиск и установка пакетов 16 | 17 | Кроме этого я пробегусь по этим полезным вещам: 18 | 19 | * [virtualenv] 20 | * lint'ы 21 | * ipython 22 | * [pythonanywhere.com] 23 | 24 | Выбор дистрибутив питона и его установка 25 | ---------------------------------------- 26 | 27 | Если вы используете linux, то лучше использовать python идущий в пакетах - 28 | как правило это немного измененный cpython. Для windows можно выбирать между 29 | [стандартным] питоном и дистрибутивом от [active_python|Active State]. 30 | Последний содержит расширенную документацию и некоторые дополнительные 31 | библиотеки. Мы не будем рассматривать PyPy/Stackless/etc - ограничимся только 32 | CPython. Дальше нужно сделать выбор между двумя ветками - 3.2/3.3 и 2.7. Пока 33 | что с 2.7 у вас будет меньше проблем, но третья версия по поддержке уже 34 | подбирается достаточно близко. x86 и amd64 версии выбираем по вкусу. 35 | Установка и под windows и совершенно стандартна и не должна вызывать проблем. 36 | В linux питон уже почти 100% установлен. 37 | 38 | Выбор IDE 39 | --------- 40 | 41 | Динамический характер языка делает написание функциональных IDE достаточно 42 | сложным, а высокая компактность кода и pythonic подход заметно уменьшает в них 43 | необходимость. Так что не сложные проекты можно делать в продвинутых текстовых 44 | редакторах - [notepad++], [sublime text] (или vim/emacs). Хотя новичкам IDE 45 | будут оказывать заметную помошь встроенной подсказкой и каким ни каким 46 | статическим анализом. Из IDE я бы выделил eclipse + [pydev] и платные [PyCharm] 47 | и [KomodoIDE]. Также есть [Python tools for VS], которые добавляет поддержку 48 | cpython и ironpython в VS2010/VS2012. 49 | 50 | Я бы советовал выбирать между sublime text и eclipse + pydev. 51 | 52 | Поиск и установка пакетов 53 | ------------------------- 54 | 55 | Пакеты/модули в python это файлы с расширениями py/pyc/pyo/(pyd или so), или 56 | директории с такими файлами. Также весь пакет может быть в одном архиве 57 | (только если пакет не содержит pyd/so файлы). По умолчанию пакеты 58 | устанавливаются в системную папку - PYTHON_ROOT\lib\site-packages для windows и 59 | /usr/local/lib/pythonXX/dist-packages для ubuntu (XX - версия питона, 60 | PYTHON_ROOT - корневая папка установки python, как правило С:\PythonXX) 61 | 62 | Если вы используете linux, то можно использовать пакеты из дистрибутива - 63 | в Ubuntu/Fedora есть практически все. Иначе искать пакеты в основном стоит на 64 | [pypi] или с помощью google. Пакеты могут быть в трех основных форматах: архив, 65 | exe/msi, egg. 66 | 67 | Архив нужно распаковать, в корневой папке должен быть файл setup.py. Если 68 | его там нет, то можно просто скопировать содержимое архива в директорию с 69 | пакетами. Если setup.py есть, то нужно выполнить python setup.py install. При 70 | этом следует использовать тот интерпретатор питона, в который вы хотите 71 | установить пакет. Если пакет не предоставляет модулей написанных на С/С++, то 72 | установка должна пройти без особенных проблем. Иначе python будет пытаться 73 | собрать компилируемые расширения. В linux такой процесс проходит чаще всего 74 | безболезненно (максимум требуется установка пакетов с заголовочными файлами для 75 | для используемых C библиотек), а вот в windows путь компиляции может быть 76 | достаточно трудным. 77 | 78 | При установке в windows проще использовать уже собранный exe/msi файл. 79 | Для большинства пакетов они доступны на pypi или на сайте библиотеки, также 80 | много бинарных пакетов можно найти на [pythonlibs]. При загрузке обратите 81 | внимание на архитектуру и версию python. Для установки такие пакеты нужно 82 | запустить. Библиотеки не содержащие компилируемого кода уставливаются без 83 | проблем на обеих системах. 84 | 85 | egg это формат пакетов одного из пакетные менеджеров питона - [setuptools]. 86 | Грубо говоря это zip архив с дополнительной информацией о пакете и его 87 | зависимостях. Более новой и активно развиваемой альтернативой setuptools 88 | является [pip]. pip использует код setuptools(или distribute) и не поддерживает 89 | egg. Оба этих менеджера умеют находить пакеты по имени на pypi, по URL и 90 | локально. Поддерживаются разнообразные форматы архивов и автоматическая 91 | установка зависимостей. pip умеет деинсталлировать пакеты и поддерживает 92 | установку из svn/git/mercurial. 93 | 94 | Установка pip - http://www.pip-installer.org/en/latest/installing.html 95 | 96 | * скачать и запустить http://python-distribute.org/distribute_setup.py 97 | * скачать и запустить https://raw.github.com/pypa/pip/master/contrib/get-pip.py 98 | 99 | Установка setuptools 100 | 101 | * Скачать и запустить http://peak.telecommunity.com/dist/ez_setup.py 102 | 103 | Оба этих менеджера предоставляют команду easy_install, pip 104 | кроме этого предоставляет команду pip. 105 | 106 | Использование (примеры команд без их вывода): 107 | 108 | shell: 109 | # pip install pylint # установим pylint 110 | # easy_install install -U pylint # обновить пакет 111 | # pip install --upgrade simplejson 112 | # pip uninstall simplejson # удалить 113 | # pip install http://my.package.repo/SomePackage-1.0.4.zip 114 | # pip install git+https://github.com/simplejson/simplejson.git 115 | # pip install svn+svn://svn.zope.org/repos/main/zope.interface/trunk/ 116 | 117 | 118 | virtualenv 119 | ---------- 120 | 121 | [virtualenv] позволяет делать на одной машине несколько независимых 122 | инсталляций python, каждая из которых имеет свой интерпретатор, набор настроек 123 | и библиотек. Некоторые из таких окружений могут использовать системную папку с 124 | дополнительными пакетами. Кроме этого virtualenv позволяет устанавливать питон 125 | и пакеты пользователям без прав root. 126 | 127 | shell: 128 | $ sudo pip install virtualenv # или sudo apt-get install python-virtualenv 129 | $ virtualenv --distribute ENV_NAME # или python virtualenv.py --distribute ENV_NAME 130 | 131 | --distribute заставить virtualenv установить distribute вместо setuptools. 132 | 133 | Эта команда создаст папку ENV_NAME внутри которой будет интерпретатор python 134 | ENV_NAME/bin/python и каталог для пакетов ENV_NAME/lib/pythonX.X/site-packages. 135 | ENV_NAME/bin/python будет настроен на поиск пакетов в 136 | ENV_NAME/lib/pythonX.X/site-packages. Также virtualenv устанавливает в новое 137 | окружение pip. Что-бы активировать это окружений нужно исполнить скрипт 138 | activate. 139 | 140 | shell: 141 | $ source ENV_NAME/bin/activate 142 | > ENV_NAME\Scripts\activate # для windows 143 | 144 | Теперь команда python будет приводить к запуску питона из 145 | ENV_NAME/bin/python, то же относится и к pip. После окончания работы нужно 146 | выполнить deactivate. virtualenv включили в стандартную библиотеку 147 | начиная с python3.3 148 | 149 | 150 | lint'ы 151 | ------ 152 | 153 | Линтами называют средства статического анализа по имени первой такой 154 | утилиты, которая находила странно написанные участки C кода, потенциально 155 | содержащие ошибки. Из-за динамического характера python сделать для него очень 156 | хороший линт невозможно, а даже просто хороший очень сложно. Ошибки при которых 157 | С программа даже не скомпилируется могут легко загнать в угол python линты. Но 158 | тем не менее значительную часть (а у начинающих - практически все) 159 | ошибок/опечаток они найдут. 160 | 161 | Три основных lint'а для python это [pylint], [pychecker] и [pyflakes]. Из 162 | них pylint, наверное, наиболее сообразительный. Кроме этого он имеет большое 163 | количество настроек, которые позволяют изменить особенности проверок. Также 164 | pylint проверяет стиль кода, используя шаблоны из конфигурационного файла и 165 | собирает полезную статистику. Плюс большая часть IDE и даже sublime имеют 166 | интеграцию с pylint. 167 | 168 | По умолчанию pylint слишком требовательный так что начинать его 169 | использование стоит с подстройки конфига под себя, кроме этого иногда он дает 170 | ложные срабатывания. 171 | 172 | Как более легкую альтернативу можно использовать [pep8], проверяющий код на 173 | соответствие основному python [pep8_pep|стандарту кодирования]. 174 | 175 | ipython 176 | ------- 177 | 178 | Чуть подробнее о установке ipython. Под linux с правами root все 179 | просто (ubuntu): 180 | 181 | shell: 182 | $ sudo apt-get install ipython ipython-doc ipython-notebook ipython-qtconsole python-zmq 183 | 184 | или 185 | 186 | shell: 187 | $ sudo apt-get install --install-suggests ipython 188 | 189 | ipython готов к запуску - 190 | 191 | shell: 192 | $ ipython qtconsole # GUI консоль 193 | $ ipython notebook # Web интерфейс 194 | $ ipython # консольный интерфейс 195 | 196 | Под windows все не так просто - нужно загрузить все пакеты и зависимости 197 | вручную и установить их. pip поможет не сильно, поскольку большая часть пакетов 198 | С расширения с внешними зависимости и собирать их будет лишней сложностью. 199 | Зависимости ipython (поскольку мы не будем использовать pip то их придется 200 | выяснять и устанавливать самостоятельно) можно определить двумя способами - 201 | найти в [ipython_install|документации по установке] или пытаться запускать 202 | ipython и смотреть на ошибки импорта. Из документации находим зависимости: 203 | 204 | * pyqt или pyside 205 | * pyzmq 206 | * tornado 207 | * pygments 208 | * pyreadline 209 | * distribute или setuptools 210 | 211 | Бинарные версии всех этих пакетов есть в [pythonlibs]. Загружаем и ставим 212 | в любом порядке. После чего выбираем из: 213 | 214 | shell: 215 | > С:\Python2.7\Scripts\ipython.bat qtconsole # GUI консоль 216 | > С:\Python2.7\Scripts\ipython.bat notebook # Web интерфейс 217 | > С:\Python2.7\Scripts\ipython.bat # консольный интерфейс 218 | 219 | 220 | [pythonanywhere.com] 221 | -------------------- 222 | 223 | Если поставить питон совсем никак нельзя, то можно воспользоваться web 224 | консолью на указанном сайте. После регистрации можно бесплатно запустить 2 225 | python/ipython консоли в браузере и пробовать python без установки. 226 | 227 | 228 | linklist: 229 | virtualenv http://pypi.python.org/pypi/virtualenv 230 | стандартным http://www.python.org/download/ 231 | active_python http://www.activestate.com/activepython/downloads 232 | pypi http://pypi.python.org/pypi 233 | pythonlibs http://www.lfd.uci.edu/~gohlke/pythonlibs/ 234 | pip http://www.pip-installer.org/en/latest/index.html 235 | ipython_install http://ipython.org/ipython-doc/stable/install/install.html 236 | pychecker http://pychecker.sourceforge.net/ 237 | Python_tools_for_VS http://pytools.codeplex.com/ 238 | pyflakes https://launchpad.net/pyflakes 239 | pep8_pep http://www.python.org/dev/peps/pep-0008/ 240 | pylint http://pypi.python.org/pypi/pylint 241 | pep8 http://pypi.python.org/pypi/pep8 242 | PyCharm http://www.jetbrains.com/pycharm/ 243 | pydev http://pydev.org/ 244 | setuptools http://pypi.python.org/pypi/setuptools 245 | sublime_text http://www.sublimetext.com/ 246 | KomodoIDE http://www.activestate.com/komodo-ide 247 | pythonanywhere.com http://pythonanywhere.com -------------------------------------------------------------------------------- /posts/python_interfaces.txt: -------------------------------------------------------------------------------- 1 | =============================================== 2 | Интерфейсы в python или "Предъявите документы!" 3 | =============================================== 4 | 5 | Люди с опытом программирования на C#/Java/C++ начиная писать на python 6 | часто интересуются как обстоят дела с [интерфейс|интерфейсами] в python. 7 | Надеюсь остальные тоже найдут тут интересное для себя. 8 | 9 | В компилируемых языках используется модель интерфейсов основанная на статическом 10 | декларировании. 11 | Для реализации интерфейса класс должен унаследовать интерфейс или другим способом 12 | сообщить компилятору о его реализации - без этого генерация эффективного бинарного 13 | кода невозможна. Cтатическая типизация позволяет перенести на этап компиляции 14 | проверку и преобразование типов интерфейсов а также проверку того, что класс реализует 15 | декларируемый интерфейс. 16 | 17 | <-------------------------------------------------------------------------------> 18 | 19 | В динамических языках все обстоит по другому. Во-первых на этапе компиляции 20 | типы не известны, во-вторых нет необходимости иметь детальную информацию о типе 21 | для генерации байтокода. Здесь используется [утиная типизация] : 22 | если функция использует параметр 'out_file' что-бы писать в него 23 | данные вызывая 24 | 25 | python: 26 | out_file.write("some_text") 27 | 28 | то любой объект у которого есть 29 | метод 'write' принимающий строку ей сойдет. Набор методов и полей, необходимых для 30 | использования объекта в некоторой роли называется протоколом - в документации по python 31 | описаны протоколы для всех основных типов (Например - [протокол для чисел]). 32 | 33 | По итогу интерфейс и протокол это продолжение идей статической и динамической 34 | типизации. Если интерфейс достается объекту в наследство (декларативно, проверяемо 35 | на этапе компиляции), то реализация протокола не предполагает декларации, а только 36 | реализацию. Интересный вариант - шаблоны в C++. Фактически они используют 37 | утиную типизацию и протоколы. Если бы в С++ еще удалось реализовать интроспекцию этапа 38 | компиляции.... Но это я размечтался. 39 | 40 | Две главные библиотеки интерфейсов/протоколов в python это [zope.interface] и 41 | [PyProtocols] (наследник интерфейсов [PEAK]). Рассмотрим их подробнее. 42 | 43 | У 'zope.interface' есть хорошая русская документация [z.i.ru] которую нет 44 | смысла пересказывать, так что пробежимся по нему быстро, одним примером: 45 | 46 | python: 47 | import zope.interface 48 | 49 | # Декларируем интерфейс. Как легко догадаться тут немножко 50 | # черной магии метаклассов 51 | 52 | class IFile(zope.interface.Interface): 53 | mode = zope.interface.Attribute("open mode") 54 | def write(data): 55 | pass 56 | 57 | # Интерфейс поддерживает 'mapping' протокол для своих элементов 58 | # немного более удобная интроспекция, чем через 'vars'/'__dict__' 59 | 60 | print 'mode' in IFile # True 61 | print IFile['mode'] 62 | print IFile['write'] 63 | 64 | IFile.mode # => AttributeError :) черная магия в действии 65 | 66 | # реализуем интерфейс 67 | class NoSQLFile(object): 68 | zope.interface.implements(IFoo) 69 | 70 | def __init__(self): 71 | self.mode = 'w' 72 | 73 | def write(self, data): 74 | # Персистентность? Не, не слышал :) 75 | # Декларирование интерфейса означает только что его можно будет 76 | # вызвать. Но не означает, что он сделает что-то адекватное 77 | pass 78 | 79 | # классы реализуют интерфейсы 80 | print IFile.implementedBy(NoSQLFile) # True 81 | 82 | # экземпляры классов их предоставляют 83 | print IFile.providedBy(NoSQLFile()) # True 84 | 85 | # список всех реализуемых интерфейсов 86 | list(zope.interface.implementedBy(NoSQLFile)) 87 | 88 | # можно обявлять реализацию/предоставление интерфейсов вне класса 89 | import StringIO 90 | zope.interface.classImplements(StringIO.StringIO, IFile) 91 | 92 | # адаптация 93 | x = IFile('/tmp/some_data.txt') #TypeError 94 | 95 | def adapt_str_to_file(iface, fname): 96 | if iface is IFile: 97 | # тут бы стоило декларировать предоставление интерфейса 98 | # но и так будет работать 99 | return open(fname) 100 | 101 | zope.interface.interface.adapter_hooks.append(adapt_str_to_file) 102 | x = IFile('/tmp/some_data.txt') # открыли файл 103 | 104 | Итого - 'zope.interface' позволяет: 105 | 106 | * Объявлять интерфейсы 107 | * Декларировать реализацию и предоставление интерфейса, как при создании класса 108 | так и для уже созданных классов (в т.ч. описанных в сторонних библиотеках) 109 | * Добавлять ограничения на объекты, предоставляющие интерфейс - [z.i.инварианты] 110 | * Адаптировать классы к интерфейсам, не заложенным в них мзначально. Возможности 111 | адаптации заметно шире, чем показано в примере выше - см. 112 | [z.i.реестры_адаптеров] 113 | 114 | 'zope.interface' имеет достаточно длинную историю развития и на сегодня это 115 | основной пакет для интерфейс-ориентированного программирования на python. 116 | Он используется в [zope] (и его наследнике - [grok]), [twisted], [pyramid], 117 | и д.р. Ядро написано на C, так что со скоростью проблем быть не должно. 118 | 119 | Теперь 'PyProtocols'. В отличии от 'zope.interface' главное в нем 120 | адаптация и интерфейсы предназначенны только для указания к чему адаптировать, 121 | но не предполагают наследование и явную реализацию. Другими словами если 122 | 'zope.interface' вносит элементы декларативного (a-la компилируемого) подхода, 123 | то 'PyProtocols' полностью построен на идеях протоколов. 124 | 125 | python: 126 | import types 127 | import protocols 128 | 129 | # интерфейс 130 | class IFile(protocols.Interface): 131 | def write(data): 132 | pass 133 | 134 | # адаптор - преобразует строку к IFile 135 | class String2File(object): 136 | protocols.advise( 137 | provides = [IFile], 138 | asAdapterForTypes = [types.StringType], 139 | ) 140 | 141 | def __init__(self, fname): 142 | self.fd = open(fname) 143 | 144 | def write(self, data): 145 | self.fd.write(data) 146 | 147 | При этом 'PyProtocols' может использовать не только свои интерфейсы, но и 148 | 'zope.interfaces'. 149 | 150 | Были также 'Twisted.interfaces', но 'twisted' позже перешел на использование 151 | 'zope.interfaces'. 152 | 153 | Немного про адаптацию. Ее основная задача - связывать вместе 154 | библиотеки которые не были написаны для совместной работы. Например 155 | функция 'select.select' требует что-бы передаваемые в нее объекты 156 | были или типа 'int' или имели метод 'fileno', возвращающий 'int'. 157 | Внутри это, очевидно, сделано примерно так: 158 | 159 | python: 160 | def select(rfds, wfds, efds, tout=None): 161 | real_fds = [] 162 | for fd in rfds: 163 | if isinstance(fd, (int,long)): 164 | real_fds = int(fd) 165 | else: 166 | real_fds = int(fd.fileno()) 167 | ....... 168 | 169 | Как могло бы выглядеть при использовании интерфейсов и адаптации('PyProtocols'): 170 | 171 | python: 172 | import protocols 173 | 174 | # интерфейс 175 | class ISelectable(Interface): 176 | def fileno(data): 177 | pass 178 | 179 | # адаптер int -> ISelectable 180 | class SelectableInt(object): 181 | protocols.advise( 182 | provides = [ISelectable], 183 | asAdapterForTypes = [types.IntType], 184 | ) 185 | 186 | def __init__(self, fd_int): 187 | self.fd_int = fd_int 188 | 189 | def fileno(self): 190 | return self.fd_int 191 | 192 | def select(rfds, wfds, efds, tout=None): 193 | real_fds = [] 194 | for fd in rfds: 195 | adapted_iface = adapt(fd, ISelectable) 196 | real_fds.append(adapted_iface.fileno()) 197 | 198 | 199 | На более сложных примерах адаптация не так сильно проигрывает по 200 | количеству кода, но в любом случае мы меняем 201 | 'if' 'isintance'(...)/'elif' 'isintance'(...)/'elif' 'isintance'(...) на более 202 | гибкий код, при этом увеличивая его количество. Фактически первый вариант 203 | select имеет жестко закодированную внутри адаптацию ограниченного количества 204 | типов, но выигрывает на отсутствии создания адаптирующего 205 | класса. Один из вариантов критики первого подхода можно посмотреть тут: 206 | [Don't use isinstance]. Мне кажется что наиболее удачен был-бы подход с 207 | некоторым количеством 'if/elif' для базовых типов и адаптацией для остальных. 208 | Адаптация также могла бы помочь c одной из врожденных проблем python - отсутствия 209 | механизма перегрузки функций. Правда не похоже что при ей использовании 210 | можно писать перегрузку так-же лаконично, как, например, на C++. 211 | 212 | Ну это все было во времена динозавров, а как дела обстоят сейчас? 213 | С 2001 года были отклонены два PEP по внесению в python интерфейсов и адаптации 214 | ([PEP-0246], [PEP-0245]), и в конце-концов был принят [PEP-3119] - 215 | о поддержке абстрактных базовых классов. Этот PEP стандартизует способ 216 | создания интерфейса и проверки реализации/предоставления интерфейса. 217 | Тепер что-бы проверить, что объект индексируется можно вместо 218 | 219 | python: 220 | hasattr(x, "__getitem__") and hasattr(x, "__setitem__") 221 | 222 | написать 223 | 224 | python: 225 | isinstance(x, MutableSequence) 226 | 227 | PEP реализован модулем [модуль abs] и добавлением методов 228 | '__instancecheck__' и '__subclasscheck__', 229 | позволяющими перегружать 'isinstance' и 'issubclass'. 230 | Реализация адаптации оставлена на откуп сторонним библиотекам 231 | (которых пока не очень, а 'PyProtocols' давно не обновлялся). 232 | 233 | Как видим попытки привнести в python систему интерфейсов и адаптации 234 | делались неоднократно, но никакой существенной поддержки в языке и 235 | библиотеках не получили (PEP-3119 фактически только расширяет isinstance). 236 | С моей точки зрения основная проблема в том, что повсеместное использование 237 | интерфейсов/адаптации лишает язык лаконичности и простоты. А мы любим python 238 | за простоту и низкий порог вхождения - за простые в использовании и освоении 239 | библиотеки и часто не особо важно, какой длинны 'if/elif' там внутри. 240 | 241 | linklist: 242 | утиная типизация http://www.voidspace.org.uk/python/articles/duck_typing.shtml 243 | zope http://www.zope.org/ 244 | zope.interface http://pypi.python.org/pypi/zope.interface 245 | zope.interface_doc1 http://www.muthukadan.net/docs/zca.html#interfaces 246 | z.i.ru http://docs.zope.org/zope.interface/README.ru.html 247 | z.i.инварианты http://docs.zope.org/zope.interface/README.ru.html#id16 248 | z.i.реестры_адаптеров http://docs.zope.org/zope.interface/adapter.ru.html 249 | twisted http://twistedmatrix.com/documents/current/core/howto/plugin.html 250 | grok http://pypi.python.org/pypi/grok 251 | pyramid http://docs.pylonsproject.org/projects/pyramid/en/1.2-branch/index.html 252 | PyProtocols http://peak.telecommunity.com/PyProtocols.html 253 | PEAK http://peak.telecommunity.com/Articles/WhatisPEAK.html 254 | Don't use isinstance http://www.canonical.org/~kragen/isinstance/ 255 | протокол для чисел http://docs.python.org/reference/datamodel.html#emulating-numeric-types 256 | PEP-0245 http://www.python.org/dev/peps/pep-0245/ 257 | PEP-0246 http://www.python.org/dev/peps/pep-0246/ 258 | PEP-3119 http://www.python.org/dev/peps/pep-3119/ 259 | модуль abs http://docs.python.org/library/abc.html 260 | интерфейс http://ru.wikipedia.org/wiki/%D0%98%D0%BD%D1%82%D0%B5%D1%80%D1%84%D0%B5%D0%B9%D1%81_%28%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%BD%D0%BE-%D0%BE%D1%80%D0%B8%D0%B5%D0%BD%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%29 261 | -------------------------------------------------------------------------------- /posts/python_pattern_matching.txt: -------------------------------------------------------------------------------- 1 | ==================================================== 2 | Сопоставление объектов с образцом (pattern matching) 3 | ==================================================== 4 | 5 | В [haskell|функциональных] [erlang|языках] есть интересная 6 | возможность, фактически являющаяся расширением идеи перегрузки 7 | функций - [сопоставление с образцом]. Для этого поддерживается 8 | специальный синтаксис шаблонов структур данных, позволяющий 9 | проверить что объект имеет определенный тип и/или поля, а также извлечь 10 | из него некоторые данные. Пример из [haskell] (частично взято тут [wiki]): 11 | 12 | haskell: 13 | -- f - функция от одного целого параметра 14 | -- возвращающая целое 15 | f :: Int -> Int 16 | f 1 = 0 17 | -- если ей передать 1, то она вернет 0 18 | f _ -> 1 19 | -- если что-либо другое - 1 20 | 21 | -- map от чего угодно и пустого списка возвращает пустой список 22 | map _ [] = [] 23 | -- рекурсия - map от функции и списка это конкатенация 24 | -- f от первого параметра и map от f и остатка списка 25 | map f (x:xs) = f x : map f xs 26 | 27 | -- разбор структуры 28 | -- Foo это или Bar или Baz 29 | data Foo = Bar | Baz {bazNumber::Int, bazName::String} 30 | h :: Foo -> Int 31 | -- Baz - это тип структуры, bazName - это имя поля 32 | h Baz {bazName=name} = length name 33 | h Bar {} = 0 34 | 35 | Примерно тоже можно сделать во многих функциональных языках, 36 | но я никогда не видел подобных возможностей в императивных языках. 37 | Самое близкое что есть по интеллектуальности - перегрузка функций в C++. 38 | Такое положение связанно и с особенностями задач, обычно решаемыми 39 | в функциональных языках, и с их ориентированностью на рекурсивные структуры 40 | данных и с попытками уйти от 'if' и других императивных особенностей. 41 | 42 | <---------------------------------------------------------------------------> 43 | 44 | Но тем не менее желание сделать что-то подобное для python 45 | возникало после каждого ковыряния в функциональщине и после каждой конструкции 46 | вида: 47 | 48 | python: 49 | if isinstance(x, Message): 50 | if x.mtype == DATA_READY and x.data is not None: 51 | #some code 52 | pass 53 | elif x.mtype == PROCESS_FINISHED: 54 | #some code 55 | pass 56 | # .... 57 | # ..... 58 | 59 | А тут что-то захотелось посмотреть внимательно на модуль [ast] 60 | ([abstract syntax tree]) - давно не 61 | использовал его, последний раз еще во времена 2.4 - тогда очень 62 | жалел, что он не позволяет компилировать измененный 'ast' (кстати 63 | делал интересный проект по портированию большого куска кода с 64 | PyQt3 на PyQt4 и ast позволил значительно автоматизировать этот 65 | перенос). 66 | 67 | 'ast' позволяет получить из python кода его результат после 68 | синтаксического разбора, но еще до компиляции в байтокод, 69 | исследовать его и/или изменять и компилировать новый вариант. 70 | Пример ast: 71 | 72 | raw: 73 | a = f.b(1) 74 | 75 | => 76 | 77 | Assign( 78 | targets=[Name(id='a', ctx=Store())], 79 | value=Call( 80 | func=Attribute( 81 | value=Name(id='f', ctx=Load()), 82 | attr='b', 83 | ctx=Load()), 84 | args=[Num(n=1)], 85 | keywords=[], 86 | starargs=None, 87 | kwargs=None 88 | ) 89 | ) 90 | 91 | 92 | Фактически мы получаем исходный текст в удобном для ковыряния виде 93 | (правда несколько громоздком). Именно с абстрактными синтаксически 94 | деревьями работаю всяческие анализаторы кода, оптимизаторы и прочее. 95 | 'ast' предоставляет некоторое количество вспомогательных функций и два 96 | класса - 'NodeVisitor' для просмотра ast и 'NodeTransformer' для 97 | модификации. 98 | 99 | На этом все про 'ast'. Что хотелось от сопоставления с образцом: 100 | 101 | * Чистый python синтаксис, что-бы никаких новых зарезервированных слов 102 | и IDE что-бы не ругались 103 | * Как-можно меньше кода при использовании 104 | * Обеспечить сопоставление с константой, типом, проверку атрибутов и 105 | вложенную проверку 106 | 107 | После некоторого времени размышлений остановился на таком варианте: 108 | 109 | python: 110 | with match(x) as res: 111 | 1 >> 2 112 | int >> x * 3 113 | str >> func_str(x) 114 | SomeType(c=V_c, d=V_c) >> on_val(V_c) 115 | SomeType(c=V_c, d=V_d) >> on_val2(x, V_c) 116 | 117 | print "res =", res 118 | 119 | Как это должно было-бы работать: 120 | 121 | python: 122 | if x == 1: 123 | res = 2 124 | elif isinstance(x, int): 125 | res = x * 3 126 | elif isinstance(x, str): 127 | res = func_str(x) 128 | elif isinstance(x, SomeType) and \ 129 | hasattr(x, 'c') and \ 130 | hasattr(x, 'd') and \ 131 | x.c == x.d: 132 | res = on_val(x.c) 133 | elif isinstance(x, SomeType) and \ 134 | hasattr(x, 'c') and \ 135 | hasattr(x, 'd'): 136 | res = on_val2(x, x.c) 137 | else: 138 | raise ValueError("{0!r} don't match any pattern!".format(x)) 139 | 140 | Совсем так, как хотелось, сразу не вышло. Вышло так: 141 | 142 | python: 143 | import python_match 144 | 145 | @python_match.mathing 146 | def come_func(): 147 | # some code 148 | with python_match.match(x) as res: 149 | 1 >> 2 150 | int >> x * 3 151 | str >> func_str(x) 152 | SomeType(c=V_c, d=V_c) >> on_val(V_c) 153 | SomeType(c=V_c, d=V_d) >> on_val2(x, V_c) 154 | 155 | print res.val 156 | 157 | Из необязательных ограничений - нужно импортировать модуль 158 | 'python_match' без переименования. Обернуть все функции, где 159 | используется сопоставление с образцом, декоратором 160 | ''python_match.mathing''. 161 | 162 | Как это работает: 163 | 164 | * декоратор с помощью модуля 'inspect' получает исходный код функции, 165 | разбирает его в ast и прогоняет через класс 'MatchReplacer' 166 | * 'MatchReplacer' наследует 'ast.NodeTransformer' и перегружает метод 'visit_With', 167 | в котором подменяет ноду 'with' на измененную конструкцию со сравнениями. Строка 168 | до ''>>'' изменяется на сравнение, а в строка после - подменяются переменные. 169 | * класс 'Match' делает сопоставление объектов с образцом, если использовалось 170 | сравнение атрибутов. 171 | 172 | Осталось некоторое количество ограничений, которые однако не принципиальные, 173 | так что поскольку задача скорее стояла из разряда - "как бы это сделать" я не стал 174 | заниматься дальнейшими оптимизациями/улучшениями. 175 | 176 | Полный код тут - [python_match.py], [test_pm.py]. 177 | 178 | 179 | linklist: 180 | haskell http://ru.wikipedia.org/wiki/haskell 181 | erlang http://ru.wikipedia.org/wiki/Erlang 182 | сопоставление с образцом http://en.wikipedia.org/wiki/Pattern_matching 183 | wiki http://en.wikibooks.org/wiki/Haskell/Pattern_matching 184 | ast http://docs.python.org/library/ast.html 185 | python_match.py https://github.com/koder-ua/python-lectures/blob/master/python_match.py 186 | test_pm.py https://github.com/koder-ua/python-lectures/blob/master/test_pm.py 187 | abstract syntax tree http://en.wikipedia.org/wiki/Abstract_syntax_tree -------------------------------------------------------------------------------- /posts/python_why_use_with.txt: -------------------------------------------------------------------------------- 1 | Зачем в python with 2 | =================== 3 | 4 | Долгое время при работе с файлами из python я писал 5 | примерно следующий код: 6 | 7 | python: 8 | def some_func(fname): 9 | fd = open(fname) 10 | some_data_processing(fd.read()) 11 | return result 12 | 13 | Тут предполагается, что в любом случае при выходе из функции 14 | переменная fd уничтожится и вместе с ней закроется файл и все будут жить 15 | долго и счастливо. 16 | 17 | Но что будет если в some_data_processing произойдет исключение? 18 | 19 | <--------------------------------------------------------------------------> 20 | 21 | Например так: 22 | 23 | python: 24 | import sys 25 | 26 | class TestClass(object): 27 | def __del__(self): 28 | print "I'm deleted" 29 | 30 | def data_process(): 31 | obj = TestClass() 32 | raise IndexError() 33 | 34 | try: 35 | data_process() 36 | except: 37 | print "In exception handler" 38 | 39 | print "after except" 40 | 41 | На консоли появляется: 42 | 43 | raw: 44 | In exception handler 45 | after except 46 | I'm deleted 47 | 48 | Почему-то "In exception handler" и "after except" выводятся раньше 49 | "I'm deleted". 50 | 51 | Первая проблема в том, что вместе с исключением питон хранит и трейс стека, 52 | содержащий все фреймы вплоть до породившего исключение. 53 | А внутри фрейма живет f_locals - словарь локальных переменных, и именно 54 | он имеет ссылку на экземпляр класса TestClass. Таким образом до окончания 55 | обработки исключения obj будет жить точно. Почему же "after except" появляется 56 | раньше чем "I'm deleted"? Было бы логично чистить трейс после успешного выхода из блока try. 57 | Дело в том что 2.X питон не всегда чистит внутренние структуры 58 | после обработки исключения и в общем случае вы должны явно вызывать функцию [sys.exc_clear] 59 | чтобы очистить их. Когда я подошел с этим вопросом к Larry Hastings (одному из 60 | основных разработчиков ядра питона) ему потребовалось около 20ти минут, что-бы понять что 61 | происходит и найти в документации sys.exc_clear. 62 | (Правда стоит отметить, что он давно использует 3.X, где это поведение стало адекватнее.) 63 | В 3.X это поведение улучшили, и теперь sys.exc_clear автоматически вызывается окончанию 64 | обработки исключения. 65 | 66 | Кстати, если вы напишете примерно такой код: 67 | 68 | python: 69 | try: 70 | data_process() 71 | except: 72 | fr = sys.exc_info()[2] 73 | del fr 74 | 75 | то не забудьте удалить fr используя del, как в последней строке - иначе он образует 76 | циклическую ссылку с текущим фреймом и тогда все станет совсем плохо. 77 | 78 | Стоит отметить, что подобное поведение проявляется не всегда. Например 79 | следующий код исполняется более предсказуемо: 80 | 81 | python: 82 | import sys 83 | 84 | class TestClass(object): 85 | def __del__(self): 86 | print "I'm deleted" 87 | 88 | def data_process(): 89 | fd = TestClass() 90 | try: 91 | raise IndexError() 92 | except: 93 | print "In internal exception handler" 94 | 95 | data_process() 96 | print "after except" 97 | 98 | 99 | raw: 100 | In internal exception handler 101 | I'm deleted 102 | after except 103 | 104 | 105 | В общем что-бы гарантированно избавить себя от этих проблем нужно явно 106 | закрывать все файлы и прочие объекты или так: 107 | 108 | python: 109 | fd = open(fname) 110 | try: 111 | process_code() 112 | finally: 113 | fd.close() 114 | 115 | или так: 116 | 117 | python: 118 | with open(fname) as fd: 119 | process_code() 120 | 121 | собственное with именно для этого и был сделан. Без его использования вы рискуете 122 | исчерпать лимит на дескрипторы или что-там-еще в 123 | зависимости от объектов. Впрочем это только начало печальной истории, продолжение дальше. 124 | 125 | linklist: 126 | sys.exc_clear http://docs.python.org/2/library/sys.html#sys.exc_clear 127 | nuitka http://nuitka.net/ 128 | -------------------------------------------------------------------------------- /posts/pythonic_api_generator.rst: -------------------------------------------------------------------------------- 1 | ==================================== 2 | Генерируем внешние API по-питоновски 3 | ==================================== 4 | 5 | В python есть негласное правило - никогда не повторяйся. 6 | Чаще всего если в программе приходиться писать почти одно и то-же два раза, значит 7 | вы что-то сделали не так. Я приведу пример, как можно 8 | автоматизировать генерацию внешних API таким образом, что 9 | достаточно будет в одном месте в удобной и универсальной форме 10 | описать поддерживаемые вызовы, а все внешнее API для этих 11 | вызовов сделает написаный один раз код. 12 | 13 | Итак мы пишем серверный компонент программы, который должен 14 | контролироваться внешними утилитами. Типичные варианты управления: 15 | 16 | * CLI - административный интерфейс командной строки, так-же удобен для разработки 17 | * REST - для других языков, WebUI & Co 18 | * RCP в каком-то виде (thrift, PyRo, etc) 19 | 20 | Нам нужна библиотека, которая позволит один раз задать интерфейсы API функций, 21 | сгенерирует по ним интерфейсы для всех внешних API, будет автоматически 22 | проверять входящие параметры и сделает удобочитаемую документацию. 23 | Для начала хватит. 24 | 25 | .. cut:: 26 | 27 | pass 28 | pass 29 | 30 | 31 | Любая библиотека проектируется отталкиваясь от примеров ее использования. 32 | 33 | .. sourcecode:: python 34 | 35 | class Add(APICallBase): 36 | "Add two integers" 37 | class Params(object): 38 | params = Param([int], "list of integers to make a sum") 39 | 40 | def execute(self): 41 | return sum(self.params) 42 | 43 | 44 | class Sub(APICallBase): 45 | "Substitute two integers" 46 | class Params(object): 47 | params = Param((int, int), "substitute second int from first") 48 | 49 | def execute(self): 50 | return self.params[0] - self.params[1] 51 | 52 | 53 | class Ping(APICallBase): 54 | "Ping host" 55 | class Params(object): 56 | ip = Param(IPAddr, "ip addr to ping") 57 | num_pings = Param(int, "number of pings", default=3) 58 | 59 | def execute(self): 60 | res = subprocess.check_stdout('ping -c {0} {1}'.format(self.num_pings, 61 | self.ip)) 62 | return sum(map(float, re.findall(r'time=(\d+\.?\d*)', out))) / \ 63 | self.num_pings 64 | 65 | 66 | Это желаемое описание API. Каждый API вызов наследует класс **APICallBase**, 67 | определяет внутренний класс **Params**, где экземплярами класса **Param** описывает 68 | параметры вызова и перегружает вызов **execute**, в котором выполняется вся работа. 69 | Этой информации более чем достаточно, что-бы сгенерировать все API 70 | и документацию пользуясь интроспекцией и генерацией объектов на лету. 71 | 72 | Начнем с базы - нужно уметь находить все классы, унаследованные от **APICallBase**. 73 | Это можно сделать через метаклассы_ 74 | 75 | .. sourcecode:: python.hide 76 | 77 | class APIMeta(type): 78 | 79 | # список всех API вызовов 80 | api_classes = [] 81 | def __new__(cls, name, bases, clsdict): 82 | 83 | new_cls = super(APIMeta, cls).__new__(cls, name, bases, clsdict) 84 | 85 | # пропускаем APICallBase 86 | if name != 'APICallBase': 87 | self.api_classes.append(new_cls) 88 | 89 | # all_params итерирует по всем параметрам этого вызова 90 | # передаем в параметры имена атрибутов, которым они присвоены 91 | # таким образом мы избегаем дублирования имени 'ip' в след строке 92 | # ip = Param(IPAddr, "ip addr to ping") 93 | # и других таких-же 94 | 95 | for name, param in new_cls.Params.__dict__.items(): 96 | param.name = name 97 | 98 | return new_cls 99 | 100 | # базовый класс для всех API вызовов 101 | class APICallBase(object): 102 | __metaclass__ = APIMeta 103 | 104 | def __init__(self, **dt): 105 | self._consume(dt) 106 | 107 | @classmethod 108 | def name(cls): 109 | return cls.__name__.lower() 110 | 111 | @classmethod 112 | def all_params(cls): 113 | return cls.Params.__dict__.values() 114 | 115 | def rest_url(self): 116 | return '/{0}'.format(self.name()) 117 | 118 | @classmethod 119 | def from_dict(cls, data): 120 | obj = cls.__new__(cls) 121 | obj._consume(data) 122 | return obj 123 | 124 | def _consume(self, data, from_strings=False): 125 | # этот метод заполняет экземпляр команды из словаря параметров 126 | # и проводит все необходимые проверки параметров 127 | 128 | 129 | required_param_names = set() 130 | all_param_names = set() 131 | 132 | for param in self.all_params(): 133 | if param.required(): 134 | required_param_names.add(param.name) 135 | all_param_names.add(param.name) 136 | 137 | # проверяем наличие лишних параметров data 138 | extra_params = set(data.keys()) - all_param_names 139 | if extra_params != set(): 140 | raise ValueError("Extra parameters {0} for cmd {1}".format( 141 | ','.join(extra_params), self.__class__.__name__)) 142 | 143 | # проверяем наличие в data всех необходимых параметров 144 | missed_params = required_param_names - set(data.keys()) 145 | if missed_params != set(): 146 | raise ValueError("Missed parameters {0} for cmd {1}".format( 147 | ','.join(missed_params), self.__class__.__name__)) 148 | 149 | # проверяем значение параметра или пребразовываем его из строки 150 | # (прошедшей из CLI) в целевой тип 151 | 152 | parsed_data = {} 153 | for param in self.all_params(): 154 | try: 155 | val = data[param.name] 156 | except KeyError: 157 | parsed_data[param.name] = param.default 158 | 159 | if from_strings: 160 | parsed_data[param.name] = param.from_cli(val) 161 | else: 162 | param.validate(val) 163 | parsed_data[param.name] = val 164 | 165 | # обновляем аттрибуты и возвращает объект 166 | self.__dict__.update(parsed_data) 167 | return self 168 | 169 | def to_dict(self): 170 | res = {} 171 | for param in self.all_params(): 172 | res[param.name] = getattr(self, param.name) 173 | return res 174 | 175 | def execute(self): 176 | # базовый метод для выполнения работы 177 | pass 178 | 179 | def __str__(self): 180 | res = "{0}({{0}})".format(self.__class__.__name__) 181 | params = ["{0}={1!r}".format(param.name, getattr(self, param.name)) 182 | for param in self.all_params()] 183 | return res.format(', '.join(params)) 184 | 185 | def __repr__(self): 186 | return str(self) 187 | 188 | 189 | Классы для типов данных, используемых в **Params** 190 | 191 | .. sourcecode:: python 192 | 193 | # базовый класс для типов данных 194 | class DataType(object): 195 | 196 | # проверить, про val принадлежит к денному типу 197 | def validate(self, val): 198 | return True 199 | 200 | # преобразовать val из формата для командной строки 201 | def from_cli(self, val): 202 | return None 203 | 204 | # параметры для парсера CLI 205 | def arg_parser_opts(self): 206 | return {} 207 | 208 | # список параметров определенного типа 209 | class ListType(DataType): 210 | def __init__(self, dtype): 211 | self.dtype = get_data_type(dtype) 212 | 213 | def validate(self, val): 214 | if not isinstance(val, (list, tuple)): 215 | return False 216 | 217 | for curr_item in val: 218 | if not self.dtype.valid(curr_item): 219 | return False 220 | 221 | return True 222 | 223 | def from_cli(self, val): 224 | return [self.dtype.from_cli(curr_item) for curr_item in val] 225 | 226 | def arg_parser_opts(self): 227 | opts = self.dtype.arg_parser_opts() 228 | opts['nargs'] = '*' 229 | return opts 230 | 231 | # целое число 232 | class IntType(DataType): 233 | 234 | def validate(self, val): 235 | return isinstance(val, int) 236 | 237 | def from_cli(self, val): 238 | return int(val) 239 | 240 | def arg_parser_opts(self): 241 | return {'type': int} 242 | 243 | Итак переходим к генерации API. Для начала - CLI 244 | 245 | .. sourcecode:: python 246 | 247 | def get_arg_parser(): 248 | parser = argparse.ArgumentParser() 249 | subparsers = parser.add_subparsers() 250 | 251 | for call in APIMeta.api_classes(): 252 | 253 | # для каждого вызова - свой вложенный парсер 254 | sub_parser = subparsers.add_parser(call.name(), 255 | help=call.__doc__) 256 | sub_parser.set_defaults(cmd_class=call) 257 | 258 | # проходим по всем параметрам и добавляем для них опции в CLI 259 | for param in call.all_params(): 260 | opts = {'help':param.help} 261 | 262 | # значение по умолчанию, если оно есть 263 | # _NoDef это специальный класс, что-бы отличать значение 264 | # None и полное отсутствие параметра 265 | if param.default is not _NoDef: 266 | opts['default'] = param.default 267 | 268 | opts.update(param.arg_parser_opts()) 269 | sub_parser.add_argument('--' + param.name.replace('_', '-'), 270 | **opts) 271 | return parser, subparsers 272 | 273 | 274 | REST API с помощью CherryPy_ 275 | 276 | .. sourcecode:: python 277 | 278 | import cherrypy as cp 279 | def get_cherrypy_server(): 280 | 281 | class Server(object): 282 | pass 283 | 284 | # замыкание-обработчик для команды 285 | 286 | def call_me(cmd_class): 287 | 288 | # обмениваться данными будем через json 289 | @cp.tools.json_out() 290 | def do_call(self, opts): 291 | cmd = cmd_class.from_dict(json.loads(opts)) 292 | return cmd.execute() 293 | return do_call 294 | 295 | # добавляем к классу Server по методу для каждой команды 296 | # CherryPy будет их вызывать для обработки REST запросов 297 | 298 | for call in APIMeta.all_classes(APICallBase): 299 | setattr(Server, 300 | call.name(), 301 | cp.expose(call_me(call))) 302 | 303 | return Server 304 | 305 | CherryPy довольно интересный веб-сервер, который использует интроспекцию 306 | и атрибуты классов для обработки HTTP запросов. Запрос вида 307 | http://localhost:8080/xyz?a=1&b=2 приведет к вызову **Server.xyz(a="1", b="2")**, 308 | если такой есть и проброшен в web через **cherrypy.expose**. 309 | 310 | Завершающий аккорд - функция main 311 | 312 | .. sourcecode:: python 313 | 314 | def main(argv=None): 315 | 316 | # наполняем парсер CLI и разбираем командную строку 317 | argv = argv if argv is not None else sys.argv 318 | parser, subparsers = get_arg_parser() 319 | 320 | sub_parser = subparsers.add_parser('start-server', 321 | help="Start REST server") 322 | sub_parser.set_defaults(cmd_class='start-server') 323 | 324 | res = parser.parse_args(argv) 325 | cmd_cls = res.cmd_class 326 | 327 | # если пришел запрос на запуск сервера 328 | if cmd_cls == 'start-server': 329 | rest_server = get_cherrypy_server() 330 | cp.quickstart(rest_server()) 331 | else: 332 | # иначе конструируем объек-команду 333 | for opt in cmd_cls.all_params(): 334 | data = {} 335 | try: 336 | data[opt.name] = getattr(res, opt.name.replace('_', '-')) 337 | except AttributeError: 338 | pass 339 | cmd = cmd_cls.from_dict(data) 340 | 341 | # если не определена переменная окружения REST_SERVER_URL 342 | rest_url = os.environ.get('REST_SERVER_URL', None) 343 | 344 | if rest_url is None: 345 | # исполняем локально 346 | print "Local exec" 347 | print "Res =", cmd.execute() 348 | else: 349 | # иначе исполняем на сервере 350 | print "Remote exec" 351 | params = urllib.urlencode({'opts': json.dumps(cmd.to_dict())}) 352 | res = urllib2.urlopen("http://{0}{1}?{2}".format(rest_url, 353 | cmd.rest_url(), 354 | params)).read() 355 | print "Res =", json.loads(res) 356 | 357 | 358 | return 0 359 | 360 | Пробуем: 361 | 362 | .. sourcecode:: console 363 | 364 | $ python api.py -h 365 | usage: api.py [-h] {add,sub,ping,start-server} ... 366 | 367 | positional arguments: 368 | {add,sub,ping,start-server} 369 | add Add two integers 370 | sub Substitute two integers 371 | ping Ping host 372 | start-server Start REST server 373 | 374 | optional arguments: 375 | -h, --help show this help message and exit 376 | 377 | $ python api.py add --params 1 3 378 | Local exec 379 | Res = 4 380 | 381 | $ export REST_SERVER_URL=localhost:8080 382 | 383 | $ python api.py add --params 1 3 384 | Remote exec 385 | Res = 4 386 | 387 | Идея очень простая, так что особенно писать нечего - код говорит сам за себя. 388 | Более полный вариант можно найти на `koder github`_. Основная мысль - вынос каждой команды 389 | в отдельный класс и описание всех ее параметров в виде, удобном для интроспекции. 390 | Похожим на описанный образом можно генерировать логику для `django piston`_, 391 | html документацию по всем параметрам, отличия между версиями API для различных версий 392 | сервера и другое, как это делается на нашем текущем проекте. 393 | 394 | 395 | .. _метаклассы: http://koder-ua.blogspot.com/2011/12/blog-post.html 396 | .. _CherryPy: http://tools.cherrypy.org/ 397 | .. _koder github: https://github.com/koder-ua/python-lectures/blob/master/posts/api.py 398 | .. _django piston: https://bitbucket.org/jespern/django-piston/wiki/Home 399 | -------------------------------------------------------------------------------- /posts/tiny_cloud.txt: -------------------------------------------------------------------------------- 1 | ========================================== 2 | libvirt & Co. Облако "на коленке". Часть 1 3 | ========================================== 4 | 5 | Buzzword 6 | ======== 7 | 8 | [cloud|Облако(cloud)] это инфраструктура для управления виртуальными 9 | машинами. Агенты облака устанавливаются на железных серверах, превращая их 10 | единый мегасервер, которые используется для виртуализации. Облако должно уметь: 11 | 12 | * запускать группы виртуальных машин на базе загруженных в него образов 13 | * изменять образы виртуальных машин 14 | * управлять сетевой инфраструктурой - объединять виртуальные машины в 15 | ( возможно виртуальные ) локальные сети, настраивать правила доступа к 16 | этим сетям извне и доступ наружу из сетей 17 | * поддерживать остановку, приостановку и миграцию виртуалок 18 | * балансировать нагрузку на железные сервера 19 | * управлять местом на дисках 20 | * .............. 21 | 22 | Предисловие 23 | =========== 24 | 25 | На сегодняшний день есть четыре основных облачных системы - перспективный и 26 | активно развиваемый [openstack], 27 | рабочий но мало интересный из-за лицензии [eucalyptus], совсем-совсем 28 | проприетарный [VMware vCloud] и очень-очень microsoft [azure]. Но это все "серьезные" 29 | облака, а как это часто бывает большие системы не удобно использовать на малых 30 | задачах. Я расскажу как управлять небольшими группами виртуальных машин 31 | "малой кровью". Впрочем openstack использует эти же утилиты, а все остальные 32 | узнают на чем основываются linux клауды. 33 | 34 | <--------------------------------------------------------------------------------> 35 | 36 | Для описанных методик вам необходим Linux 2.6.26+ и процессор с поддержкой 37 | виртуализации. Проверить это можно следующими командами: 38 | 39 | shell: 40 | $ cat /proc/cpuinfo | egrep 'vmx|svm' 41 | $ cat /proc/cpuinfo | egrep 'rvi|ept' 42 | 43 | Если первая команда ничего не вывела - вам не повезло, аппаратной поддержки 44 | виртуализации у вас нет. Если обе команды выдали не пустой ответ - вам повезло 45 | вдвойне - в вашем процессоре есть поддержка виртуализации таблицы страниц - это 46 | значительно ускоряет работу с памятью, фактически выводя ее на уровень сырого 47 | железа. 48 | 49 | Вложенная аппаратная виртуализация не поддерживается, т.е. если linux 50 | установлен в виртуальной машине, то описанные примеры работать не будут. 51 | Впрочем и те, кто запускает линукс в виртуалке и те, у кого нет поддержки виртуализации 52 | могут адаптировать эти примеры для использования [xen] c паравиртуализацией или 53 | [lxc] - эти техники не требуют аппаратной поддержки. В принципе ипользуемая [libvirt] 54 | имеет зачаточную поддержку windows, желающие могут попробовать и так. 55 | 56 | Из других аппаратных требований желательно по-больше оперативной памяти (3Gb+) 57 | и быстрый диск (SSD). 58 | На магнитном жестком диске все будет работать, но некоторые наиболее интересные 59 | варианты организации виртульных образов заметно тормозят на дисковых операциях 60 | из-за большого количества разрозненных обращений. 61 | 62 | Все примеры для Ubuntu 11.10, для других дистрибутивов нужно подправить 63 | обращения к пакетному менеджеру и пути к конфигам. 64 | 65 | libvirt 66 | ======= 67 | 68 | Хотя формально [libvirt] называется библиотекой, но это целая инфраструктура 69 | для управления виртуальными машинами. Она включает: 70 | 71 | * libvirt-bin демон с внешним API, управляющий виртуальными машинами 72 | * libvirt - библиотека для доступа к демону 73 | * masqdns - dns/dhcp сервер, используемый совместно с iptables, vlan и бриджами 74 | для управлением виртуальными сетями 75 | * virsh - клиент командной строки 76 | 77 | libvirt предоставляет почти унифицированный интерфейс для работы с различными 78 | гипервизорами - поддерживаются [kvm], [lxc], [xen], vmware, hyper-v, [openvz], 79 | и другие - в общем почти все, что еще шевелится. При этом libvirt не пытается 80 | подобрать общий знаменатель ко всем системам виртуализации, а 81 | предоставляет полный набор возможностей каждого гипервизора - просто не все конфигурации 82 | будут работать на всех системах виртуализаций. 83 | 84 | Для описания виртуальных машин, сетей и хранилищ libvirt использует xml, 85 | строки с которым выступают параметрами во всех основных функциях. Модель кажется 86 | сначала странной, но после ближайшего рассмотрения понимаешь, что это очень 87 | яркий пример удачного использования [dependency injection]. 88 | В 99% случаев программам которые используют libvirt все равно какая структура 89 | каждой конкретной виртуальной машины. А при использовании внешних xml файлов 90 | их можно править не трогая код, при этом исходная программа будет(почти) 91 | однообразно работать на всех поддерживаемых гипервизорах и с самыми разными 92 | виртуальными машинами. 93 | 94 | Итак начнем с самого простого - с запуска vm. Примеры кода будут на python, 95 | но все эти действия можно выполнить из командной строки с помощью 'virsh'. В 96 | качестве гипервизора будем использовать kvm. Он по умолчанию доступен в 97 | современных Linux системах. 98 | 99 | Ставим необходимые пакеты: 100 | 101 | shell: 102 | # apt-get install kvm qemu qemu-kvm qemu-common libvirt-bin libvirt0 python-libvirt 103 | # modprobe kvm 104 | # mkdprobe kvm-intel # kvm-amd 105 | 106 | Выключаем apparmor/selinux. Желающие могут его настроить, но по умолчанию 107 | они настроены криво: 108 | 109 | shell: 110 | # service apparmor teardown 111 | # service libvirt-bin stop 112 | # service libvirt-bin start 113 | 114 | Выкачиваем образ [debian_vm] и делаем конфигурационный файл для нее: 115 | --меня запинали по поводу лишних элементов, пока в таком виде оставим-- 116 | 117 | hide.xml: 118 | 119 | 120 | 121 | {vm_name} 122 | {mem} 123 | 124 | {vcpu} 125 | 126 | hvm 127 | 128 | 129 | 130 | 131 | 132 | 133 | destroy 134 | restart 135 | destroy 136 | 137 | 138 | 139 | 140 | 141 | 142 | /usr/bin/kvm 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | Файл для запуска vm: 167 | 168 | python: 169 | # tiny_cloud.py 170 | import sys 171 | import libvirt 172 | 173 | # соединяемся с libvirtd 174 | uri = 'qemu://system' 175 | conn = libvirt.open(uri) 176 | 177 | vm_xml_templ = open(sys.argv[1]).read() 178 | 179 | vm_xml = vm_xml_templ.format(vcpu=1, 180 | mem=1024 * 1024, # 1Gb 181 | name=sys.argv[2], 182 | mac="00:44:01:61:78:01", 183 | image_file=sys.argv[3] 184 | ) 185 | 186 | # запускаем vm 187 | conn.createXML(vm_xml, 0) 188 | 189 | Запускаем: 190 | 191 | shell: 192 | # python tiny_cloud.py vm_templ.xml debian path_to_image 193 | 194 | Проверяем, что виртуалка запущенна: 195 | 196 | shell: 197 | # virsh list 198 | 199 | Id Name State 200 | ---------------------------------- 201 | 1 debian running 202 | 203 | В принципе libvirt с помощью 'virsh' позволяет 204 | регистрировать/запускать/останавливать виртуальные машины и без 205 | использования python, но для более сложных задач bash не самый лучший 206 | язык программирования. 207 | 208 | Виртуальные машины запущенные под управлением kvm являются обычными 209 | linux процессами, так что ими можно управлять в том числе с помощью стандартных 210 | средств - ps, kill, nice, ionice, etc. Это так-же означает что работают все стандартные 211 | системы мониторинга (htop/atop/iotop/sar) и другое, а 212 | 213 | shell: 214 | # ps aux | grep kvm 215 | 216 | покажет командную строку, с помощью которой можно запустить vm не используя 217 | libvirt. 218 | 219 | Продолжим с tiny_cloud.py - добавим останов виртуалки, разбор командной 220 | строки, etc. 221 | 222 | hide.python: 223 | # tiny_cloud.py 224 | import sys 225 | import argparse 226 | 227 | import libvirt 228 | 229 | vm_sets = \ 230 | { 231 | 'ubuntu' : 232 | { 233 | 'vcpu' : 1, 234 | 'mem' : 1024 * 1024, # 1Gb RAM 235 | 'mac' : "00:44:01:61:78:01", 236 | 'image_file' : '/home/koder/vm_images/ubuntu-server-nova-1.qcow2' 237 | }, 238 | 'debian' : 239 | { 240 | 'vcpu' : 1, 241 | 'mem' : 1024 * 1024, # 1Gb RAM 242 | 'mac' : "00:44:01:61:78:01", 243 | 'image_file' : 244 | '/home/koder/vm_images/debian_squeeze_amd64_standard.qcow2' 245 | } 246 | } 247 | 248 | class TinyCloud(object): 249 | def __init__(self, conn): 250 | self.conn = conn 251 | 252 | def start_vm(self, template, vmname): 253 | vm_xml_templ = open(template).read() 254 | vm_xml = vm_xml_templ.format(vmname=vmname, **vm_sets[vmname]) 255 | self.conn.createXML(vm_xml, 0) 256 | 257 | def stop_vm(self, vmname): 258 | vm = self.conn.lookupByName(vmname) 259 | vm.destroy() 260 | 261 | def list_vms(self): 262 | for domain_id in self.conn.listDomainsID(): 263 | yield self.conn.lookupByID(domain_id) 264 | 265 | def main(argv=None): 266 | argv = argv if argv is not None else sys.argv 267 | 268 | parser = argparse.ArgumentParser() 269 | 270 | parser.add_argument('cmd', choices=('start', 'stop', 'list')) 271 | parser.add_argument('--name', choices=vm_sets.keys()) 272 | parser.add_argument('--uri', default="qemu:///system") 273 | parser.add_argument('--template', default="vm_templ.xml") 274 | 275 | opts = parser.parse_args(argv[1:]) 276 | 277 | cloud = TinyCloud(libvirt.open(opts.uri)) 278 | 279 | if opts.cmd == 'start': 280 | cloud.start_vm(opts.template, opts.name) 281 | elif opts.cmd == 'stop': 282 | cloud.stop_vm(opts.name) 283 | elif opts.cmd == 'list': 284 | for domain in cloud.list_vms(): 285 | print "{0:>5} {1}".format(domain.ID(), domain.name()) 286 | else: 287 | print >> sys.stderr, "Unknown cmd {0}".format(opts.cmd) 288 | return 0 289 | 290 | if __name__ == "__main__": 291 | sys.exit(main(sys.argv)) 292 | 293 | Для подключения к полученным виртуалкам можно использовать 294 | ssh или vnc viewer. Для виртуалок, поднятых с помощью libvirt есть 295 | удобный [virt-manager], который показывает все запущенные домены и позволяет 296 | подключится по vnc, что необходимо если сеть не загрузилась или на 297 | образе не было ssh сервера. 298 | 299 | center.img[with=400]: 300 | http://3.bp.blogspot.com/-9ORCk64v3Cc/TvPIAnLPeaI/AAAAAAAAAs8/eTEyeK5JGJE/s1600/debian_vm.png 301 | 302 | center.img[with=700]: 303 | http://4.bp.blogspot.com/-hK7mqAUBaHA/TvPIA4_XV-I/AAAAAAAAAtI/8RjPCk4jztc/s1600/debian_vm_vnc.png 304 | 305 | Первая проблема после запуска виртуалки - програмно определять ip, 306 | который она получила. Для этого желательно разобраться с сетевой моделью 307 | libvirt и вообще с основными сетевыми средствами linux, чему и будет 308 | посвящена следующая статья. 309 | 310 | linklist: 311 | debian-vm http://people.debian.org/~aurel32/qemu/amd64/debian_squeeze_amd64_standard.qcow2 312 | cloud http://ru.wikipedia.org/wiki/%D0%9E%D0%B1%D0%BB%D0%B0%D1%87%D0%BD%D1%8B%D0%B5_%D0%B2%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F 313 | openstack http://openstack.org/ 314 | eucalyptus http://www.eucalyptus.com/ 315 | VMware vCloud http://www.vmware.com/solutions/cloud-computing/index.html 316 | azure http://en.wikipedia.org/wiki/Azure_Services_Platform 317 | kvm http://en.wikipedia.org/wiki/Kernel-based_Virtual_Machine 318 | xen http://xen.org/ 319 | lxc http://lxc.sourceforge.net/ 320 | openvz http://wiki.openvz.org/Main_Page 321 | libvirt http://libvirt.org/ 322 | dependency injection http://en.wikipedia.org/wiki/Dependency_injection 323 | virt-manager http://virt-manager.org/ 324 | debian_vm http://people.debian.org/~aurel32/qemu/amd64/debian_lenny_amd64_standard.qcow2 325 | -------------------------------------------------------------------------------- /posts/underunder_madness.txt: -------------------------------------------------------------------------------- 1 | ========================= 2 | Подчеркнутая защищенность 3 | ========================= 4 | 5 | Инкапсуляция - одна из основ ООП. Мы договариваемся использовать только 6 | часть функциональности класса, а взамен получаем возможность работать с 7 | самыми разными типами, даже с теми, которые будут написаны после окончания 8 | работы над текущим кодом. 9 | 10 | Компилируемые языки реализуют инкапсуляцию методом принуждения. 11 | Программист отмечает методы и поля как личные или защищенные, а компилятор 12 | играет в большого брата и проверяет что все используется в корректном контексте. 13 | На моей памяти война за способ использования *private/protected* минимум пару раз 14 | принимала нешуточный оборот. 15 | 16 | Попадая в питон С++/Java-программисты начинают искать замену 17 | родным private/protected в этом мире безудержного эксгибиционизма. И, как 18 | правило, быстро находят два подчеркивания. Не совсем то, что хотелось бы, но 19 | довольно сильно похоже на private. В итоге нижнее подчеркивание быстро 20 | становится самым популярным символом в коде. 21 | 22 | Я попробую показать, что: 23 | 24 | * '__' - не эквивалент *private* и решает совсем другие задачи; 25 | * Можно отлично жить без *private/protected/friend*. Оружие массового запрещения 26 | не единственный способ реализовать инкапсуляцию; 27 | * При желании можно написать аналог *private/protected* и даже более гибкий 28 | контроль доступа для python (в следующем посте) 29 | 30 | <-----------------------------------------------------------------------------> 31 | 32 | Итак зачем в python поля с двумя подчеркиваниями в начале имени. Пусть 33 | у нас есть такой код: 34 | 35 | python: 36 | from some_module import SomeClass 37 | 38 | class SomeClassChildren(SomeClass): 39 | def __init__(self): 40 | super(SomeClassChildren, self).__init__() 41 | self.some_field = 12 42 | 43 | Допустим код *SomeClass* очень большой или нам не доступен или постоянно 44 | неконтролируемо меняется или по любой другой причине мы не может быть уверенны, 45 | что какое бы благозвучное имя не было выбрано для *some_field* мы не можем быть 46 | уверенны, что не затрем поле с таким же именем в родительском классе. 47 | Компилируемый язык решил бы эту проблему, не позволив нам создать поле, если 48 | поле с таким именем уже унаследовано. Это не решает проблему полностью, но 49 | избавляет нас от странного поведения. 50 | 51 | Для этого в питоне и есть поля с двумя подчеркиваниями в начале 52 | (но без двух подчеркиваний в конце). Когда компилятор питона видит 53 | подобное имя он дописывает к нему в начало еще одно подчеркивание и имя 54 | текущего компилируемого класса. Это можно увидеть с помощью питоновского 55 | дизассемблера: 56 | 57 | python: 58 | import dis 59 | 60 | class A(object): 61 | def func(self, x): 62 | self.__attr1 63 | 64 | class B(A): 65 | def func(self, x): 66 | self.__attr2 67 | 68 | class C(A): 69 | def func(self, x): 70 | class C1(object): 71 | def func(self): 72 | x.__attr3 73 | 74 | dis.dis(C1.func) 75 | 76 | def r(self): 77 | self.__attr4 78 | 79 | class D(object): 80 | func = r 81 | 82 | dis.dis(A.func) 83 | dis.dis(B.func) 84 | C().func(A()) 85 | dis.dis(D.func) 86 | dis.dis(lambda: A.__attr5) 87 | 88 | 89 | raw: 90 | # dis.dis(A.func) 91 | 0 LOAD_FAST 0 (self) << object 92 | 3 LOAD_ATTR 0 (_A__attr1) << attribute name 93 | # == self._A__attr1 94 | 95 | # dis.dis(B.func) 96 | 0 LOAD_FAST 0 (self) 97 | 3 LOAD_ATTR 0 (_B__attr2) 98 | 99 | # dis.dis(C1.func) 100 | 0 LOAD_DEREF 0 (x) 101 | 3 LOAD_ATTR 0 (_C1__attr3) 102 | 103 | # dis.dis(D.func) 104 | 0 LOAD_FAST 0 (self) 105 | 3 LOAD_ATTR 0 (__attr4) 106 | 107 | # dis.dis(lambda: A.__attr5) 108 | 0 LOAD_GLOBAL 0 (A) 109 | 3 LOAD_ATTR 1 (__attr5) 110 | 111 | Итого '__' приводит к переименованию поля и позволяет использовать поля с 112 | одинаковыми именами в разных классах одной иерархии. Но это не мешает добраться 113 | до такого поля, например *x._X__some_priv_field*. BTW - 114 | если нужно сделать действительно скрытое поле, то можно и так: 115 | 116 | python: 117 | class MyProxy(object): 118 | def __init__(self, proxifyed): 119 | self.__dict__[self] = proxifyed # <<< 120 | 121 | def __getattr__(self, name): 122 | return getattr(self.__dict__[self], name) 123 | 124 | *self.__dict__* - обычный словарь и ключами в нем могут быть не только 125 | строки. Злоупотреблять таким хаком не стоит поскольку много различных 126 | библиотек, например сериализаторы, сильно удивятся увидев в *__dict__* 127 | нестроковой ключ. 128 | 129 | Итак: '__' - это очень специфический аналог частного поля и он предназначен 130 | для несколько других целей. 131 | 132 | Модификаторы доступа защищают программиста от случайного и преднамеренного 133 | доступа к тем частям API, которые разработчик класса захотел скрыть. 134 | 135 | Начнем со случайного доступа на примере С++. Случайно в нем можно вызвать: 136 | 137 | * конструктор (присваиванием, в контейнере при копировании, etc) 138 | * деструктор (по выходу объекта или его владельца из области видимости) 139 | * оператор преобразования типа 140 | * опечатавшись, скопировав неправильно код, etc 141 | 142 | Последний пункт скорее из области фантастики. Все остальные тоже не 143 | применимы к питону. Питон не копирует объекты, все всегда передается и хранится 144 | по ссылке, *deepcopy/loads* не вызывают конструктор. Деструктор вызывается 145 | непонятно когда и чаще всего его вызов сложно контролировать. 146 | Доступ к имеющиеся преобразования типов бессмысленно запрещать (*__str__*, 147 | *__int__*). Так что операции, выполняемые питоном без явного указания 148 | программиста не особо нуждаются в разграничении доступа. 149 | 150 | Кроме того в С++ в указанных случаях мы получим ошибку на этапе компиляции, 151 | а в случае с питоном - в этапе исполнения, когда уже будет не очень понятно что 152 | с нею делать. 153 | 154 | Перейдем к преднамеренному вызову защищенного метода. Если очень хочется, 155 | то никакой private/protected не остановит: 156 | 157 | raw: 158 | #define protected public 159 | #include "foo.h" 160 | #undef protected 161 | 162 | Есть еще 666 способов добраться до защищенного метода. Есть они и в Java 163 | (reflections) и в C#, иначе [контейнеры внедрения зависимостей] не смогли бы 164 | работать. 165 | 166 | Итого - бессмысленно защищаться и от преднамеренного вызова. Всегда есть 167 | способ обойти такую защиту. Да и если кто-то очень хочет - пусть вызывает. 168 | А для защиты от случайного вызова обычно достаточно просто не упоминать 169 | метод/поле в документации. 170 | 171 | *Документация - первая линия инкапсуляции в python.* 172 | 173 | В С++ чтение заголовочных файлов - достаточно распространенный способ 174 | получения документации и модификаторы доступа в данном случае тоже помогают. В 175 | python чтение исходников затруднено тем, что код и прототип смешаны. При 176 | желании для донесения информации до читающих исходники случая можно помечать 177 | функции декоратором, подобным этому: 178 | 179 | python: 180 | def protected(func): 181 | return func 182 | 183 | class X(object): 184 | @protected 185 | def some_method(self): 186 | pass 187 | 188 | Наконец очень распространенный способ посмотреть что можно сделать с 189 | объектом это интроспекция. Например, если нажать после *x.* в ipython, то 190 | он покажет какие поля и методы есть у объекта *x*. Для управлением видимостью 191 | методов/полей в случае такой интроспекции в питоне есть метод *__dir__*: 192 | 193 | python: 194 | 195 | class X(object): 196 | def __dir__(self): 197 | return 'public_method public_field'.split() 198 | 199 | def __init__(self): 200 | self.public_field = 12 201 | self.protected_field = 13 202 | 203 | def public_method(self): 204 | pass 205 | 206 | def protected_method(self): 207 | pass 208 | 209 | dir(X()) # == ['public_method', 'public_field'] 210 | 211 | 212 | *Интроспеция - вторая линия инкапсуляции в python.* 213 | 214 | Пользуйтесь этими способами и не засоряйте питон подчеркиваниями. 215 | В следующем посте мы объединим автоматизируем *dir* и сделаем для питона аналог 216 | *private*. 217 | 218 | linklist: 219 | контейнеры внедрения зависимостей http://koder-ua.blogspot.com/2013/11/python.html 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | -------------------------------------------------------------------------------- /posts/vm_ut.txt: -------------------------------------------------------------------------------- 1 | ================================================================ 2 | Использование виртуальных машин для автоматического тестирования 3 | ================================================================ 4 | 5 | В системном программирования достаточно часто возникает ситуация, когда 6 | значительная часть функциональности программы перекладывается на внешние 7 | компоненты. Типичный пример - операции с iptables, дисковыми образами и 8 | виртуальными машинами. Классически для тестирования такого кода используются 9 | массовые моки, отрезающие тестируемый код от всех внешних зависимостей. 10 | 11 | При очевидных достоинствах (полная независимость тестов от внешнего мира, 12 | скорость исполнения, etc) у моков есть некоторое количество недостатков - 13 | самый главный это переход от тестирования того что должно быть сделано 14 | к тестирования того как это сделано. Если нужно проверить функцию, которая 15 | настраивает проброс порта, то вместо тестирования результата (правильного 16 | прохождения пакетов) проверяется, что iptables вызвалась с правильными 17 | параметрами. 18 | 19 | <---------------------------------------------------------------------------> 20 | 21 | По итогу юнит тест проверяет не правильность работы кода, а является 22 | отражением его структуры. Такой тест помогает обеспечить постоянную 23 | проверку на отсутствие ''AttributeError'' и ему подобных (python), 24 | но на этом его полезность оканчивается. Учитывая желание менеджера и/или 25 | заказчика получить заветные X% покрытия ситуация становится совсем идиотской. 26 | Несколько последних проектов, в которых я учавствовал были именно такие - 27 | тонкая прослойка из python, связывающая 28 | вместе БД, REST, xen, iptables и еще горстку linux утилит в 29 | небольшой специализированный клауд. По итогу заметная часть UT требует 30 | переписывания после каждого рефакторинга, потому как изменилось 31 | взаимодействие с внешними компонентами. То что должно поощрять 32 | рефакторинг и улучшение кода становится одним из главных его тормозов. 33 | 34 | Частично эта ситуация отражает объективный факт - мы не можем позволить 35 | юнит-тестам бесконтрольтно модифицировать файловую систему на локальной машине, 36 | изменять правила прохождения пакетов или ip маршруты. Дополнительный минус - 37 | рабочая машина разработчика не всегда соответствует требованиям к конечному серверу. 38 | 39 | Решение совершенно очевидное - использовать для тестов виртуальные машины и 40 | проводить тесты на необходимой конфигурации + исключить бОльшую часть моков из тестов. 41 | 42 | Итого - что хотелось получить: 43 | 44 | * исполнять отдельные юнит-тесты на виртуальных машинах или группах машин 45 | * интеграция с [nosetests] и [coverage] 46 | * максимально простое использование 47 | * высокая скорость - юнит-тесты должны исполняться быстро 48 | 49 | Как хотелось это использовать: 50 | 51 | python: 52 | @on_vm('worker-1') 53 | def test_iptables(): 54 | make_iptables_rules() 55 | check_packages_goes_ok() 56 | 57 | @on_vm('worker-2') 58 | def test_something(): 59 | make_something() 60 | check_something_works() 61 | 62 | Доводить идею до рабочего варианта в рамках внутреннего проекта 63 | взялись интерны [нашей компании] - [Игорь Гарагатый] и [Настя Криштопа]. 64 | 65 | Для начала было решено реализовать достаточно простой вариант: 66 | перед исполнением каждого теста, требующего виртуальную машину, запускалась 67 | соответствующая vm, на нее копировался код и тесты, запускались тесты и их результаты 68 | тестов возвращались назад на хост машину. Если тест выбросит исключение 69 | оно должно передаваться назад на хост и выбрасываться из локального теста - 70 | nose не должен замечать разницы между локальным и удаленным исполнением теста. 71 | 72 | В итоге были выбраны два варианта - [LXC] и KVM. LXC позволяет запустить 73 | виртуальную машину менее чем за секунду и не требует аппаратной поддержки 74 | виртуализации, а KVM это более надежный вариант, позволяющий запускать 75 | виртуальные машины любых конфигураций (LXC использует ядро хост системы, 76 | поэтому поднять в нем другую версию ядра или другую OS невозможно). 77 | 78 | Хотелось иметь в vm файловую систему доступную для записи, но возвращаемую в 79 | начальное состояние после окончания теста. Для kvm это естественным образом 80 | решается возможностями qcow2, который позволяет сохранять все изменения в отдельный файл, 81 | не трогая оригинальный образ. Для LXC же нужна была файловая система с поддержкой 82 | снимков и быстрым откатом к ним. После рассмотрения btrfs, LVM+XFS и aufs решили 83 | остановиться на первом варианте. 84 | 85 | Что в итоге получилось: 86 | 87 | * Пользователь подготавливает образы и конфигурации виртуальных машин, которые 88 | будут использоваться для UT 89 | 90 | * Оборачивает отдельные тесты декоратором on_vm с указанием на какой конфигурации 91 | его запускать 92 | 93 | * nosetests unitTests 94 | 95 | * profit (итоги тестов и coverage) 96 | 97 | Примерная схема работы: 98 | 99 | * Декоратор on_vm создает отдельный процесс, для поднятия 100 | ВМ и запускает поток, слушающий результаты на сокете 101 | 102 | * test_executor.py создает с помощью libvirt необходимую конфигурацию vm, 103 | предварительно сделав слепок btrfs или подключив qcow2 файл для сохранения изменений 104 | (в зависимости от типа виртуальной машины) 105 | 106 | * test_executor.py дожидается окончания запуска vm, копирует туда необходимые файлы 107 | и запускает только выбранные тест на исполнение, предварительно выставив переменные окружения 108 | 109 | * on_vm по переменным окружения определяет, что это реальных запуск и исполняет тест 110 | 111 | * при возникновении ошибки она сериализуется и передается на хост 112 | 113 | * итоги теста передаются на хост вместе с результатами покрытия 114 | 115 | * процесс на хосте принимает результаты, гасит vm, откатывает состояние образа и имитирует 116 | локальное исполнение теста. 117 | 118 | 119 | На текущий момент результат пока в состоянии альфа готовности, еще много чего хотелось бы добавить 120 | (иммитацию правильного времени исполнения, повторное использование уже запущенных vm, поднятие 121 | групп vm с определенными сетевыми настройками), но текущая реализация уже готова для проб. 122 | Код можно найти тут [vm_ut]. 123 | 124 | linklist: 125 | vm_ut https://github.com/koder-ua/vm_ut 126 | LXC http://koder-ua.blogspot.com/2012/01/lxc.html 127 | нашей компании http://mirantis.com/ 128 | Игорь Гарагатый https://github.com/ogirOK 129 | Настя Криштопа https://github.com/anakriya 130 | coverage http://pypi.python.org/pypi/coverage 131 | nosetests http://readthedocs.org/docs/nose/en/latest/ 132 | -------------------------------------------------------------------------------- /posts/with_statement.txt: -------------------------------------------------------------------------------- 1 | ============= 2 | Оператор with 3 | ============= 4 | 5 | Теория 6 | ====== 7 | 8 | Оператор [with] появился в python 2.5, но, 9 | не смотря на это, используется до сих пор недостаточно широко. Являясь 10 | упрощенной версией анонимных блоков кода 'with' позволяет: 11 | 12 | * исполнить код до начала блока 13 | * исполнить код по выходу из блока, независимо от того это выход по исключению 14 | с помощью 'return' или другим способом 15 | * обработать исключение, возникшее в блоке. 16 | 17 | Синтаксически 'with' выглядит следующим образом: 18 | 19 | python[-]: 20 | with operation: 21 | code 22 | 23 | 'operation' может быть объектом, выражением или конструкцией вида 24 | 'expression as var'. Как и много других конструкций он является синтаксическим 25 | сахаром для более громоздкого выражения: 26 | 27 | <------------------------------------------------------------------------------> 28 | 29 | python[-]: 30 | with operation as var: 31 | code 32 | 33 | => 34 | 35 | python[-]: 36 | _obj = operation 37 | 38 | # вход в блок 39 | var = _obj.__enter__() 40 | 41 | try: 42 | code 43 | except Exception as exc: 44 | # если произошло исключение - передаем его управляющему объекту 45 | if not _obj.__exit__(*sys.exception_info()): 46 | # если он вернул False(None) возбуждаем его 47 | raise 48 | # если True - подавляем исключение 49 | else: 50 | # если не было исключения - передаем None * 3 51 | _obj.__exit__(None, None, None) 52 | 53 | Более подробно с 'with' можно ознакомиться в соответствующем [PEP-343]. 54 | 'with' управляется объектом, называемым менеджером контекста (МК) - '_obj' в 55 | примере выше. Есть два основных способа написания МК - класс с методами 56 | '__enter__' и '__exit__' и генератор: 57 | 58 | python: 59 | import os 60 | from contextlib import contextmanager 61 | 62 | # Это только пример. 63 | # Использование такого кода для генерации временных файлов 64 | # небезопасно. Используйте функции 'os.tmpfile'. 65 | 66 | class TempoFileCreator(object): 67 | def __init__(self): 68 | self.fname = None 69 | self.fd = None 70 | 71 | def __inter__(self): 72 | # вызывается по входу в блок 73 | self.fname = os.tmpnam() 74 | self.fd = open(self.fname, "w+") 75 | return self.fname, self.fd 76 | 77 | def __exit__(self, exc_type, exc_val, traceback): 78 | # вызывается по выходу из блока 79 | # если в блоке выброшено исключение, то 80 | # его тип, значение и трейс будут переданы в параметрах 81 | 82 | self.fd.close() 83 | os.unlink(self.fname) 84 | self.fd = None 85 | self.fname = None 86 | 87 | # здесь написано return None => исключение не будет подавляться 88 | 89 | @contextmanager 90 | def tempo_file(): 91 | # полностью равноценно классу TempoFileCreator 92 | fname = os.tmpnam() 93 | fd = open(fname, "w+") 94 | try: 95 | yield fname, fd 96 | #сейчас исполняется блок 97 | finally: 98 | # это наш __exit__ 99 | fd.close() 100 | os.unlink(fd) 101 | 102 | Использование: 103 | 104 | python: 105 | with tempo_file() as (fname, fd): 106 | # читаем-пишем в файл 107 | # по выходу из блока он будет удален 108 | pass 109 | 110 | Ядро python реализует только первый вариант для контекст менеджера, 111 | второй реализуется в 'contextlib.contextmanager'. 112 | 113 | В том случае если во внутреннем блоке кода есть оператор 'yield', т.е. мы 114 | работаем в генераторе, '__exit__' будет вызван по выходу из генератора 115 | или по его удалению. Таким образом если ссылку на генератор сохранить, 116 | то '__exit__' не будет вызван до тех пор, пока ссылка будет существовать: 117 | 118 | python: 119 | @contextmanager 120 | def cmanager(): 121 | yield 122 | print "Exit" 123 | 124 | def some_func(): 125 | with cmanager(): 126 | yield 1 127 | 128 | it = some_func() 129 | for val in it: 130 | pass 131 | # Exit напечатается здесь 132 | 133 | it = some_func() 134 | 135 | del it # или по выходу из текущего блока 136 | # Exit напечатается здесь 137 | 138 | Подводя итоги - 'with' позволяет сэкономить 2-4 строки кода на каждое 139 | использование и повышает читаемость программы, меньше отвлекая нас от логики 140 | деталями реализации. 141 | 142 | Практика 143 | ======== 144 | 145 | Начнем с примеров, которые встречаются в стандартной библиотеке и будем 146 | постепенно переходить к менее распространенным вариантам использования. 147 | 148 | * Открытие/создание объекта по входу в блок - закрытие/удаление по выходу: 149 | 150 | python: 151 | with open('/tmp/tt.txt') as fd: 152 | pass 153 | # здесь файл закрывается 154 | # переменная fd доступна, но файл уже закрыт 155 | # 156 | 157 | Чаще всего в python программах не закрывают файл вручную, обоснованно 158 | полагаясь на подсчет ссылок. Блоки 'with' кроме явного указания области, где 159 | файл открыт имеют еще одно небольшое преимущество, связанное с особенностями 160 | обработки исключений: 161 | 162 | python: 163 | def i_am_not_always_close_files(fname): 164 | fd = open(fname) 165 | 166 | i_am_not_always_close_files("/tmp/x.txt") 167 | # в этой точке файл уже закрыт 168 | 169 | Если внутри фцнкции 'i_am_not_always_close_files' будет 170 | возбуждено исключение, то файл не закроется до того момента, пока оно не будет 171 | обработано: 172 | 173 | python: 174 | import sys 175 | 176 | def i_am_not_always_close_files(fname): 177 | fd = open(fname) 178 | raise RuntimeError('') 179 | 180 | try: 181 | i_am_not_always_close_files("/tmp/x.txt") 182 | except RuntimeError: 183 | #тут файл еще открыт 184 | traceback = sys.exc_info()[2] 185 | 186 | # спуск на один кадр стека глубже 187 | # 'fd' в его локальных переменных 188 | print traceback.tb_next.tb_frame.f_locals['fd'] 189 | 190 | # 191 | 192 | # в этой точке файл уже закрыт 193 | 194 | Дело в том, что пока жив объект-исключение он хранит путь исключения со 195 | всеми кадрами стека. При использовании 'with' файл закрывается по выходу блока 196 | кода, обрамляемого в 'with', так что в обработчике исключения файл был бы уже 197 | закрыт. Впрочем это обычно не существенное различие. 198 | 199 | Еще пример: 200 | 201 | python[-]: 202 | # создадим виртуальную машину 203 | with create_virtual_machine(root_passwd) as vm_ip: 204 | # выполним на ней тестирования скрипта автоматической установки 205 | test_auto_deploy_script(vm_ip, root_passwd) 206 | # по выходу уничтожим vm_ip 207 | 208 | 209 | * Захват/освобождение объекта 210 | 211 | Эту семантику поддерживают все стандартные объекты синхронизации 212 | 213 | python: 214 | 215 | import threading 216 | lock = Threading.Lock() 217 | 218 | with lock: 219 | # блокровка захваченна 220 | pass 221 | # блокировка отпущенна 222 | 223 | * Временное изменение настроек (примеры из документации python) 224 | 225 | python: 226 | import warnings 227 | from decimal import localcontext 228 | 229 | with warnings.catch_warnings(): 230 | warnings.simplefilter("ignore") 231 | # в этом участке кода все предепреждения игнорируются 232 | 233 | with localcontext() as ctx: 234 | ctx.prec = 42 # расчеты с типом Decimal выполняются с 235 | # заоблачной точностью 236 | s = calculate_something() 237 | 238 | 239 | * Смена текущей директории (пример использования библиотеки [fabric]) 240 | 241 | python: 242 | from fabric.context_managers import lcd 243 | 244 | os.chdir('/opt') 245 | print os.getcwd() # => /opt 246 | 247 | with lcd('/tmp'): 248 | print os.getcwd() # => /tmp 249 | 250 | print os.getcwd() # => /opt 251 | 252 | 253 | Нужно помнить, что изменение таким образом глобальных настроек в 254 | многопоточной программе может доставить много веселых минут при отладке. 255 | 256 | * Подмена/восстановление объекта (временный [monkey patching], пример 257 | использования библиотеки [mock]) 258 | 259 | python: 260 | import mock 261 | 262 | my_mock = mock.MagicMock() 263 | with mock.patch('__builtin__.open', my_mock): 264 | # open подменена на mock.MagicMock 265 | with open('foo') as h: 266 | pass 267 | 268 | * Транзакции баз данных.... 269 | 270 | Менеджер транзакций для [sqlalchemy] 271 | 272 | python: 273 | from config import DB_URI 274 | from db_session import get_session 275 | 276 | class DBWrapper(object): 277 | 278 | def __init__(self): 279 | self.session = None 280 | 281 | def __enter__(self): 282 | self.session = get_session(DB_URI) 283 | 284 | def __exit__(self, exc, *args): 285 | # при выходе из 'with': 286 | if exc is None: 287 | # если все прошло успешно коммитим 288 | # транзакцию и закрываем курсор 289 | self.session.commit() 290 | 291 | # если было исключение - откатываем 292 | self.session.close() 293 | 294 | # тут методы, скрывающие работу с базой 295 | 296 | with DBWrapper() as dbw: # открываем транзакцию 297 | dbw.get_some_data() 298 | dbw.update_some_data("...") 299 | 300 | 301 | * ....и не только баз данных 302 | 303 | python: 304 | from threading import local 305 | import subprocess 306 | 307 | # обобщенная транзакция - выполняет набор обратных действий 308 | # при возникновении в блоке 'with' не обработанного исключения 309 | 310 | class Transaction(object): 311 | def __init__(self, parent): 312 | self.rollback_cmds = [] 313 | self.set_parent(parent) 314 | 315 | def set_parent(self, parent): 316 | # родительская транзакция 317 | # если откатывается родительская транзакция, то она автоматом 318 | # откатывает и дочерние, даже если они было уже успешно закрыты 319 | # если откатывается дочерняя, то родительская может продолжить 320 | # исполнение, если код выше по стеку обработает исключение 321 | 322 | if parent is not None: 323 | self.parent_add = parent.add 324 | else: 325 | self.parent_add = lambda *cmd : None 326 | 327 | def __enter__(self): 328 | return self 329 | 330 | def __exit__(self, exc, *dt): 331 | if exc is None: 332 | self.commit() 333 | else: 334 | self.rollback() 335 | 336 | def add(self, cmd): 337 | self.parent_add(cmd) 338 | self.transaction.append(cmd) 339 | 340 | def commit(self): 341 | self.transaction = [] 342 | 343 | def rollback(self): 344 | for cmd in reversed(self.transaction): 345 | if isinstance(cmd, basestring): 346 | subprocess.check_call(cmd, shell=True) 347 | else: 348 | cmd[0](*cmd[1:]) 349 | 350 | 351 | class AutoInheritedTransaction(object): 352 | # словарь, id потока => [список вложенных транзакций] 353 | # позволяет автоматически находить родительскую транзакцию 354 | # в том случае, если для каждого потока может быть не более 355 | # одной цепи вложенных транзакций 356 | 357 | transactions = local() 358 | 359 | def __init__(self): 360 | super(AutoInheritedTransaction, self).__init__(self.current()) 361 | self.register() 362 | 363 | def register(self): 364 | self.transaction.list = getattr(self.transaction, 'list') + [self] 365 | 366 | @classmethod 367 | def current(cls): 368 | return getattr(self.transaction, 'list', [None])[-1] 369 | 370 | used_loop_devs = [] 371 | 372 | with AutoInheritedTransaction() as tr: 373 | # создаем loop устройство 374 | loop_name = subprocess.check_output("losetup -f --show /tmp/fs_image") 375 | # вызов для его удаления 376 | tr.add("losetup -d " + loop_name) 377 | 378 | # записываем новое устройство в массив 379 | used_loop_devs.append(loop_name) 380 | tr.add(lambda : used_loop_devs.remove( 381 | used_loop_devs.index( 382 | loop_name))) 383 | 384 | # монтируем его 385 | subprocess.check_output("mount {0} /mnt/some_dir") 386 | tr.add("umount /mnt/some_dir") 387 | 388 | some_code 389 | 390 | Эта модель программирования позволяет группировать в одной точке код прямой 391 | и обратной операции и избавляет от вложенных 'try'/'finally'. Также 'with' 392 | предоставляет естественный интерфейс для [STM]. [cpython-withatomic] - один из 393 | вариантов STM для руthon с поддержкой 'with'. 394 | 395 | * Подавление исключений 396 | 397 | python: 398 | def supress(*ex_types): 399 | # стоит добавить логирования подавляемого исключения 400 | try: 401 | yield 402 | except Exception as x: 403 | if not isinstance(x, ex_types): 404 | raise 405 | 406 | with supress(OSError): 407 | os.unlink("some_file") 408 | 409 | 410 | * Генерация XML/HTML других структурированных языков. 411 | 412 | 413 | python: 414 | 415 | from xmlbuilder import XMLBuilder 416 | 417 | # новый xml документ 418 | 419 | x = XMLBuilder('root') 420 | x.some_tag 421 | x.some_tag_with_data('text', a='12') 422 | 423 | # вложенные теги 424 | with x.some_tree(a='1'): 425 | with x.data: 426 | x.mmm 427 | x.node(val='11') 428 | 429 | print str(x) # <= string object 430 | 431 | 432 | Получим в итоге: 433 | 434 | xml: 435 | 436 | 437 | 438 | text 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | Код библиотеки находится на [xmlbuilder]. 448 | 449 | * Трассировка блока в логере (установка 'sys.settrace') 450 | 451 | python: 452 | import sys 453 | import contextlib 454 | 455 | def on_event(fr, evt, data): 456 | print fr, evt, data 457 | return on_event 458 | 459 | @contextlib.contextmanager 460 | def trace_me(): 461 | 462 | prev_trace = sys.gettrace() 463 | sys.settrace(on_event) 464 | try: 465 | yield 466 | finally: 467 | sys.settrace(prev_trace) 468 | print "after finally" 469 | 470 | 471 | with trace_me(): 472 | print "in with" 473 | x = 1 474 | y = 2 475 | print "before gettrace" 476 | sys.gettrace() 477 | print "after gettrace" 478 | 479 | Этот код напечатает: 480 | 481 | raw: 482 | in with 483 | before gettrace 484 | after gettrace 485 | call None 486 | line None 487 | line None 488 | line None 489 | call None 490 | line None 491 | after finally 492 | 493 | Для лучшего понимания трассировки питона - [python-aware-python]. 494 | 495 | 496 | linklist: 497 | 498 | PEP-343 http://www.python.org/dev/peps/pep-0343/ 499 | with http://docs.python.org/reference/compound_stmts.html#the-with-statement 500 | xmlbuilder https://github.com/koder-ua/megarepo/tree/master/xmlbuilder/xmlbuilder 501 | mock http://www.voidspace.org.uk/python/mock/compare.html#mocking-a-context-manager 502 | monkey patching http://en.wikipedia.org/wiki/Monkey_patch 503 | sqlalchemy http://www.sqlalchemy.org/ 504 | fabric http://fabfile.org 505 | STM http://en.wikipedia.org/wiki/Software_Transaction_Memory 506 | cpython-withatomic https://bitbucket.org/arigo/cpython-withatomic 507 | python-aware-python http://blip.tv/pycon-us-videos-2009-2010-2011/pycon-2011-python-aware-python-4896752 508 | -------------------------------------------------------------------------------- /py_struct.py: -------------------------------------------------------------------------------- 1 | class StructMeta(type): 2 | def __new__(cls, name, bases, dct): 3 | if name != 'Struct': 4 | 5 | mandatory_attrs = [] 6 | optional_attrs = [] 7 | 8 | attrs = dct['attrs'].replace(" ", "") 9 | attrs = attrs.replace("\t", "") 10 | 11 | if '[' in attrs: 12 | m_arttrs, opt_attrs = attrs.split('[') 13 | assert opt_attrs.endswith(']') 14 | assert opt_attrs.startswith(',') 15 | mandatory_attrs = m_arttrs.split(',') 16 | optional_attrs = opt_attrs[1:-1].split(',') 17 | else: 18 | mandatory_attrs = attrs.split(',') 19 | optional_attrs = [] 20 | 21 | all_attrs = mandatory_attrs + optional_attrs 22 | 23 | init_attrs = ", ".join(mandatory_attrs[:]) 24 | 25 | if optional_attrs: 26 | init_attrs += ", " 27 | init_attrs += ", ".join(map("{0}=None".format, optional_attrs)) 28 | 29 | init = "def __init__(self, {0}):\n".format(init_attrs) 30 | 31 | for name in all_attrs: 32 | init += " self.{0} = {0}\n".format(name) 33 | 34 | loc = {} 35 | exec compile(init, "".format(name), \ 36 | 'exec') in loc 37 | dct['__init__'] = loc['__init__'] 38 | 39 | return super(StructMeta, cls).__new__(cls, name, bases, dct) 40 | 41 | class Struct(object): 42 | __metaclass__ = StructMeta 43 | 44 | -------------------------------------------------------------------------------- /pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # Specify a configuration file. 4 | #rcfile= 5 | 6 | # Python code to execute, usually for sys.path manipulation such as 7 | # pygtk.require(). 8 | #init-hook= 9 | 10 | # Profiled execution. 11 | profile=no 12 | 13 | # Add files or directories to the blacklist. They should be base names, not 14 | # paths. 15 | ignore=CVS 16 | 17 | # Pickle collected data for later comparisons. 18 | persistent=yes 19 | 20 | # List of plugins (as comma separated values of python modules names) to load, 21 | # usually to register additional checkers. 22 | load-plugins= 23 | 24 | 25 | [MESSAGES CONTROL] 26 | 27 | # Enable the message, report, category or checker with the given id(s). You can 28 | # either give multiple identifier separated by comma (,) or put this option 29 | # multiple time. 30 | #enable= 31 | 32 | # Disable the message, report, category or checker with the given id(s). You 33 | # can either give multiple identifier separated by comma (,) or put this option 34 | # multiple time (only on the command line, not in the configuration file where 35 | # it should appear only once). 36 | #disable= 37 | 38 | 39 | [REPORTS] 40 | 41 | # Set the output format. Available formats are text, parseable, colorized, msvs 42 | # (visual studio) and html 43 | output-format=text 44 | 45 | # Include message's id in output 46 | include-ids=no 47 | 48 | # Put messages in a separate file for each module / package specified on the 49 | # command line instead of printing them on stdout. Reports (if any) will be 50 | # written in a file name "pylint_global.[txt|html]". 51 | files-output=no 52 | 53 | # Tells whether to display a full report or only the messages 54 | reports=yes 55 | 56 | # Python expression which should return a note less than 10 (10 is the highest 57 | # note). You have access to the variables errors warning, statement which 58 | # respectively contain the number of errors / warnings messages and the total 59 | # number of statements analyzed. This is used by the global evaluation report 60 | # (RP0004). 61 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 62 | 63 | # Add a comment according to your evaluation note. This is used by the global 64 | # evaluation report (RP0004). 65 | comment=no 66 | 67 | 68 | [FORMAT] 69 | 70 | # Maximum number of characters on a single line. 71 | max-line-length=80 72 | 73 | # Maximum number of lines in a module 74 | max-module-lines=1000 75 | 76 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 77 | # tab). 78 | indent-string=' ' 79 | 80 | 81 | [MISCELLANEOUS] 82 | 83 | # List of note tags to take in consideration, separated by a comma. 84 | notes=FIXME,XXX,TODO 85 | 86 | 87 | [TYPECHECK] 88 | 89 | # Tells whether missing members accessed in mixin class should be ignored. A 90 | # mixin class is detected if its name ends with "mixin" (case insensitive). 91 | ignore-mixin-members=yes 92 | 93 | # List of classes names for which member attributes should not be checked 94 | # (useful for classes with attributes dynamically set). 95 | ignored-classes=SQLObject 96 | 97 | # When zope mode is activated, add a predefined set of Zope acquired attributes 98 | # to generated-members. 99 | zope=no 100 | 101 | # List of members which are set dynamically and missed by pylint inference 102 | # system, and so shouldn't trigger E0201 when accessed. Python regular 103 | # expressions are accepted. 104 | generated-members=REQUEST,acl_users,aq_parent 105 | 106 | 107 | [VARIABLES] 108 | 109 | # Tells whether we should check for unused import in __init__ files. 110 | init-import=no 111 | 112 | # A regular expression matching the beginning of the name of dummy variables 113 | # (i.e. not used). 114 | dummy-variables-rgx=_|dummy 115 | 116 | # List of additional names supposed to be defined in builtins. Remember that 117 | # you should avoid to define new builtins when possible. 118 | additional-builtins= 119 | 120 | 121 | [BASIC] 122 | 123 | # Required attributes for module, separated by a comma 124 | required-attributes= 125 | 126 | # List of builtins function names that should not be used, separated by a comma 127 | bad-functions=filter,apply,input 128 | 129 | # Regular expression which should only match correct module names 130 | module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 131 | 132 | # Regular expression which should only match correct module level names 133 | const-rgx=[a-z_][a-z0-9_]{2,30}$ 134 | 135 | # Regular expression which should only match correct class names 136 | class-rgx=[A-Z_][a-zA-Z0-9]+$ 137 | 138 | # Regular expression which should only match correct function names 139 | function-rgx=[a-z_][a-z0-9_]{2,30}$ 140 | 141 | # Regular expression which should only match correct method names 142 | method-rgx=[a-z_][a-z0-9_]{2,30}$ 143 | 144 | # Regular expression which should only match correct instance attribute names 145 | attr-rgx=[a-z_][a-z0-9_]{2,30}$ 146 | 147 | # Regular expression which should only match correct argument names 148 | argument-rgx=[a-z_][a-z0-9_]{2,30}$ 149 | 150 | # Regular expression which should only match correct variable names 151 | variable-rgx=[a-z_][a-z0-9_]{2,30}$ 152 | 153 | # Regular expression which should only match correct list comprehension / 154 | # generator expression variable names 155 | inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ 156 | 157 | # Good variable names which should always be accepted, separated by a comma 158 | good-names=i,j,k,ex,Run,_,fd,fc,db,it,fr,x,y 159 | 160 | # Bad variable names which should always be refused, separated by a comma 161 | bad-names=foo,bar,baz,toto,tutu,tata 162 | 163 | # Regular expression which should only match functions or classes name which do 164 | # not require a docstring 165 | no-docstring-rgx=__.*__ 166 | 167 | 168 | [SIMILARITIES] 169 | 170 | # Minimum lines number of a similarity. 171 | min-similarity-lines=4 172 | 173 | # Ignore comments when computing similarities. 174 | ignore-comments=yes 175 | 176 | # Ignore docstrings when computing similarities. 177 | ignore-docstrings=yes 178 | 179 | 180 | [CLASSES] 181 | 182 | # List of interface methods to ignore, separated by a comma. This is used for 183 | # instance to not check methods defines in Zope's Interface base class. 184 | ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by 185 | 186 | # List of method names used to declare (i.e. assign) instance attributes. 187 | defining-attr-methods=__init__,__new__,setUp 188 | 189 | # List of valid names for the first argument in a class method. 190 | valid-classmethod-first-arg=cls 191 | 192 | 193 | [IMPORTS] 194 | 195 | # Deprecated modules which should not be used, separated by a comma 196 | deprecated-modules=regsub,string,TERMIOS,Bastion,rexec 197 | 198 | # Create a graph of every (i.e. internal and external) dependencies in the 199 | # given file (report RP0402 must not be disabled) 200 | import-graph= 201 | 202 | # Create a graph of external dependencies in the given file (report RP0402 must 203 | # not be disabled) 204 | ext-import-graph= 205 | 206 | # Create a graph of internal dependencies in the given file (report RP0402 must 207 | # not be disabled) 208 | int-import-graph= 209 | 210 | 211 | [DESIGN] 212 | 213 | # Maximum number of arguments for function / method 214 | max-args=5 215 | 216 | # Argument names that match this expression will be ignored. Default to name 217 | # with leading underscore 218 | ignored-argument-names=_.* 219 | 220 | # Maximum number of locals for function / method body 221 | max-locals=15 222 | 223 | # Maximum number of return / yield for function / method body 224 | max-returns=6 225 | 226 | # Maximum number of branch for function / method body 227 | max-branchs=12 228 | 229 | # Maximum number of statements in function / method body 230 | max-statements=50 231 | 232 | # Maximum number of parents for a class (see R0901). 233 | max-parents=7 234 | 235 | # Maximum number of attributes for a class (see R0902). 236 | max-attributes=7 237 | 238 | # Minimum number of public methods for a class (see R0903). 239 | min-public-methods=0 240 | 241 | # Maximum number of public methods for a class (see R0904). 242 | max-public-methods=20 243 | 244 | 245 | [EXCEPTIONS] 246 | 247 | # Exceptions that will emit a warning when being caught. Defaults to 248 | # "Exception" 249 | # overgeneral-exceptions=Exception 250 | -------------------------------------------------------------------------------- /tests/test_nsr_lexer.py: -------------------------------------------------------------------------------- 1 | from oktest import ok 2 | from nsr_lexer import * 3 | 4 | data1 = \ 5 | """ 6 | ==== 7 | _h1_ 8 | ==== 9 | 10 | _h2_ 11 | ==== 12 | 13 | _h3_ 14 | ---- 15 | 16 | _h4_ 17 | ~~~~ 18 | 19 | MyParax 20 | yyyy 21 | 22 | """ 23 | 24 | data2 = \ 25 | """ 26 | raw: 27 | some_data 28 | 29 | python: 30 | with y: 31 | pass 32 | 33 | python: 34 | with y: 35 | pass 36 | 37 | x = y + 1 38 | 39 | Autor: koder 40 | 41 | rrrr : some data 42 | """ 43 | 44 | data3 = \ 45 | """ 46 | Some text: 47 | * X1 48 | * X2 49 | * X3 50 | """ 51 | 52 | test_data = { data1 : [(TEXT_H1, tuple(), "_h1_"), 53 | (TEXT_H2, tuple(), "_h2_"), 54 | (TEXT_H3, tuple(), "_h3_"), 55 | (TEXT_H4, tuple(), "_h4_"), 56 | (TEXT_PARA, tuple(), " MyParax\nyyyy")], 57 | data2 : [('raw', tuple(), " some_data"), 58 | ('python', tuple(), " with y:\n pass"), 59 | ('python', tuple(), " with y:\n pass\n\n x = y + 1"), 60 | ('Autor', tuple(), "koder"), 61 | (TEXT_PARA, tuple(), "rrrr : some data")] 62 | } 63 | 64 | def test(): 65 | for bdata, data_list in test_data.items(): 66 | for (need_tp, opts, need_data),(get_tp, get_data) in zip(data_list, parse(bdata)): 67 | ok(need_tp) == get_tp 68 | ok(need_data) == get_data 69 | 70 | def test_lex(): 71 | data = "python:x" 72 | lexems = list(lex(data)) 73 | ok(lexems).length(1) 74 | ok(lexems[0]) == (BLOCK_SLINE, [], ('python', 'x')) 75 | 76 | data = "python[1,2]:x" 77 | lexems = list(lex(data)) 78 | ok(lexems).length(1) 79 | ok(lexems[0]) == (BLOCK_SLINE, ['1', '2'], ('python', 'x')) 80 | 81 | data = "python[some_tag, ff, some_else_tag]:x" 82 | lexems = list(lex(data)) 83 | ok(lexems).length(1) 84 | ok(lexems[0]) == (BLOCK_SLINE, ['some_tag', 'ff', 'some_else_tag'], ('python', 'x')) 85 | 86 | data = "python:\n x = 1\n y = 2" 87 | lexems = list(lex(data)) 88 | ok(lexems).length(3) 89 | ok(lexems[0]) == (BLOCK_BEGIN, [], 'python') 90 | 91 | data = "python[1, some_tag, mmm ]:\n x = 1\n y = 2" 92 | lexems = list(lex(data)) 93 | ok(lexems).length(3) 94 | ok(lexems[0]) == (BLOCK_BEGIN, ['1', 'some_tag', 'mmm'], 'python') 95 | 96 | if __name__ == "__main__": 97 | test() 98 | test_lex() 99 | -------------------------------------------------------------------------------- /tests/test_pm.py: -------------------------------------------------------------------------------- 1 | import python_match 2 | 3 | def on_val(x): 4 | print "In on_val :", repr(x) 5 | return "it works!" 6 | 7 | def printM(obj, val): 8 | print "In print m", obj, val 9 | 10 | class M(object): 11 | pass 12 | 13 | @python_match.update 14 | def test(): 15 | #x = M() 16 | #x.c = 12 17 | #x.d = 11 18 | 19 | x = 1 20 | 21 | with python_match.match(x) as res: 22 | 1 >> 2 23 | 2 >> on_val(2) 24 | int >> on_val(int) 25 | M(c=V_c, d=V_c) >> on_val("equal") 26 | M(c=V_c, d=V_d) >> printM(x, V_c) 27 | 28 | print res.res 29 | 30 | test() 31 | --------------------------------------------------------------------------------