tag:blogger.com,1999:blog-11744897157774307432024-03-11T05:21:23.541+02:00о python, it, виртуализации и Coблог о python, виртуализации, программировании и другомAnonymoushttp://www.blogger.com/profile/06645078574760734586noreply@blogger.comBlogger23125tag:blogger.com,1999:blog-1174489715777430743.post-89588333964781376502013-12-12T23:09:00.000+02:002013-12-12T23:09:50.832+02:00Подчеркнутая защищенность<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<p style="text-indent:20px"> Инкапсуляция - одна из основ ООП. Мы договариваемся использовать только часть функциональности класса, а взамен получаем возможность работать с самыми разными типами, даже с теми, которые будут написаны после окончания работы над текущим кодом.</p><p style="text-indent:20px"> Компилируемые языки реализуют инкапсуляцию методом принуждения. Программист отмечает методы и поля как личные или защищенные, а компилятор играет в большого брата и проверяет что все используется в корректном контексте. На моей памяти война за способ использования <b>private/protected</b> минимум пару раз принимала нешуточный оборот.</p><p style="text-indent:20px"> Попадая в питон С++/Java-программисты начинают искать замену родным private/protected в этом мире безудержного эксгибиционизма. И, как правило, быстро находят два подчеркивания. Не совсем то, что хотелось бы, но довольно сильно похоже на private. В итоге нижнее подчеркивание быстро становится самым популярным символом в коде.</p><p style="text-indent:20px">Я попробую показать, что:</p><ul><li>'__' - не эквивалент <b>private</b> и решает совсем другие задачи;<li>Можно отлично жить без <b>private/protected/friend</b>. Оружие массового запрещения не единственный способ реализовать инкапсуляцию;<li>При желании можно написать аналог <b>private/protected</b> и даже более гибкий контроль доступа для python (в следующем посте)</ul><a name='more'></a><p style="text-indent:20px"> Итак зачем в python поля с двумя подчеркиваниями в начале имени. Пусть у нас есть такой код:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="975b4db0637111e3a94ebc7737dae05d" objtohide2="975b8c6c637111e3a94ebc7737dae05d" >Без подсветки синтаксиса</a><br><span id="975b4db0637111e3a94ebc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">from</span> <span style="color: #0000FF; font-weight: bold">some_module</span> <span style="color: #008000; font-weight: bold">import</span> SomeClass
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">SomeClassChildren</span>(SomeClass):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>):
<span style="color: #008000">super</span>(SomeClassChildren, <span style="color: #008000">self</span>)<span style="color: #666666">.</span>__init__()
<span style="color: #008000">self</span><span style="color: #666666">.</span>some_field <span style="color: #666666">=</span> <span style="color: #666666">12</span>
</pre></div></span><span style="line-height:100%;display:none" id="975b8c6c637111e3a94ebc7737dae05d"><pre><font face="courier">from some_module import SomeClass
class SomeClassChildren(SomeClass):
def __init__(self):
super(SomeClassChildren, self).__init__()
self.some_field = 12</font></pre></span><p style="text-indent:20px"> Допустим код <b>SomeClass</b> очень большой или нам не доступен или постоянно неконтролируемо меняется или по любой другой причине мы не может быть уверенны, что какое бы благозвучное имя не было выбрано для <b>some_field</b> мы не можем быть уверенны, что не затрем поле с таким же именем в родительском классе. Компилируемый язык решил бы эту проблему, не позволив нам создать поле, если поле с таким именем уже унаследовано. Это не решает проблему полностью, но избавляет нас от странного поведения.</p><p style="text-indent:20px"> Для этого в питоне и есть поля с двумя подчеркиваниями в начале (но без двух подчеркиваний в конце). Когда компилятор питона видит подобное имя он дописывает к нему в начало еще одно подчеркивание и имя текущего компилируемого класса. Это можно увидеть с помощью питоновского дизассемблера:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="975c6ec0637111e3a94ebc7737dae05d" objtohide2="975caca0637111e3a94ebc7737dae05d" >Без подсветки синтаксиса</a><br><span id="975c6ec0637111e3a94ebc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">dis</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">A</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">func</span>(<span style="color: #008000">self</span>, x):
<span style="color: #008000">self</span><span style="color: #666666">.</span>__attr1
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">B</span>(A):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">func</span>(<span style="color: #008000">self</span>, x):
<span style="color: #008000">self</span><span style="color: #666666">.</span>__attr2
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">C</span>(A):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">func</span>(<span style="color: #008000">self</span>, x):
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">C1</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">func</span>(<span style="color: #008000">self</span>):
x<span style="color: #666666">.</span>__attr3
dis<span style="color: #666666">.</span>dis(C1<span style="color: #666666">.</span>func)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">r</span>(<span style="color: #008000">self</span>):
<span style="color: #008000">self</span><span style="color: #666666">.</span>__attr4
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">D</span>(<span style="color: #008000">object</span>):
func <span style="color: #666666">=</span> r
dis<span style="color: #666666">.</span>dis(A<span style="color: #666666">.</span>func)
dis<span style="color: #666666">.</span>dis(B<span style="color: #666666">.</span>func)
C()<span style="color: #666666">.</span>func(A())
dis<span style="color: #666666">.</span>dis(D<span style="color: #666666">.</span>func)
dis<span style="color: #666666">.</span>dis(<span style="color: #008000; font-weight: bold">lambda</span>: A<span style="color: #666666">.</span>__attr5)
</pre></div></span><span style="line-height:100%;display:none" id="975caca0637111e3a94ebc7737dae05d"><pre><font face="courier">import dis
class A(object):
def func(self, x):
self.__attr1
class B(A):
def func(self, x):
self.__attr2
class C(A):
def func(self, x):
class C1(object):
def func(self):
x.__attr3
dis.dis(C1.func)
def r(self):
self.__attr4
class D(object):
func = r
dis.dis(A.func)
dis.dis(B.func)
C().func(A())
dis.dis(D.func)
dis.dis(lambda: A.__attr5)</font></pre></span><pre><font face="courier"> # dis.dis(A.func)
0 LOAD_FAST 0 (self) << object
3 LOAD_ATTR 0 (_A__attr1) << attribute name
# == self._A__attr1
# dis.dis(B.func)
0 LOAD_FAST 0 (self)
3 LOAD_ATTR 0 (_B__attr2)
# dis.dis(C1.func)
0 LOAD_DEREF 0 (x)
3 LOAD_ATTR 0 (_C1__attr3)
# dis.dis(D.func)
0 LOAD_FAST 0 (self)
3 LOAD_ATTR 0 (__attr4)
# dis.dis(lambda: A.__attr5)
0 LOAD_GLOBAL 0 (A)
3 LOAD_ATTR 1 (__attr5)</font></pre><p style="text-indent:20px"> Итого '__' приводит к переименованию поля и позволяет использовать поля с одинаковыми именами в разных классах одной иерархии. Но это не мешает добраться до такого поля, например <b>x._X__some_priv_field</b>. BTW - если нужно сделать действительно скрытое поле, то можно и так:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="975d6668637111e3a94ebc7737dae05d" objtohide2="975d6d02637111e3a94ebc7737dae05d" >Без подсветки синтаксиса</a><br><span id="975d6668637111e3a94ebc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">MyProxy</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>, proxifyed):
<span style="color: #008000">self</span><span style="color: #666666">.</span>__dict__[<span style="color: #008000">self</span>] <span style="color: #666666">=</span> proxifyed <span style="color: #408080; font-style: italic"># <<<</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__getattr__</span>(<span style="color: #008000">self</span>, name):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">getattr</span>(<span style="color: #008000">self</span><span style="color: #666666">.</span>__dict__[<span style="color: #008000">self</span>], name)
</pre></div></span><span style="line-height:100%;display:none" id="975d6d02637111e3a94ebc7737dae05d"><pre><font face="courier">class MyProxy(object):
def __init__(self, proxifyed):
self.__dict__[self] = proxifyed # <<<
def __getattr__(self, name):
return getattr(self.__dict__[self], name)</font></pre></span><p style="text-indent:20px"> <b>self.__dict__</b> - обычный словарь и ключами в нем могут быть не только строки. Злоупотреблять таким хаком не стоит поскольку много различных библиотек, например сериализаторы, сильно удивятся увидев в <b>__dict__</b> нестроковой ключ.</p><p style="text-indent:20px"> Итак: '__' - это очень специфический аналог частного поля и он предназначен для несколько других целей.</p><p style="text-indent:20px"> Модификаторы доступа защищают программиста от случайного и преднамеренного доступа к тем частям API, которые разработчик класса захотел скрыть.</p><p style="text-indent:20px">Начнем со случайного доступа на примере С++. Случайно в нем можно вызвать:</p><ul><li>конструктор (присваиванием, в контейнере при копировании, etc)<li>деструктор (по выходу объекта или его владельца из области видимости)<li>оператор преобразования типа<li>опечатавшись, скопировав неправильно код, etc</ul><p style="text-indent:20px"> Последний пункт скорее из области фантастики. Все остальные тоже не применимы к питону. Питон не копирует объекты, все всегда передается и хранится по ссылке, <b>deepcopy/loads</b> не вызывают конструктор. Деструктор вызывается непонятно когда и чаще всего его вызов сложно контролировать. Доступ к имеющиеся преобразования типов бессмысленно запрещать (<b>__str__</b>, <b>__int__</b>). Так что операции, выполняемые питоном без явного указания программиста не особо нуждаются в разграничении доступа.</p><p style="text-indent:20px"> Кроме того в С++ в указанных случаях мы получим ошибку на этапе компиляции, а в случае с питоном - в этапе исполнения, когда уже будет не очень понятно что с нею делать.</p><p style="text-indent:20px"> Перейдем к преднамеренному вызову защищенного метода. Если очень хочется, то никакой private/protected не остановит:</p><pre><font face="courier"> #define protected public
#include "foo.h"
#undef protected</font></pre><p style="text-indent:20px"> Есть еще 666 способов добраться до защищенного метода. Есть они и в Java (reflections) и в C#, иначе <a href="http://koder-ua.blogspot.com/2013/11/python.html">контейнеры внедрения зависимостей</a> не смогли бы работать.</p><p style="text-indent:20px"> Итого - бессмысленно защищаться и от преднамеренного вызова. Всегда есть способ обойти такую защиту. Да и если кто-то очень хочет - пусть вызывает. А для защиты от случайного вызова обычно достаточно просто не упоминать метод/поле в документации.</p><p style="text-indent:20px"><b>Документация - первая линия инкапсуляции в python.</b></p><p style="text-indent:20px"> В С++ чтение заголовочных файлов - достаточно распространенный способ получения документации и модификаторы доступа в данном случае тоже помогают. В python чтение исходников затруднено тем, что код и прототип смешаны. При желании для донесения информации до читающих исходники случая можно помечать функции декоратором, подобным этому:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="975e0bcc637111e3a94ebc7737dae05d" objtohide2="975e0e38637111e3a94ebc7737dae05d" >Без подсветки синтаксиса</a><br><span id="975e0bcc637111e3a94ebc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">protected</span>(func):
<span style="color: #008000; font-weight: bold">return</span> func
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">X</span>(<span style="color: #008000">object</span>):
<span style="color: #AA22FF">@protected</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">some_method</span>(<span style="color: #008000">self</span>):
<span style="color: #008000; font-weight: bold">pass</span>
</pre></div></span><span style="line-height:100%;display:none" id="975e0e38637111e3a94ebc7737dae05d"><pre><font face="courier">def protected(func):
return func
class X(object):
@protected
def some_method(self):
pass</font></pre></span><p style="text-indent:20px"> Наконец очень распространенный способ посмотреть что можно сделать с объектом это интроспекция. Например, если нажать <tab> после <b>x.</b> в ipython, то он покажет какие поля и методы есть у объекта <b>x</b>. Для управлением видимостью методов/полей в случае такой интроспекции в питоне есть метод <b>__dir__</b>:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="975e6694637111e3a94ebc7737dae05d" objtohide2="975e68d8637111e3a94ebc7737dae05d" >Без подсветки синтаксиса</a><br><span id="975e6694637111e3a94ebc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">X</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__dir__</span>(<span style="color: #008000">self</span>):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #BA2121">'public_method public_field'</span><span style="color: #666666">.</span>split()
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>):
<span style="color: #008000">self</span><span style="color: #666666">.</span>public_field <span style="color: #666666">=</span> <span style="color: #666666">12</span>
<span style="color: #008000">self</span><span style="color: #666666">.</span>protected_field <span style="color: #666666">=</span> <span style="color: #666666">13</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">public_method</span>(<span style="color: #008000">self</span>):
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">protected_method</span>(<span style="color: #008000">self</span>):
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #008000">dir</span>(X()) <span style="color: #408080; font-style: italic"># == ['public_method', 'public_field']</span>
</pre></div></span><span style="line-height:100%;display:none" id="975e68d8637111e3a94ebc7737dae05d"><pre><font face="courier">
class X(object):
def __dir__(self):
return 'public_method public_field'.split()
def __init__(self):
self.public_field = 12
self.protected_field = 13
def public_method(self):
pass
def protected_method(self):
pass
dir(X()) # == ['public_method', 'public_field']</font></pre></span><p style="text-indent:20px"><b>Интроспеция - вторая линия инкапсуляции в python.</b></p><p style="text-indent:20px"> Пользуйтесь этими способами и не засоряйте питон подчеркиваниями. В следующем посте мы объединим автоматизируем <b>dir</b> и сделаем для питона аналог <b>private</b>.</p>Ссылки:<br> <a name="контейнеры_внедрения_зависимостей"><a href="http://koder-ua.blogspot.com/2013/11/python.html">koder-ua.blogspot.com/2013/11/python.html</a></a><br><p style="text-indent:20px">Исходники этого и других постов со скриптами лежат тут - <a href="https://github.com/koder-ua/python-lectures">github.com/koder-ua</a>. При использовании их, пожалуйста, ссылайтесь на <a href="http://koder-ua.blogspot.com/">koder-ua.blogspot.com</a>.</p>
<script type="text/javascript">
function on_hidabble_click()
{
var me = $(this);
var hide_id = me.attr("objtohide");
var controlled_object = $('#' + hide_id);
controlled_object.toggle();
if ( controlled_object.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".hidder").click(on_hidabble_click);
function on_double_hidabble_click()
{
var me = $(this);
var hide_id1 = me.attr("objtohide1");
var hide_id2 = me.attr("objtohide2");
var controlled_object1 = $('#' + hide_id1);
var controlled_object2 = $('#' + hide_id2);
controlled_object1.toggle();
controlled_object2.toggle();
if ( controlled_object1.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".dhidder").click(on_double_hidabble_click);
</script>
Anonymoushttp://www.blogger.com/profile/06645078574760734586noreply@blogger.com26tag:blogger.com,1999:blog-1174489715777430743.post-81166379769391838782013-11-28T13:44:00.000+02:002013-11-28T13:44:55.922+02:00Функциональный стиль в питоне<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<p style="text-indent:20px"> <b>Пост чисто философский</b></p><p style="text-indent:20px"> Периодическое чтение кусков кода, написанных при обострении хаскеля головного мозга, выработало у меня четкую ассоциацию: функциональный стиль - это нечитаемо. Точнее стиль с множеством map/filter/zip. Вот немного облагороженный пример такого кода (автор считает, что с кодом все ок):</p><a name='more'></a><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="e8865280582111e3ae3914feb5b819a0" objtohide2="e886f1e0582111e3ae3914feb5b819a0" >Без подсветки синтаксиса</a><br><span id="e8865280582111e3ae3914feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">some_res <span style="color: #666666">=</span> <span style="color: #BA2121">", "</span><span style="color: #666666">.</span>join(
<span style="color: #008000">map</span>(<span style="color: #008000">str</span>,
<span style="color: #008000">filter</span>(<span style="color: #008000">None</span>,
<span style="color: #008000">map</span>(<span style="color: #008000; font-weight: bold">lambda</span> x: <span style="color: #008000">getattr</span>(obj<span style="color: #666666">.</span>zip, x, <span style="color: #008000">None</span>),
[<span style="color: #BA2121">'a'</span>, <span style="color: #BA2121">'b'</span>, <span style="color: #BA2121">'c'</span>, <span style="color: #BA2121">'d'</span>]))))
</pre></div></span><span style="line-height:100%;display:none" id="e886f1e0582111e3ae3914feb5b819a0"><pre><font face="courier">some_res = ", ".join(
map(str,
filter(None,
map(lambda x: getattr(obj.zip, x, None),
['a', 'b', 'c', 'd']))))</font></pre></span><p style="text-indent:20px"> Без переписывания в многострочный вариант ориентироваться в нем вообще сложно:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="e887f0f4582111e3ae3914feb5b819a0" objtohide2="e8884efa582111e3ae3914feb5b819a0" >Без подсветки синтаксиса</a><br><span id="e887f0f4582111e3ae3914feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">attrs <span style="color: #666666">=</span> [<span style="color: #BA2121">'a'</span>, <span style="color: #BA2121">'b'</span>, <span style="color: #BA2121">'c'</span>, <span style="color: #BA2121">'d'</span>]
attr_vals <span style="color: #666666">=</span> <span style="color: #008000">map</span>(<span style="color: #008000; font-weight: bold">lambda</span> x: <span style="color: #008000">getattr</span>(obj<span style="color: #666666">.</span>zip, x, <span style="color: #008000">None</span>), attrs)
non_none_attrs <span style="color: #666666">=</span> <span style="color: #008000">filter</span>(<span style="color: #008000">None</span>, attr_vals)
some_res <span style="color: #666666">=</span> <span style="color: #BA2121">", "</span><span style="color: #666666">.</span>join(<span style="color: #008000">map</span>(<span style="color: #008000">str</span>, non_none_attrs))
</pre></div></span><span style="line-height:100%;display:none" id="e8884efa582111e3ae3914feb5b819a0"><pre><font face="courier">attrs = ['a', 'b', 'c', 'd']
attr_vals = map(lambda x: getattr(obj.zip, x, None), attrs)
non_none_attrs = filter(None, attr_vals)
some_res = ", ".join(map(str, non_none_attrs))</font></pre></span><p style="text-indent:20px"> Этот вариант читается лучше уже потому, что понизилась концентрация логики, и добавились переменные, имена которых служат документацией промежуточным результатам. Но, IMO, это не главные причины.</p><p style="text-indent:20px"> Первом меня на эту мысль натолкнуло наблюдение за девушкой, которая только начал учить питон и не программировала серьезно до этого. У нее вызывало затруднение определить какие параметры относятся к какой функции даже в достаточно простых выражениях, например:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="e888f526582111e3ae3914feb5b819a0" objtohide2="e888fa9e582111e3ae3914feb5b819a0" >Без подсветки синтаксиса</a><br><span id="e888f526582111e3ae3914feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">x <span style="color: #666666">=</span> some_func1(a, b, some_func2(c, d), e)
</pre></div></span><span style="line-height:100%;display:none" id="e888fa9e582111e3ae3914feb5b819a0"><pre><font face="courier">x = some_func1(a, b, some_func2(c, d), e)</font></pre></span><p style="text-indent:20px"> Понятно, что со временем это прошло, но осадок остался - в выражения где много вложенных вызовов и скобок сложно быстро соотнести параметры, функции и удерживать это в голове, пока его анализируешь. Если код не форматирован построчно, как пример выше, то совсем тяжело.</p><p style="text-indent:20px"> Следующий случай - это функциональный стиль в скале. Его чтение у меня не вызывает того чувства трясины, какое вызывал аналогичный код в python/haskell. Тот же пример на 'скалапитоне' выглядел бы так:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="e8894dbe582111e3ae3914feb5b819a0" objtohide2="e889505c582111e3ae3914feb5b819a0" >Без подсветки синтаксиса</a><br><span id="e8894dbe582111e3ae3914feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">some_res <span style="color: #666666">=</span> [<span style="color: #BA2121">'a'</span>, <span style="color: #BA2121">'b'</span>, <span style="color: #BA2121">'c'</span>, <span style="color: #BA2121">'d'</span>]<span style="color: #666666">.</span>map(<span style="color: #008000">getattr</span>(obj<span style="color: #666666">.</span>zip, _, <span style="color: #008000">None</span>))\
<span style="color: #666666">.</span>filter(<span style="color: #008000">None</span>)<span style="color: #666666">.</span>map(<span style="color: #008000">str</span>)<span style="color: #666666">.</span>join(<span style="color: #BA2121">","</span>)
</pre></div></span><span style="line-height:100%;display:none" id="e889505c582111e3ae3914feb5b819a0"><pre><font face="courier">some_res = ['a', 'b', 'c', 'd'].map(getattr(obj.zip, _, None))\
.filter(None).map(str).join(",")</font></pre></span><p style="text-indent:20px"> Если отвлечься от более удобной формы записи <a href="http://ananthakumaran.in/2010/03/29/scala-underscore-magic.html">лямбды</a>, то он все равно читается гораздо проще. Мне кажется дело в том, что он читается линейно слева направо, а не "вообще изнутри наружу, но местами слева направо", как читается код в питоне.</p><p style="text-indent:20px"> Это относится скорее не к функциональному стилю, а к процедурный vs ООП, но именно функциональный стиль провоцирует избавление от переменных и написание множества вложенных вызовов функций. Он как лакмусовая бумажка вскрывает плохую масштабируемость читаемости процедурных выражений:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="e8899f1c582111e3ae3914feb5b819a0" objtohide2="e889a322582111e3ae3914feb5b819a0" >Без подсветки синтаксиса</a><br><span id="e8899f1c582111e3ae3914feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">a(x, b(c(), <span style="color: #666666">1</span>, <span style="color: #666666">4</span>), <span style="color: #BA2121">'d'</span>)
<span style="color: #408080; font-style: italic"># vs</span>
c()<span style="color: #666666">.</span>b(<span style="color: #666666">1</span>, <span style="color: #666666">4</span>)<span style="color: #666666">.</span>a(x, <span style="color: #BA2121">'d'</span>)
</pre></div></span><span style="line-height:100%;display:none" id="e889a322582111e3ae3914feb5b819a0"><pre><font face="courier">a(x, b(c(), 1, 4), 'd')
# vs
c().b(1, 4).a(x, 'd')</font></pre></span><p style="text-indent:20px"> К сожалению питон чаще всего не позволяет писать сцепленными методами, поскольку бОльщая часть методов возвращает None вместо self (а именно все, которые модифицируют объект на месте), а map/filter - функции, а не методы.</p><p style="text-indent:20px"> Итого я для себя сменил идею с "функциональный код нечитаем" на "функциональный код, написанный в процедурном стиле, нечитаем".</p>Ссылки:<br> <a name="лямбды"><a href="http://ananthakumaran.in/2010/03/29/scala-underscore-magic.html">ananthakumaran.in/2010/03/29/scala-underscore-magic.html</a></a><br><p style="text-indent:20px">Исходники этого и других постов со скриптами лежат тут - <a href="https://github.com/koder-ua/python-lectures">github.com/koder-ua</a>. При использовании их, пожалуйста, ссылайтесь на <a href="http://koder-ua.blogspot.com/">koder-ua.blogspot.com</a>.</p>
<script type="text/javascript">
function on_hidabble_click()
{
var me = $(this);
var hide_id = me.attr("objtohide");
var controlled_object = $('#' + hide_id);
controlled_object.toggle();
if ( controlled_object.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".hidder").click(on_hidabble_click);
function on_double_hidabble_click()
{
var me = $(this);
var hide_id1 = me.attr("objtohide1");
var hide_id2 = me.attr("objtohide2");
var controlled_object1 = $('#' + hide_id1);
var controlled_object2 = $('#' + hide_id2);
controlled_object1.toggle();
controlled_object2.toggle();
if ( controlled_object1.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".dhidder").click(on_double_hidabble_click);
</script>
Anonymoushttp://www.blogger.com/profile/06645078574760734586noreply@blogger.com1tag:blogger.com,1999:blog-1174489715777430743.post-61359502685105466062013-11-23T20:27:00.000+02:002013-11-24T02:49:42.591+02:00Контейнеры внедрения зависимостей для python<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<p style="text-indent:20px"> Начнем издалека - создание объекта инстанцированием класса <a href="http://gbracha.blogspot.com/2007/06/constructors-considered-harmful.html">плохо совместимо</a> с идеями ООП. Они гласят, что код должен зависеть от интерфейсов, а не от реализаций. До тех пор пока на вход нашему коду приходят готовые объекты - все хорошо. Он будет с готовностью принимать любые типы, реализующие требуемый интерфейс, но как только мы начинаем создавать новые объекты ситуация меняется. Теперь код зависит от конкретного класса, что усложняет следующие задачи:</p><ul><li>Изменение класса на другой, хоть и реализующий тот же интерфейс. Приходится вручную менять все точки инстанцирования, и, возможно, перекомпилировать код;<li>Выбор конкретного класса на основе внешних условий или точки инстанцирования;<li>Использование уже готового объекта - взятого из пула или какого то конкретного (синглетон);<li>Построение объекта с большим количеством зависимостей - приходиться передавать в точку конструирования все данные для построения множества взаимосвязанных объектов;<li>Не классическая проблема для ICC, но из той-же области:</ul><a name='more'></a><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="b9d4a2d054a111e3b455bc7737dae05d" objtohide2="b9d522aa54a111e3b455bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="b9d4a2d054a111e3b455bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">A</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>, val):
<span style="color: #008000">self</span><span style="color: #666666">.</span>val <span style="color: #666666">=</span> val
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__add__</span>(<span style="color: #008000">self</span>, val):
<span style="color: #008000; font-weight: bold">return</span> A(<span style="color: #008000">self</span><span style="color: #666666">.</span>val <span style="color: #666666">+</span> val)
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">B</span>(A):
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #008000; font-weight: bold">print</span> B(<span style="color: #666666">1</span>) <span style="color: #666666">+</span> <span style="color: #666666">1</span> <span style="color: #408080; font-style: italic"># <__main__.A object at 0x18877d0></span>
</pre></div></span><span style="line-height:100%;display:none" id="b9d522aa54a111e3b455bc7737dae05d"><pre><font face="courier">class A(object):
def __init__(self, val):
self.val = val
def __add__(self, val):
return A(self.val + val)
class B(A):
pass
print B(1) + 1 # <__main__.A object at 0x18877d0></font></pre></span><p style="text-indent:20px">А хотелось бы получить экземпляр В.</p><p style="text-indent:20px"> Все эти проблемы связаны общей причиной - код делает работу, которую он делать не должен - инстанцирование конкретных классов. На самом деле чаще всего нам не нужен фиксированный класс. Нам нужен класс, предоставляющий определенный интерфейс. Нужно отвязаться от явного указания класса и передать его создание стороннему коду. Фактически мы хотим "виртуализовать" инстанцирование.</p><p style="text-indent:20px"> В самом простом случае можно воспользоваться <a href="http://en.wikipedia.org/wiki/Factory_method_pattern">фабричной функцией(ФФ)</a>. Если же мы хотим конфигурировать поведение ФФ, или сохранять состояние между вызовами (синглетон, пул объектов, etc), то логично сделать ФФ методом класса, в экземпляре которого будут храниться настройки. Такой класс может быть синглетоном(если конфигурация глобальная), или передаваться образом по цепочке вызовов во все точки, где нужно инстанцирование. Этот класс как раз и называется Inversion of Control Container (ICC дальше).</p><p style="text-indent:20px"> Для его использования нужно заменить прямое инстанцирование классов на вызов метода ICC. Параметрами метода будут требуемый интерфейс, и, возможно, контекст вызова и часть параметров для конструктора (последнее применяется редко). ICC возвращает готовый экземпляр. Конкретный класс для инстанцирования и параметры конструктора настраиваются програмно или берутся из конфигурационного файла.</p><p style="text-indent:20px"> Типичный пример - <a href="http://koder-ua.blogspot.com/2011/12/libvirt-co-1.html">создание</a> виртуальной машины в libvirt. Основная функция API принимает xml строку, описывающую виртуальную машину. Эта строка чаще всего берется вызывающим кодом из внешнего источника, потому как в большинстве случаев ему не важны подробности конфигурации для работы с VM соответственно и код создания можно унифицировать, а строку с конфигурацией использовать как черный ящик.</p><p style="text-indent:20px"> ICC также можно рассматривать как шаблон проектирования, объединяющий и унифицирующий другие порождающие шаблоны - ФФ, синглетон, и прочее.</p><p style="text-indent:20px"> Java и C# имеет различные реализации ICC (<a href="http://ru.wikipedia.org/wiki/Spring_Framework#Inversion_of_Control">java spring</a>, <a href="https://github.com/square/dagger">dagger</a>) которые используются очень широко. Для питона же они практически не применяются. Сначала я покажу как написать pythonic ICC, а потом рассмотрю почему он не нужен. Написание своего связанно с тем, что по уже готовые пишутся людьми только что пришедшими с Java/C# и не отличаются питонистичностью.</p><p style="text-indent:20px"> Итак что можно хотеть от идеального ICC? Во-первых оставить пользовательский код почти без изменений. Во-вторых поддерживать возможность возвращать при инстанцировании целевого класса экземпляры другого класса, или определенный объект или результат вызова некоторой функции.</p><p style="text-indent:20px">Итак был такой код:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="b9d664ee54a111e3b455bc7737dae05d" objtohide2="b9d6d61854a111e3b455bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="b9d664ee54a111e3b455bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Bee</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>, x):
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Cee</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>, x):
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #008000; font-weight: bold">assert</span> <span style="color: #008000">isinstance</span>(Bee(<span style="color: #666666">1</span>), Bee)
</pre></div></span><span style="line-height:100%;display:none" id="b9d6d61854a111e3b455bc7737dae05d"><pre><font face="courier">class Bee(object):
def __init__(self, x):
pass
class Cee(object):
def __init__(self, x):
pass
assert isinstance(Bee(1), Bee)</font></pre></span><p style="text-indent:20px"> Мы хотим иметь возможность не меняя код инстанцирования Bee выбирать что именно будет получаться - экземпляр Bee или Cee. С позиции duck typing классы Bee и Cee реализуют один и тот-же интерфейс и взаимозаменяемы, хоть мы это и не декларируем явным наследованием.</p><p style="text-indent:20px"> В принципе инстанцирование можно и не менять, но тогда его поведение будет не совсем очевидным. С первого взгляда кажется, что мы инстанцируем обычный класс Bee, а в итоге получаем экземпляр другого класса, который к классу Bee никакого отношения не имеет. Т.е. isinstance(Bee(), Bee) == False. Поэтому немного изменим пример. Bee и Cee будут наследовать общий интерфейс IBee и именно этот интерфейс мы и будем инстанцировать.</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="b9d7901254a111e3b455bc7737dae05d" objtohide2="b9d794cc54a111e3b455bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="b9d7901254a111e3b455bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">IBee</span>(IOCInterface):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>, x):
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Bee</span>(IBee):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>, x):
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"Bee.__init__ called"</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Cee</span>(IBee):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>, x):
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"Cee.__init__ called"</span>
IBee<span style="color: #666666">.</span>register(Bee)
<span style="color: #008000; font-weight: bold">assert</span> <span style="color: #008000">isinstance</span>(IBee(<span style="color: #666666">1</span>), Bee)
IBee<span style="color: #666666">.</span>register(Cee)
<span style="color: #008000; font-weight: bold">assert</span> <span style="color: #008000">isinstance</span>(IBee(<span style="color: #666666">1</span>), Cee)
</pre></div></span><span style="line-height:100%;display:none" id="b9d794cc54a111e3b455bc7737dae05d"><pre><font face="courier">class IBee(IOCInterface):
def __init__(self, x):
pass
class Bee(IBee):
def __init__(self, x):
print "Bee.__init__ called"
class Cee(IBee):
def __init__(self, x):
print "Cee.__init__ called"
IBee.register(Bee)
assert isinstance(IBee(1), Bee)
IBee.register(Cee)
assert isinstance(IBee(1), Cee)</font></pre></span><p style="text-indent:20px"> Что бы это работало нужно перехватить конструирование объекта типа IBee и вернуть что-мы-там-хотим. Для этого вспоминаем, что конструирование объекта в python выражается следующим псевдокодом:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="b9d7dcd454a111e3b455bc7737dae05d" objtohide2="b9d7dea054a111e3b455bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="b9d7dcd454a111e3b455bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #408080; font-style: italic"># obj = Cls(x, y) ==></span>
obj <span style="color: #666666">=</span> Cls<span style="color: #666666">.</span>__new__(Cls, x, y)
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #008000">isinstance</span>(obj, Cls):
Cls<span style="color: #666666">.</span>__init__(obj, x, y)
</pre></div></span><span style="line-height:100%;display:none" id="b9d7dea054a111e3b455bc7737dae05d"><pre><font face="courier"># obj = Cls(x, y) ==>
obj = Cls.__new__(Cls, x, y)
if isinstance(obj, Cls):
Cls.__init__(obj, x, y)</font></pre></span><p style="text-indent:20px"> Т.е. Cls.__new__ возвращает пустой экземпляр типа Cls, Cls.__init__ наполняет его реальными данными. Очень похоже на operator new + конструктор в С++. Итак нам нужно перегрузить IBee.__new__ и возвращать из него наш объект.</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="b9d8494454a111e3b455bc7737dae05d" objtohide2="b9d84b1054a111e3b455bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="b9d8494454a111e3b455bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">ioc <span style="color: #666666">=</span> {}
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">IOCInterface</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__new__</span>(cls, <span style="color: #666666">*</span>args, <span style="color: #666666">**</span>kwargs):
<span style="color: #008000; font-weight: bold">return</span> ioc[cls](cls, <span style="color: #666666">*</span>args, <span style="color: #666666">**</span>kwargs)
<span style="color: #AA22FF">@classmethod</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">register</span>(cls, impl):
factory <span style="color: #666666">=</span> <span style="color: #008000; font-weight: bold">lambda</span> ccls, <span style="color: #666666">*</span>args, <span style="color: #666666">**</span>kwargs: \
<span style="color: #008000">super</span>(IOCInterface, ccls)<span style="color: #666666">.</span>__new__(impl, <span style="color: #666666">*</span>args, <span style="color: #666666">**</span>kwargs)
cls<span style="color: #666666">.</span>register_factory(factory)
<span style="color: #AA22FF">@classmethod</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">register_instance</span>(cls, obj):
cls<span style="color: #666666">.</span>register_factory(<span style="color: #008000; font-weight: bold">lambda</span> <span style="color: #666666">*</span>args, <span style="color: #666666">**</span>kwargs: obj)
<span style="color: #AA22FF">@classmethod</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">register_factory</span>(cls, func):
ioc[cls] <span style="color: #666666">=</span> func
</pre></div></span><span style="line-height:100%;display:none" id="b9d84b1054a111e3b455bc7737dae05d"><pre><font face="courier">ioc = {}
class IOCInterface(object):
def __new__(cls, *args, **kwargs):
return ioc[cls](cls, *args, **kwargs)
@classmethod
def register(cls, impl):
factory = lambda ccls, *args, **kwargs: \
super(IOCInterface, ccls).__new__(impl, *args, **kwargs)
cls.register_factory(factory)
@classmethod
def register_instance(cls, obj):
cls.register_factory(lambda *args, **kwargs: obj)
@classmethod
def register_factory(cls, func):
ioc[cls] = func</font></pre></span><p style="text-indent:20px"> Немного пояснений. Класс IOCInterface будет базовым для всех интерфейсов. Переменная ioc будет хранить текущую конфигурацию - отображение интерфейса на фабричную функцию для этого интерфейса. Для простоты примера мы будем хранить конфигурацию в глобальной переменной. Перегруженный метод __new__ получает инстанцируемый класс первым параметром, а дальше идут параметры конструктора. Он берет зарегистрированную для этого класса фабричную функцию и создает новый объект с ее помощью. IOCInterface.register позволяет зарегистрировать класс для данного интерфейса. IOCInterface.register_instance - зарегистрировать синглетон. Для унификации они создают специальные фабричные функции.</p><p style="text-indent:20px">Замечания:</p><ul><li>Нельзя использовать cls.__new__ как фабричную функцию в IOCInterface.register, так как мы получим вечный цикл. Нужно "проскочить" IOCInterface в иерархии сcls;<li>Для классов с перегруженным __new__ нужно смотреть по ситуации;<li>Есть соблазн просто сохранять класс/синглетон в словарь и потом в __new__</ul><p style="text-indent:20px">делать что-то вида;</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="b9d8b76254a111e3b455bc7737dae05d" objtohide2="b9d8b92e54a111e3b455bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="b9d8b76254a111e3b455bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__new__</span>(cls, <span style="color: #666666">*</span>args, <span style="color: #666666">**</span>kwargs):
obj <span style="color: #666666">=</span> ioc[cls]
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #008000">isinstance</span>(obj, <span style="color: #008000">type</span>):
<span style="color: #008000; font-weight: bold">return</span> obj(cls, <span style="color: #666666">*</span>args, <span style="color: #666666">**</span>kwargs)
<span style="color: #008000; font-weight: bold">elif</span> <span style="color: #008000">type</span>(obj, (types<span style="color: #666666">.</span>FunctionType, types<span style="color: #666666">.</span>LambdaType)):
<span style="color: #008000; font-weight: bold">return</span> obj(cls, <span style="color: #666666">*</span>args, <span style="color: #666666">**</span>kwargs)
<span style="color: #008000; font-weight: bold">else</span>:
<span style="color: #008000; font-weight: bold">return</span> obj
</pre></div></span><span style="line-height:100%;display:none" id="b9d8b92e54a111e3b455bc7737dae05d"><pre><font face="courier">def __new__(cls, *args, **kwargs):
obj = ioc[cls]
if isinstance(obj, type):
return obj(cls, *args, **kwargs)
elif type(obj, (types.FunctionType, types.LambdaType)):
return obj(cls, *args, **kwargs)
else:
return obj</font></pre></span><p style="text-indent:20px">Делать этого не стоит, хотя бы потому что так мы не сможем зарегистрировать экземпляр класса с перегруженным __call__ или функцию как синглетон для типа</p><ul><li>Теперь мы не может инстанцировать классы, которые прямо или опосредованно наследуют IOCInterface, без регистрации для них реализации. Т.е. Bee(1) вывалится с KeyError. В принципе это соответствует духу происходящего - мы запрещаем прямое инстанцирование классов. Разве что стоит перехватывать KeyError и генерировать понятное сообщение. Или изменить __new__ вот так</ul><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="b9d8ffa654a111e3b455bc7737dae05d" objtohide2="b9d9031654a111e3b455bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="b9d8ffa654a111e3b455bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__new__</span>(cls, <span style="color: #666666">*</span>args, <span style="color: #666666">**</span>kwargs):
<span style="color: #008000; font-weight: bold">return</span> ioc<span style="color: #666666">.</span>get(cls, <span style="color: #008000">super</span>(IOCInterface, cls)<span style="color: #666666">.</span>__new__)(cls, <span style="color: #666666">*</span>args, <span style="color: #666666">**</span>kwargs)
</pre></div></span><span style="line-height:100%;display:none" id="b9d9031654a111e3b455bc7737dae05d"><pre><font face="courier">def __new__(cls, *args, **kwargs):
return ioc.get(cls, super(IOCInterface, cls).__new__)(cls, *args, **kwargs)</font></pre></span><p style="text-indent:20px">Тогда по-умолчанию Bee(1) будет создавать экземпляр Bee</p><ul><li>В продолжение к предыдущему пункту - можно зарегистрировать фабричные функции и для IBee и для Bee;<li>У этого кода есть проблема с синглетонами. Если мы регистрируем для IBee синглетоном экземпляр IBee или любого производного от него класса, то при каждом вызове IBee() для этого класса будет заново вызываться конструктор. Нужно или отслеживать и игнорировать повторные вызовы конструктора, или сделать его пустым и вынести инициализацию в отдельный метод.</ul><p style="text-indent:20px"> Но в питоне есть еще один способ перехватить конструирование нового экземпляра. Рассмотрим запись b = Bee(1). Что тут написано? Вызывается объект/функция Bee с параметром 1. Компилятор питона не имеет никакой семантической информации о программе (в отличии от компилятора, например, С++) - он владеет только синтаксической информацией. Для него len(a) и IBee(a) это просто вызов объекта. Т.е. питон превратит IBee(a) в IBee.__class__.__call__(IBee, 1). IBee.__class__ - это метакласс IBee. Где же происходит вызов IBee.__new__ и IBee.__init__? В type.__call__. Вызов __new__, а затем конструктора - это то, что записано в стандартном метаклассе Т.е. все выглядит примерно так:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="b9d9689254a111e3b455bc7737dae05d" objtohide2="b9d96a9054a111e3b455bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="b9d9689254a111e3b455bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">type</span>:
<span style="color: #408080; font-style: italic"># ...</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__call__</span>(<span style="color: #008000">self</span>, <span style="color: #666666">*</span>args, <span style="color: #666666">**</span>kwargs):
obj <span style="color: #666666">=</span> Cls<span style="color: #666666">.</span>__new__(Cls, x, y)
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #008000">isinstance</span>(obj, Cls):
Cls<span style="color: #666666">.</span>__init__(obj, x, y)
<span style="color: #008000; font-weight: bold">return</span> obj
</pre></div></span><span style="line-height:100%;display:none" id="b9d96a9054a111e3b455bc7737dae05d"><pre><font face="courier">class type:
# ...
def __call__(self, *args, **kwargs):
obj = Cls.__new__(Cls, x, y)
if isinstance(obj, Cls):
Cls.__init__(obj, x, y)
return obj</font></pre></span><p style="text-indent:20px"> Соответственно написав свой метакласс и перегрузив __call__ мы избавимся от проблемы с синглетоном.</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="b9d9d99454a111e3b455bc7737dae05d" objtohide2="b9d9db5654a111e3b455bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="b9d9d99454a111e3b455bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">IOCMeta</span>(<span style="color: #008000">type</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__call__</span>(<span style="color: #008000">self</span>, <span style="color: #666666">*</span>args, <span style="color: #666666">**</span>kwargs):
<span style="color: #008000; font-weight: bold">return</span> ioc[<span style="color: #008000">self</span>](<span style="color: #008000">self</span>, <span style="color: #666666">*</span>args, <span style="color: #666666">**</span>kwargs)
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">IOCInterface2</span>(<span style="color: #008000">object</span>):
__metaclass__ <span style="color: #666666">=</span> IOCMeta
<span style="color: #AA22FF">@classmethod</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">register</span>(cls, impl):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">factory</span>(ccls, <span style="color: #666666">*</span>args, <span style="color: #666666">**</span>kwargs):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">type</span><span style="color: #666666">.</span>__call__(impl, <span style="color: #666666">*</span>args, <span style="color: #666666">**</span>kwargs)
cls<span style="color: #666666">.</span>register_factory(factory)
<span style="color: #AA22FF">@classmethod</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">register_instance</span>(cls, obj):
cls<span style="color: #666666">.</span>register_factory(<span style="color: #008000; font-weight: bold">lambda</span> <span style="color: #666666">*</span>args, <span style="color: #666666">**</span>kwargs: obj)
<span style="color: #AA22FF">@classmethod</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">register_factory</span>(cls, func):
ioc[cls] <span style="color: #666666">=</span> func
</pre></div></span><span style="line-height:100%;display:none" id="b9d9db5654a111e3b455bc7737dae05d"><pre><font face="courier">class IOCMeta(type):
def __call__(self, *args, **kwargs):
return ioc[self](self, *args, **kwargs)
class IOCInterface2(object):
__metaclass__ = IOCMeta
@classmethod
def register(cls, impl):
def factory(ccls, *args, **kwargs):
return type.__call__(impl, *args, **kwargs)
cls.register_factory(factory)
@classmethod
def register_instance(cls, obj):
cls.register_factory(lambda *args, **kwargs: obj)
@classmethod
def register_factory(cls, func):
ioc[cls] = func</font></pre></span><p style="text-indent:20px"> Класс IOCInterface2 лишен проблемы с повторным вызовом __init__ для синглетона. Посмотрим как все получается, заодно прорекламирую всем замечательную замену для assert, assertEquals и др. - <a href="http://www.kuwata-lab.com/oktest/oktest-py_users-guide.html">oktest</a>:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="b9da3c4054a111e3b455bc7737dae05d" objtohide2="b9da3e0c54a111e3b455bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="b9da3c4054a111e3b455bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">from</span> <span style="color: #0000FF; font-weight: bold">oktest</span> <span style="color: #008000; font-weight: bold">import</span> ok
<span style="color: #008000; font-weight: bold">from</span> <span style="color: #0000FF; font-weight: bold">python_ioc</span> <span style="color: #008000; font-weight: bold">import</span> IOCInterface2
<span style="color: #408080; font-style: italic"># ...</span>
IBee<span style="color: #666666">.</span>register(Bee)
ok(IBee(<span style="color: #666666">1</span>))<span style="color: #666666">.</span>is_a(Bee)
IBee<span style="color: #666666">.</span>register(Cee)
ok(IBee(<span style="color: #666666">1</span>))<span style="color: #666666">.</span>is_a(Cee)
IBee<span style="color: #666666">.</span>register_instance(<span style="color: #666666">1</span>)
ok(IBee(<span style="color: #666666">1</span>)) <span style="color: #666666">==</span> <span style="color: #666666">1</span>
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"All tests passed"</span>
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"'Bee.__init__ called' message should appears once and only once"</span>
IBee<span style="color: #666666">.</span>register(Bee)
IBee<span style="color: #666666">.</span>register_instance(IBee(<span style="color: #666666">1</span>))
IBee(<span style="color: #666666">1</span>)
IBee(<span style="color: #666666">1</span>)
</pre></div></span><span style="line-height:100%;display:none" id="b9da3e0c54a111e3b455bc7737dae05d"><pre><font face="courier">from oktest import ok
from python_ioc import IOCInterface2
# ...
IBee.register(Bee)
ok(IBee(1)).is_a(Bee)
IBee.register(Cee)
ok(IBee(1)).is_a(Cee)
IBee.register_instance(1)
ok(IBee(1)) == 1
print "All tests passed"
print "'Bee.__init__ called' message should appears once and only once"
IBee.register(Bee)
IBee.register_instance(IBee(1))
IBee(1)
IBee(1)</font></pre></span><p style="text-indent:20px">Наконец допишем поддержку контекста с помощью конструкции with (этот вариант поддерживает только регистрацию классов):</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="b9da7f3454a111e3b455bc7737dae05d" objtohide2="b9da80d854a111e3b455bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="b9da7f3454a111e3b455bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #AA22FF">@contextlib.contextmanager</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">ioc_set</span>(new_context):
<span style="color: #008000; font-weight: bold">global</span> ioc
old_ioc <span style="color: #666666">=</span> ioc<span style="color: #666666">.</span>copy()
<span style="color: #008000; font-weight: bold">for</span> k, v <span style="color: #AA22FF; font-weight: bold">in</span> new_context<span style="color: #666666">.</span>items():
k<span style="color: #666666">.</span>register(v)
<span style="color: #008000; font-weight: bold">try</span>:
<span style="color: #008000; font-weight: bold">yield</span>
<span style="color: #008000; font-weight: bold">finally</span>:
ioc <span style="color: #666666">=</span> old_ioc
</pre></div></span><span style="line-height:100%;display:none" id="b9da80d854a111e3b455bc7737dae05d"><pre><font face="courier">@contextlib.contextmanager
def ioc_set(new_context):
global ioc
old_ioc = ioc.copy()
for k, v in new_context.items():
k.register(v)
try:
yield
finally:
ioc = old_ioc</font></pre></span><p style="text-indent:20px">Использование</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="b9dabaf854a111e3b455bc7737dae05d" objtohide2="b9dabc9c54a111e3b455bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="b9dabaf854a111e3b455bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">IBee<span style="color: #666666">.</span>register(Bee)
ok(IBee(<span style="color: #666666">1</span>))<span style="color: #666666">.</span>is_a(Bee)
<span style="color: #008000; font-weight: bold">with</span> ioc_set({IBee: Cee}):
<span style="color: #408080; font-style: italic"># until we exits with block IBee(xx) will gives an instance of Cee</span>
ok(IBee(<span style="color: #666666">1</span>))<span style="color: #666666">.</span>is_a(Cee)
ok(IBee(<span style="color: #666666">1</span>))<span style="color: #666666">.</span>is_a(Bee)
</pre></div></span><span style="line-height:100%;display:none" id="b9dabc9c54a111e3b455bc7737dae05d"><pre><font face="courier">IBee.register(Bee)
ok(IBee(1)).is_a(Bee)
with ioc_set({IBee: Cee}):
# until we exits with block IBee(xx) will gives an instance of Cee
ok(IBee(1)).is_a(Cee)
ok(IBee(1)).is_a(Bee)</font></pre></span><p style="text-indent:20px"> Использование контекста можно немного упростить, избежав {...} и вместо этого записав подменяемые классы в виде ioc_set(IBee=Cee, ....). Cделать так не совсем просто, поскольку питон передает IBee как строку "IBee", а не как объект IBee.Но можно подняться из ioc_set на один кадр стека вверх и посмотреть, что там лежит по имени "IBee":</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="b9db1b9c54a111e3b455bc7737dae05d" objtohide2="b9db1d6854a111e3b455bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="b9db1b9c54a111e3b455bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">ioc_set_hack</span>(<span style="color: #666666">**</span>kwargs):
fr <span style="color: #666666">=</span> sys<span style="color: #666666">.</span>_getframe(<span style="color: #666666">1</span>)
result_map <span style="color: #666666">=</span> {}
<span style="color: #008000; font-weight: bold">for</span> name, obj <span style="color: #AA22FF; font-weight: bold">in</span> kwargs<span style="color: #666666">.</span>items():
<span style="color: #008000; font-weight: bold">if</span> name <span style="color: #AA22FF; font-weight: bold">in</span> fr<span style="color: #666666">.</span>f_locals:
interface <span style="color: #666666">=</span> fr<span style="color: #666666">.</span>f_locals[name]
<span style="color: #008000; font-weight: bold">else</span>:
interface <span style="color: #666666">=</span> fr<span style="color: #666666">.</span>f_globals[name]
result_map[interface] <span style="color: #666666">=</span> obj
<span style="color: #008000; font-weight: bold">return</span> ioc_context(result_map)
<span style="color: #008000; font-weight: bold">with</span> ioc_set_hack(IBee<span style="color: #666666">=</span>Cee):
ok(IBee(<span style="color: #666666">1</span>))<span style="color: #666666">.</span>is_a(Cee)
</pre></div></span><span style="line-height:100%;display:none" id="b9db1d6854a111e3b455bc7737dae05d"><pre><font face="courier">def ioc_set_hack(**kwargs):
fr = sys._getframe(1)
result_map = {}
for name, obj in kwargs.items():
if name in fr.f_locals:
interface = fr.f_locals[name]
else:
interface = fr.f_globals[name]
result_map[interface] = obj
return ioc_context(result_map)
with ioc_set_hack(IBee=Cee):
ok(IBee(1)).is_a(Cee)</font></pre></span><p style="text-indent:20px"> Я бы не советовал использовать эту функцию, поскольку ее поведение совсем не очевидно. Вообще я против превращения питона в DSL, а то есть шанс получить на выходе читаемость, как местами у scala.</p><p style="text-indent:20px"> К полученным классам довольно легко прикрутить все возможности "серьезных" ICC: загрузку конфигураций из внешних файлов, поддержку многопоточности и прочее.</p><p style="text-indent:20px"> Итак ICC пишется в питоне очень просто - <a href="http://springpython.webfactional.com/">1</a>, <a href="https://code.google.com/p/pinsor/">2</a>, <a href="https://github.com/rande/python-simple-ioc">3</a> и используется даже проще, чем в Java/C#, так почему же их нигде нет? Почему авторы этих фреймворков <a href="http://lostechies.com/ryansvihla/2009/11/16/i-recant-my-ioc-ioc-containers-in-dynamic-languages-are-silly/">пишут</a>, что они от них отрекаются?</p><p style="text-indent:20px"> А дело в том, что ICC, как шаблон проектирования, незаметно встроен в питон повсюду и встроенные возможности покрывают бОльшую часть случаев его применения. Ради оставшихся единичных примеров не стоит городить весь огород. <a href="http://ru.wikipedia.org/wiki/YAGNI">YAGNI</a> и <a href="http://ru.wikipedia.org/wiki/KISS_%28%D0%BF%D1%80%D0%B8%D0%BD%D1%86%D0%B8%D0%BF%29">KISS</a> в чистом виде.</p><p style="text-indent:20px"> Принципиальная разница между Java и python состоит в том когда в конструкции инстанцирования происходит связывание объекта с именем. В Java - процессе компиляции. Если вы пишете obj = new SomeClass(), то компилятор совершенно точно знает, что это инстанцирование объекта и совершенно точно знает - какого. Без перекомпиляции эта строка никогда не будет значить ничего другого. В python, как я уже писал выше, запись obj = SomeClass() означает следующее - найди в пространстве имен ХХХ (globals() или locals()) объект по имени 'SomeClass' и вызови его. globals() выступает ICC, который мы конфигурируем во время исполнения программы:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="b9db6b6054a111e3b455bc7737dae05d" objtohide2="b9db6d5e54a111e3b455bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="b9db6b6054a111e3b455bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #408080; font-style: italic"># same as SomeClass = type('SomeClass', (object,), {})</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">SomeClass</span>(<span style="color: #008000">object</span>):<span style="color: #008000; font-weight: bold">pass</span>
SomeOtherClass <span style="color: #666666">=</span> SomeClass
</pre></div></span><span style="line-height:100%;display:none" id="b9db6d5e54a111e3b455bc7737dae05d"><pre><font face="courier"># same as SomeClass = type('SomeClass', (object,), {})
class SomeClass(object):pass
SomeOtherClass = SomeClass</font></pre></span><p style="text-indent:20px"> Конструкция class в питоне всего лишь синтаксический сахар для объявления переменной, хранящей объект типа. Разница с приведенным выше ICC состоит в том, что в данном случае мы запрашиваем класс по имени внутри контейнера, а не по интерфейсу.</p><p style="text-indent:20px"> Точно так же каждый модуль в питоне является ICC контейнером, обращение к которому происходит взятием атрибута. mod.name это и есть разрешение реализации, привязанной к имени.</p><p style="text-indent:20px"> Вместо ручного присваивания можно использовать подменяющие функции, например <a href="http://www.voidspace.org.uk/python/mock/patch.html">patch</a>. Которая работает подобно ioc_set (но умеет гораздо больше):</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="b9dba8d254a111e3b455bc7737dae05d" objtohide2="b9dbaa6c54a111e3b455bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="b9dba8d254a111e3b455bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Class</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #008000; font-weight: bold">with</span> patch(<span style="color: #BA2121">'__main__.Class'</span>, <span style="color: #008000">list</span>):
ok(Class())<span style="color: #666666">.</span>is_a(<span style="color: #008000">list</span>)
</pre></div></span><span style="line-height:100%;display:none" id="b9dbaa6c54a111e3b455bc7737dae05d"><pre><font face="courier">class Class(object):
pass
with patch('__main__.Class', list):
ok(Class()).is_a(list)</font></pre></span><p style="text-indent:20px">Однако модель подмены по имени имеет одну слабость:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="b9dbf26054a111e3b455bc7737dae05d" objtohide2="b9dbf3fa54a111e3b455bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="b9dbf26054a111e3b455bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #408080; font-style: italic"># some_module.py</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Class</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #408080; font-style: italic"># main.py</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">some_module</span>
<span style="color: #008000; font-weight: bold">from</span> <span style="color: #0000FF; font-weight: bold">some_module</span> <span style="color: #008000; font-weight: bold">import</span> Class
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">print_tp</span>():
<span style="color: #008000; font-weight: bold">print</span> some_module<span style="color: #666666">.</span>Class()
<span style="color: #008000; font-weight: bold">print</span> Class()
<span style="color: #408080; font-style: italic"># unit_test.py</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">main</span>
<span style="color: #008000; font-weight: bold">with</span> patch(<span style="color: #BA2121">'some_module.Class'</span>, <span style="color: #008000">list</span>):
print_tp() <span style="color: #408080; font-style: italic"># only some_module.Class() patched</span>
<span style="color: #008000; font-weight: bold">with</span> patch(<span style="color: #BA2121">'some_module.Class'</span>, <span style="color: #008000">list</span>):
<span style="color: #008000; font-weight: bold">with</span> patch(<span style="color: #BA2121">'main.Class'</span>, <span style="color: #008000">list</span>):
print_tp() <span style="color: #408080; font-style: italic"># both some_module.Class() and Class() patched</span>
</pre></div></span><span style="line-height:100%;display:none" id="b9dbf3fa54a111e3b455bc7737dae05d"><pre><font face="courier"># some_module.py
class Class(object):
pass
# main.py
import some_module
from some_module import Class
def print_tp():
print some_module.Class()
print Class()
# unit_test.py
import main
with patch('some_module.Class', list):
print_tp() # only some_module.Class() patched
with patch('some_module.Class', list):
with patch('main.Class', list):
print_tp() # both some_module.Class() and Class() patched</font></pre></span><p style="text-indent:20px"> Проблема в том, что один объект может иметь множество имен и для корректной работы нужно или сделать подмену раньше, чем другие модули создадут новый имена или заменить их все. Впрочем это не большая расплата за возможность оставить код чистым от ICC и за возможность работать с классами и модулями, которые про ICC не знают. Так же проблема смягчается тем, что в основном использование ICC - это юниттесты и в них простительно некоторое количество таких ручных хаков.</p><p style="text-indent:20px">Проблема с инстанцированием корректного класса решается совсем просто:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="b9dc3f9a54a111e3b455bc7737dae05d" objtohide2="b9dc413e54a111e3b455bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="b9dc3f9a54a111e3b455bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">A</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>, val):
<span style="color: #008000">self</span><span style="color: #666666">.</span>val <span style="color: #666666">=</span> val
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__add__</span>(<span style="color: #008000">self</span>, val):
<span style="color: #408080; font-style: italic">#return A(self.val + val)</span>
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>__class__(<span style="color: #008000">self</span><span style="color: #666666">.</span>val <span style="color: #666666">+</span> val)
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">B</span>(A):
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #008000; font-weight: bold">print</span> B(<span style="color: #666666">1</span>) <span style="color: #666666">+</span> <span style="color: #666666">1</span> <span style="color: #408080; font-style: italic"># <__main__.В object at 0x18877d0></span>
</pre></div></span><span style="line-height:100%;display:none" id="b9dc413e54a111e3b455bc7737dae05d"><pre><font face="courier">class A(object):
def __init__(self, val):
self.val = val
def __add__(self, val):
#return A(self.val + val)
return self.__class__(self.val + val)
class B(A):
pass
print B(1) + 1 # <__main__.В object at 0x18877d0></font></pre></span><p style="text-indent:20px"> Относительно других причин использовать ICC: В питоне практически никогда не нужно создавать сложные иерархии объектов. Такое API не питонистично и врядли будет пользоваться популярностью. Три модуля, которые приходят на ум - urllib2, logging и unittest. Два последние "слизаны" с явовских модулей - <a href="http://logging.apache.org/log4j/1.2/manual.html">log4j</a> и <a href="http://ru.wikipedia.org/wiki/JUnit">junit</a> соответственно. Для logging фактически есть встроенный ICC - построение дерева логеров по конфигурационному файлу, вместо unittest все использовали nosetests (в 2.7 его починили и теперь для юниттеста не нужно инстанцировать половину вселенной). А с urllib2 почти все пересели на <a href="http://www.python-requests.org/en/latest/">requests</a>.</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="b9dc877a54a111e3b455bc7737dae05d" objtohide2="b9dc892854a111e3b455bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="b9dc877a54a111e3b455bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">A</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>, val):
<span style="color: #008000">self</span><span style="color: #666666">.</span>val <span style="color: #666666">=</span> val
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__add__</span>(<span style="color: #008000">self</span>, val):
<span style="color: #008000; font-weight: bold">return</span> A(<span style="color: #008000">self</span><span style="color: #666666">.</span>val <span style="color: #666666">+</span> val)
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">B</span>(A):
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #008000; font-weight: bold">print</span> B(<span style="color: #666666">1</span>) <span style="color: #666666">+</span> <span style="color: #666666">1</span> <span style="color: #408080; font-style: italic"># <__main__.A object at 0x18877d0></span>
</pre></div></span><span style="line-height:100%;display:none" id="b9dc892854a111e3b455bc7737dae05d"><pre><font face="courier">class A(object):
def __init__(self, val):
self.val = val
def __add__(self, val):
return A(self.val + val)
class B(A):
pass
print B(1) + 1 # <__main__.A object at 0x18877d0></font></pre></span><p style="text-indent:20px"> Синглетон почти не нужен, поскольку нет проблем с порядком инициализации глобальных переменных, даже в многопсточной программе.</p><p style="text-indent:20px"> Наконец в питоне вызов функции не отличим от инстанцирования класса. Что-бы получить эту возможность в скале даже нагородили уродливый (и нарушающий <a href="http://en.wikipedia.org/wiki/Don%27t_repeat_yourself">DRY</a>) огород из <a href="http://daily-scala.blogspot.com/2009/09/companion-object.html">companion object</a>. Так что если что-то у вас сегодня - класс, а завтра - фабричная функция, это нормально. Почти никто не заметит.</p>Ссылки:<br> <a name="DRY"><a href="http://en.wikipedia.org/wiki/Don%27t_repeat_yourself">en.wikipedia.org/wiki/Don%27t_repeat_yourself</a></a><br> <a name="плохо_совместимо"><a href="http://gbracha.blogspot.com/2007/06/constructors-considered-harmful.html">gbracha.blogspot.com/2007/06/constructors-considered-harmful.html</a></a><br> <a name="companion_object"><a href="http://daily-scala.blogspot.com/2009/09/companion-object.html">daily-scala.blogspot.com/2009/09/companion-object.html</a></a><br> <a name="log4j"><a href="http://logging.apache.org/log4j/1.2/manual.html">logging.apache.org/log4j/1.2/manual.html</a></a><br> <a name="junit"><a href="http://ru.wikipedia.org/wiki/JUnit">ru.wikipedia.org/wiki/JUnit</a></a><br> <a name="requests"><a href="http://www.python-requests.org/en/latest/">www.python-requests.org/en/latest</a></a><br> <a name="patch"><a href="http://www.voidspace.org.uk/python/mock/patch.html">www.voidspace.org.uk/python/mock/patch.html</a></a><br> <a name="пишут"><a href="http://lostechies.com/ryansvihla/2009/11/16/i-recant-my-ioc-ioc-containers-in-dynamic-languages-are-silly/">lostechies.com/ryansvihla/2009/11/16/i-recant-my-ioc-ioc-containers-in-dynamic-languages-are-silly</a></a><br> <a name="YAGNI"><a href="http://ru.wikipedia.org/wiki/YAGNI">ru.wikipedia.org/wiki/YAGNI</a></a><br> <a name="KISS"><a href="http://ru.wikipedia.org/wiki/KISS_%28%D0%BF%D1%80%D0%B8%D0%BD%D1%86%D0%B8%D0%BF%29">ru.wikipedia.org/wiki/KISS_%28%D0%BF%D1%80%D0%B8%D0%BD%D1%86%D0%B8%D0%BF%29</a></a><br> <a name="1"><a href="http://springpython.webfactional.com/">springpython.webfactional.com</a></a><br> <a name="2"><a href="https://code.google.com/p/pinsor/">code.google.com/p/pinsor</a></a><br> <a name="3"><a href="https://github.com/rande/python-simple-ioc">github.com/rande/python-simple-ioc</a></a><br> <a name="oktest"><a href="http://www.kuwata-lab.com/oktest/oktest-py_users-guide.html">www.kuwata-lab.com/oktest/oktest-py_users-guide.html</a></a><br> <a name="фабричной_функцией(ФФ)"><a href="http://en.wikipedia.org/wiki/Factory_method_pattern">en.wikipedia.org/wiki/Factory_method_pattern</a></a><br> <a name="создание"><a href="http://koder-ua.blogspot.com/2011/12/libvirt-co-1.html">koder-ua.blogspot.com/2011/12/libvirt-co-1.html</a></a><br> <a name="java_spring"><a href="http://ru.wikipedia.org/wiki/Spring_Framework#Inversion_of_Control">ru.wikipedia.org/wiki/Spring_Framework#Inversion_of_Control</a></a><br> <a name="dagger"><a href="https://github.com/square/dagger">github.com/square/dagger</a></a><br><p style="text-indent:20px">Исходники этого и других постов со скриптами лежат тут - <a href="https://github.com/koder-ua/python-lectures">github.com/koder-ua</a>. При использовании их, пожалуйста, ссылайтесь на <a href="http://koder-ua.blogspot.com/">koder-ua.blogspot.com</a>.</p>
<script type="text/javascript">
function on_hidabble_click()
{
var me = $(this);
var hide_id = me.attr("objtohide");
var controlled_object = $('#' + hide_id);
controlled_object.toggle();
if ( controlled_object.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".hidder").click(on_hidabble_click);
function on_double_hidabble_click()
{
var me = $(this);
var hide_id1 = me.attr("objtohide1");
var hide_id2 = me.attr("objtohide2");
var controlled_object1 = $('#' + hide_id1);
var controlled_object2 = $('#' + hide_id2);
controlled_object1.toggle();
controlled_object2.toggle();
if ( controlled_object1.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".dhidder").click(on_double_hidabble_click);
</script>
Anonymoushttp://www.blogger.com/profile/06645078574760734586noreply@blogger.com0tag:blogger.com,1999:blog-1174489715777430743.post-74252985957279502222013-11-13T21:55:00.000+02:002013-11-14T15:58:02.487+02:00Python, processor affinity или как существенно ускорить некоторые программы, ничего не делая<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<p style="text-indent:20px"><i>Всем, кто увидел первую версию поста - цифры были кривые из-за turbo boost</i></p><p style="text-indent:20px"> Возьмем простой пример tcp клиента и сервера на python. Сервер создает пул из N потоков и ждет соединений с клиентом. Получив соединение передает его на обработку в пул. На каждом соединении сервер ждет от клиента строку данных и имитирует некую обработку. При получении 'bye\n' сервер завершает обработку клиента.</p><p style="text-indent:20px"> Клиент открывает N соединений с сервером и генерирует на них нагрузку. Общий объем нагрузки за один запуск клиента фиксирован.</p><a name='more'></a><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="543b67504cbe11e3ae68bc7737dae05d" objtohide2="543be5044cbe11e3ae68bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="543b67504cbe11e3ae68bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">data <span style="color: #666666">=</span> <span style="color: #BA2121">' '</span> <span style="color: #666666">*</span> <span style="color: #666666">100</span> <span style="color: #666666">+</span> <span style="color: #BA2121">'</span><span style="color: #BB6622; font-weight: bold">\x0A</span><span style="color: #BA2121">'</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">client</span>(th_count):
sockets <span style="color: #666666">=</span> []
<span style="color: #008000; font-weight: bold">for</span> i <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">range</span>(th_count):
sock <span style="color: #666666">=</span> socket<span style="color: #666666">.</span>socket()
<span style="color: #008000; font-weight: bold">for</span> cnt <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">range</span>(<span style="color: #666666">3</span>):
<span style="color: #008000; font-weight: bold">try</span>:
sock<span style="color: #666666">.</span>connect(host_port)
<span style="color: #008000; font-weight: bold">break</span>
<span style="color: #008000; font-weight: bold">except</span> socket<span style="color: #666666">.</span>error:
<span style="color: #008000; font-weight: bold">if</span> cnt <span style="color: #666666">==</span> <span style="color: #666666">2</span>:
<span style="color: #008000; font-weight: bold">raise</span>
time<span style="color: #666666">.</span>sleep(<span style="color: #666666">0.1</span>)
sockets<span style="color: #666666">.</span>append(sock)
<span style="color: #008000; font-weight: bold">for</span> i <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">range</span>(NUM_PACKETS):
sock <span style="color: #666666">=</span> random<span style="color: #666666">.</span>choice(sockets)
sock<span style="color: #666666">.</span>send(data)
<span style="color: #008000; font-weight: bold">for</span> sock <span style="color: #AA22FF; font-weight: bold">in</span> sockets:
sock<span style="color: #666666">.</span>send(<span style="color: #BA2121">'bye</span><span style="color: #BB6622; font-weight: bold">\x0A</span><span style="color: #BA2121">'</span>)
</pre></div></span><span style="line-height:100%;display:none" id="543be5044cbe11e3ae68bc7737dae05d"><pre><font face="courier">data = ' ' * 100 + '\x0A'
def client(th_count):
sockets = []
for i in range(th_count):
sock = socket.socket()
for cnt in range(3):
try:
sock.connect(host_port)
break
except socket.error:
if cnt == 2:
raise
time.sleep(0.1)
sockets.append(sock)
for i in range(NUM_PACKETS):
sock = random.choice(sockets)
sock.send(data)
for sock in sockets:
sock.send('bye\x0A')</font></pre></span><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="543d015a4cbe11e3ae68bc7737dae05d" objtohide2="543d81704cbe11e3ae68bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="543d015a4cbe11e3ae68bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">server</span>(th_count):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">process_client</span>(sock):
num <span style="color: #666666">=</span> <span style="color: #666666">0</span>
<span style="color: #008000; font-weight: bold">while</span> <span style="color: #008000">True</span>:
msg <span style="color: #666666">=</span> <span style="color: #BA2121">""</span>
<span style="color: #008000; font-weight: bold">while</span> <span style="color: #AA22FF; font-weight: bold">not</span> msg<span style="color: #666666">.</span>endswith(<span style="color: #BA2121">'</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">'</span>):
msg <span style="color: #666666">+=</span> sock<span style="color: #666666">.</span>recv(<span style="color: #666666">1</span>)
<span style="color: #008000; font-weight: bold">if</span> msg <span style="color: #666666">==</span> <span style="color: #BA2121">'bye</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">'</span>:
<span style="color: #008000; font-weight: bold">break</span>
<span style="color: #008000; font-weight: bold">for</span> i <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">range</span>(serv_tout):
<span style="color: #008000; font-weight: bold">pass</span>
num <span style="color: #666666">+=</span> <span style="color: #666666">1</span>
s <span style="color: #666666">=</span> socket<span style="color: #666666">.</span>socket()
s<span style="color: #666666">.</span>bind(host_port)
s<span style="color: #666666">.</span>listen(<span style="color: #666666">5</span>)
<span style="color: #008000; font-weight: bold">with</span> ThreadPoolExecutor(max_workers<span style="color: #666666">=</span>th_count) <span style="color: #008000; font-weight: bold">as</span> pool:
fts <span style="color: #666666">=</span> []
<span style="color: #008000; font-weight: bold">for</span> i <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">range</span>(th_count):
sock,_ <span style="color: #666666">=</span> s<span style="color: #666666">.</span>accept()
fts<span style="color: #666666">.</span>append(pool<span style="color: #666666">.</span>submit(process_client, sock))
<span style="color: #008000; font-weight: bold">for</span> ft <span style="color: #AA22FF; font-weight: bold">in</span> fts:
ft<span style="color: #666666">.</span>result()
</pre></div></span><span style="line-height:100%;display:none" id="543d81704cbe11e3ae68bc7737dae05d"><pre><font face="courier">def server(th_count):
def process_client(sock):
num = 0
while True:
msg = ""
while not msg.endswith('\n'):
msg += sock.recv(1)
if msg == 'bye\n':
break
for i in range(serv_tout):
pass
num += 1
s = socket.socket()
s.bind(host_port)
s.listen(5)
with ThreadPoolExecutor(max_workers=th_count) as pool:
fts = []
for i in range(th_count):
sock,_ = s.accept()
fts.append(pool.submit(process_client, sock))
for ft in fts:
ft.result()</font></pre></span><p style="text-indent:20px">Замеряем сколько времени нужно для одного прогона этой системы с N=4</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="543fa07c4cbe11e3ae68bc7737dae05d" objtohide2="543fa9f04cbe11e3ae68bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="543fa07c4cbe11e3ae68bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">$</span> python mt_test.py client 4 & <span style="color: #008000">time </span>python mt_test.py server 4
<span style="color: #888888">real 0m8.342s</span>
<span style="color: #888888">user 0m7.789s</span>
<span style="color: #888888">sys 0m6.587s</span>
</pre></div></span><span style="line-height:100%;display:none" id="543fa9f04cbe11e3ae68bc7737dae05d"><pre><font face="courier">$ python mt_test.py client 4 & time python mt_test.py server 4
real 0m8.342s
user 0m7.789s
sys 0m6.587s</font></pre></span><p style="text-indent:20px"> А теперь почти то же самое, но разрешим операционной системе исполнять все потоки сервера только на одном ядре из 8ми доступных</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="544016564cbe11e3ae68bc7737dae05d" objtohide2="54401a164cbe11e3ae68bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="544016564cbe11e3ae68bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">$</span> python mt_test.py client 4 & <span style="color: #008000">time </span>taskset 0x00000001 python mt_test.py server 4
<span style="color: #888888">real 0m4.663s</span>
<span style="color: #888888">user 0m3.186s</span>
<span style="color: #888888">sys 0m0.762s</span>
</pre></div></span><span style="line-height:100%;display:none" id="54401a164cbe11e3ae68bc7737dae05d"><pre><font face="courier">$ python mt_test.py client 4 & time taskset 0x00000001 python mt_test.py server 4
real 0m4.663s
user 0m3.186s
sys 0m0.762s</font></pre></span><p style="text-indent:20px"> Уличная магия в действии - многопоточная программа исполнилась в 2 раза быстрее, когда мы разрешили использовать только одно ядро процессора.</p><p style="text-indent:20px"> Почему такое получилось? Во-первых <a href="http://habrahabr.ru/post/84629">GIL</a> - сколько бы потоков в питоне мы не создали, они всегда будут исполняться питоновский код только по очереди. Питон не позволяет двум потокам одного процесса одновременно исполнять свой байтокод.</p><p style="text-indent:20px"> Таким образом для этой программы(как и для 99% программ на питоне) никакого заметного ускорения от использования более одного ядра ожидать и не приходится. Все чисто питоновские программы <a href="http://vimeo.com/49718712">конкурентны, но не параллельны</a>. А <strike>конкурентной</strike> такой системе от изменения количества ядер в процессоре не холодно и не жарко (почти).</p><p style="text-indent:20px"> Почему же скорость исполнения падает, если использовать более одного ядра? Причин две:</p><ul><li>Излишние переключения между потоками<li>Постоянная война за кеш с другими потоками в системе и друг с другом</ul><p style="text-indent:20px"> Итак что происходит: пусть у нас есть два потока, один из которых(первый) сейчас обрабатывает принятые данные, а второй ожидает данных от сокета. Наконец второй поток получает данные и ОС готова продолжить его исполнение. Она смотрит на доступные ядра, видит что первое ядро занято первым потоком и запускает второй поток на исполнение на втором ядре. Второй поток запускается и первым делом пытается захватить GIL. Неудача - GIL захвачен первым потоком. Второй поток снова засыпает, ожидая освобождения GIL.</p><p style="text-indent:20px"> В итоге операционная система, которая понятия не имеет ни о каких GIL, сделала кучу лишней работы (переключение контекста достаточно дорогая операция). Правда заметная часть этой работы делалась вторым ядром, так что происходила параллельно и почти не мешала исполняться первому потоку. Почти - потому что второе ядро все равно занимало шину памяти. Ситуация становится хуже, если в системе есть <a href="http://ru.wikipedia.org/wiki/Hyper-threading">HT</a> - в этом случае второе ядро может делить с первым исполняемые блоки процессора и все эти лишние переключения будут серьезно замедлять исполнение первого потока.</p><p style="text-indent:20px"> Вторая проблема состоит в том, что второй поток переброшен на исполнение на второе ядро процессора. Когда первый поток освободит GIL, то второй поток продолжит исполнение на втором ядре, потому что ОС знает, что кеши первого и второго уровня у каждого ядра свои и старается без причин не гонять потоки между ядрами. В итоге все имеющиеся потоки "размазываются" по доступным ядрам. Съедая в сумме 100% одного ядра, они превращают это в 12.5% на каждом из 8ми ядер. При этом в промежутках пока питоновские потоки ждут GIL на эти ядра вклиниваются другие потоки из системы, постоянно вытесняя наши данные из кеша.</p><p style="text-indent:20px"> В итоге питоновские потоки постоянно "бегают" по ядрам. Данные копируются в кеш и из кеша, а каждый кеш-промах стоит до тысяч тактов на обращение к RAM. Даже по меркам питона - серьезные нагрузки.</p><p style="text-indent:20px"> Выставив привязку к одному ядру мы убиваем сразу двух зайцев. Во-первых сокращаем количество переключений контекста, поскольку ОС будет заметно реже запустить на исполнение второй поток, если единственное доступное ему ядро сейчас занято. Во-вторых другие потоки не будут вклиниваться на это ядро, тем самым мы уменьшим интенсивность обмена данными между кешем и ОЗУ (питоновские потоки в одном процессе используют заметную часть данных совместно).</p><p style="text-indent:20px">Итоги тестирования.</p><ul><li>SUM - общее затраченное время<li>SYS - время, затраченное операционной системой<li>USR - время, затраченное в пользовательском режиме<li>XXX_AF - XXX в случае, если выставлена привязка к одному ядру<li>DIFF - отличие в процентах между XXX и XXX_AF</ul><p style="text-indent:20px">Все измерения сделаны на Core i7-2630QM@800MHz, python 2.7.5, x64, ubuntu 13.10 с усреднением по 7ми выборкам. Долгая война с turbo boost окончилась принудительным переводом процессора в режим минимальных частот.</p><pre><font face="courier"> -------------------------------------------------------------------------
| Потоки | SUM SUM_AF %DIFF | SYS SYS_AF %DIFF | USR USR_AF %DIFF |
-------------------------------------------------------------------------
| 1 | 3.35 3.55 -5 | 0.54 0.52 4 | 2.78 3.03 -8 |
| 2 | 7.26 4.63 36 | 4.91 0.67 86 | 5.10 2.95 42 |
-------------------------------------------------------------------------
| 4 | 8.28 4.90 41 | 6.58 0.76 88 | 7.37 3.14 57 |
| 8 | 7.96 5.00 37 | 6.49 0.84 87 | 7.32 3.15 57 |
-------------------------------------------------------------------------
| 16 | 9.77 5.88 40 | 6.53 0.73 89 | 7.01 3.15 55 |
| 32 | 9.73 6.84 30 | 6.54 0.81 88 | 7.06 3.04 57 |
-------------------------------------------------------------------------</font></pre><p style="text-indent:20px"> Прогон теста по VTune показывает, что после выставления привязки количество кеш промахов уменьшается примерно в 5 раз, а количество переключений контекста - в 40. В ходе экспериментов обнаружилась еще одна интересная вещь - при выставлении привязки к одному ядру более эффективно используется turbo boost, что тоже ускорит вашу программу, если больше никто не грузит систему. Для этого теста turbo boost был заблокирован.</p><p style="text-indent:20px"> Будет ли что-то подобное в других случаях? Хотя данная программа и обрабатывает данные, приходящие из сокета, но данные приходят быстрее, чем она может их обработать. Таким образом она является <a href="http://stackoverflow.com/questions/868568/cpu-bound-and-i-o-bound">CPU bounded</a>. Если программа будет в основном занята ожиданием данных, то выставления привязки к ядру даст меньше ускорения - ОС будет меньше перебрасывать потоки между ядрами. Чем выше нагрузка на процессор, тем больше будет выигрыш.</p><p style="text-indent:20px">Когда мы можем получить замедление:</p><ul><li>если в программе есть места, которые действительно параллельны, например часть работы делается С/С++ библиотекой, которая отпускает GIL<li>Или вы используете jython или ironpython<li>Если вы используете multiprocessing/ProcessPoolExecutor, которые запускают отдельные процессы и не имеют проблем с GIL. Привязка в линуксе наследуется потоками/процессами. Так что для дочерних процессов ее нужно или отменить, или выделить по своему ядру на каждый процесс.<li>В некоторых однопоточных системах, например при использовании gevent</ul><p style="text-indent:20px">P.S. В 3.3 поведение все то-же.</p><p style="text-indent:20px">P.S.S. Нашел уже готовую <a href="http://habrahabr.ru/post/141181">статью</a> про тоже самое.</p>Ссылки:<br> <a name="GIL"><a href="http://habrahabr.ru/post/84629">habrahabr.ru/post/84629</a></a><br> <a name="статью"><a href="http://habrahabr.ru/post/141181">habrahabr.ru/post/141181</a></a><br> <a name="конкурентны,_но_не_параллельны"><a href="http://vimeo.com/49718712">vimeo.com/49718712</a></a><br> <a name="CPU_bounded"><a href="http://stackoverflow.com/questions/868568/cpu-bound-and-i-o-bound">stackoverflow.com/questions/868568/cpu-bound-and-i-o-bound</a></a><br> <a name="HT"><a href="http://ru.wikipedia.org/wiki/Hyper-threading">ru.wikipedia.org/wiki/Hyper-threading</a></a><br><p style="text-indent:20px">Исходники этого и других постов со скриптами лежат тут - <a href="https://github.com/koder-ua/python-lectures">github.com/koder-ua</a>. При использовании их, пожалуйста, ссылайтесь на <a href="http://koder-ua.blogspot.com/">koder-ua.blogspot.com</a>.</p>
<script type="text/javascript">
function on_hidabble_click()
{
var me = $(this);
var hide_id = me.attr("objtohide");
var controlled_object = $('#' + hide_id);
controlled_object.toggle();
if ( controlled_object.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".hidder").click(on_hidabble_click);
function on_double_hidabble_click()
{
var me = $(this);
var hide_id1 = me.attr("objtohide1");
var hide_id2 = me.attr("objtohide2");
var controlled_object1 = $('#' + hide_id1);
var controlled_object2 = $('#' + hide_id2);
controlled_object1.toggle();
controlled_object2.toggle();
if ( controlled_object1.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".dhidder").click(on_double_hidabble_click);
</script>
Anonymoushttp://www.blogger.com/profile/06645078574760734586noreply@blogger.com2tag:blogger.com,1999:blog-1174489715777430743.post-53334275117571703372012-10-30T03:44:00.001+02:002012-10-31T21:42:17.983+02:00О сборке мусора, деструкторах и разных питонах<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<br><p style="text-indent:20px"> В <a href="http://koder-ua.blogspot.com/2012/10/python-with.html">этом</a> посте я писал почему работа с файлами и другими объектами,
требующими гарантированного закрытия должна должна производиться через with.
Однако кроме минуса в виде добавления в код лишнего
уровеня вложенности with еще и решает только часть проблемы -
если код обработки файла не локален (нужно возвращать дескриптор
в вызывающий код или хранить неопределенное время) with не может помочь.
И собственно никто вообще не может помочь - суровая реальность состоит в том, что
python не гарантирует вызов деструктора объекта. Т.е. если вы работаете на CPython,
и не создаете циклических ссылок, то за крайне редкими исключениями деструктор
будет вызываться вовремя. Но если вы используете ironpython/jython/pypy
то ситуация становится совсем печальна.</p><a name='more'></a><p style="text-indent:20px"> Для тех кто, вдруг, не в курсе - немного про С++, как пример
удобной для программиста реализации деструкторов. С++ гарантирует
вызов деструктора у объекта по его уничтожению чтобы не произошло
(за исключением полного краха программы/пропадания питания/конца света/etc).
Уничтожение наступает либо по выходу из блока в котором определена
переменная, либо по удалению объекта с помощью оператора
delete при выделении объекта в куче.</p><pre><font face="courier"> // C++
{
// open file
std::fstream fs(fname, std::ios_base::out);
process_file(fs);
} // file is closed before we get beyong this line, no matter what happened</font></pre><p style="text-indent:20px"> Гарантированный вызов деструктора - великое программистское благо, позволяющее
не заботится о освобождении некоторых ресурсов, не загромождать код всякими
with/using/try-finally & Co, и даже для объектов в куче есть всякие
unique_ptr. Но все это хорошо работает только в том случае, если
объект имел некую локальную область жизни(впрочем unique_ptr/shared_ptr могут и больше).
Если же объект выделяется из кучи в одном
месте, а освобождается в другом то можно забыть сделать ему
delete - получаем утечку памяти. Не смотря на множество способов
значительно сократить вероятность такого исхода (например <a href="http://en.wikipedia.org/wiki/Region-based_memory_management">арены</a>) полностью исключить
его нельзя.</p><p style="text-indent:20px"> В качестве решения этой проблемы в современных языках используются сборщики мусора.
Периодически они проходятся по памяти, и тем или иным способом удаляют неиспользуемые объекты.
Все бы хорошо, но у сборщиков
мусора возникают небольшие или большие проблемы с вызовами деструкторов у объектов.
Во-первых все сборщики мусора бессильны перед циклическими ссылками. Если объект a ссылается на b,
а b ссылается на a,
то не понятно в каком порядке вызывать деструкторы. Сначала у a или сначала у b. Если сначала у
a, то при вызове деструктора b его ссылка на a будет не валидна и наоборот. Отсутствие информации
о смысле взаимосвязей между объектами не дает сборщику мусора вызвать деструкторы в
корректном порядке. Копирующие сборщики мусора пошли еще дальше. Они вообще ничего не вызывают,
оставляя программиста один на один с этой проблемой и гордо подписываясь
"современный сборщик мусора".</p><p style="text-indent:20px"> Проблема, очевидно, состоит в том что оперативная память это не единственный ресурс
требующий освобождения. Еще, как минимум, есть объекты OS, мютексы, транзакции БД,
и много много другого. Часть из таких объектов будет через время прикрыта другим кодом
(например транзакции БД - но в любом случае они будут создавать лишнюю нагрузку на
базу все это время), но объекты OS останутся с процессом до его смерти. А ведь бОльшая часть
таких объектов имею локальную область видимости и при гарантированном вызове деструктор был бы
идеальным местом для их освобождения. Таким образом переходя
от С++ к языкам со сборкой мусора но с недетерменированным вызовом деструкторов мы выигрываем в коде
освобождения памяти, но проигрываем на других объектах.
Теперь вместо delete "где-то там" вы должны написать dispose/using/with/try-finally
прямо тут на каждый объект. Впрочем если файл, например, является не локальным,
то и это не поможет. Для примера можно открыть Экслера и глянуть как в яве правильно
работать с файлами. Страница ужасного кода ради одного маленького файлика. Не уверен, что такая
сборка мусора того стоила.</p><p style="text-indent:20px"> Итак посмотрим как себя ведут с деструкторами разные варианты питона.
В качестве примера возьмем вот такую программу:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="42de1c3e228d11e2b765bc7737dae05d" objtohide2="42dea6a4228d11e2b765bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="42de1c3e228d11e2b765bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">gc</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">sys</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">DestTest</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>, val):
<span style="color: #008000">self</span><span style="color: #666666">.</span>val <span style="color: #666666">=</span> val
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__del__</span>(<span style="color: #008000">self</span>):
sys<span style="color: #666666">.</span>stdout<span style="color: #666666">.</span>write(<span style="color: #008000">str</span>(<span style="color: #008000">self</span>) <span style="color: #666666">+</span> <span style="color: #BA2121">'.__del__()</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">'</span>)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__str__</span>(<span style="color: #008000">self</span>):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #BA2121">"DestTest({})"</span><span style="color: #666666">.</span>format(<span style="color: #008000">self</span><span style="color: #666666">.</span>val)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__repr__</span>(<span style="color: #008000">self</span>):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #BA2121">"DestTest({})"</span><span style="color: #666666">.</span>format(<span style="color: #008000">self</span><span style="color: #666666">.</span>val)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">simple_var</span>():
d <span style="color: #666666">=</span> DestTest(<span style="color: #BA2121">"simple var"</span>)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">mdeleted_var</span>():
d <span style="color: #666666">=</span> DestTest(<span style="color: #BA2121">"manyally deleted var"</span>)
<span style="color: #008000; font-weight: bold">del</span> d
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">simple_list</span>():
a <span style="color: #666666">=</span> [DestTest(<span style="color: #BA2121">"simple list"</span>)]
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">internal_exc</span>():
<span style="color: #008000; font-weight: bold">try</span>:
d <span style="color: #666666">=</span> DestTest(<span style="color: #BA2121">"exception_func"</span>)
<span style="color: #008000; font-weight: bold">raise</span> <span style="color: #D2413A; font-weight: bold">IndexError</span>()
<span style="color: #008000; font-weight: bold">except</span>:
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">exception_func</span>():
d <span style="color: #666666">=</span> DestTest(<span style="color: #BA2121">"exception_func"</span>)
<span style="color: #008000; font-weight: bold">raise</span> <span style="color: #D2413A; font-weight: bold">IndexError</span>()
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">self_ref_list</span>():
a <span style="color: #666666">=</span> [DestTest(<span style="color: #BA2121">"self-ref list"</span>)]
a<span style="color: #666666">.</span>append(a)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">cycle_refs</span>():
d1 <span style="color: #666666">=</span> DestTest(<span style="color: #BA2121">"cycle_ref_obj1"</span>)
d2 <span style="color: #666666">=</span> DestTest(<span style="color: #BA2121">"cycle_ref_obj2"</span>)
d3 <span style="color: #666666">=</span> DestTest(<span style="color: #BA2121">"free_obj"</span>)
d1<span style="color: #666666">.</span>ref <span style="color: #666666">=</span> d2
d2<span style="color: #666666">.</span>ref <span style="color: #666666">=</span> d1
d2<span style="color: #666666">.</span>ref2 <span style="color: #666666">=</span> d3
simple_var()
sys<span style="color: #666666">.</span>stdout<span style="color: #666666">.</span>write(<span style="color: #BA2121">"after simple var</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">"</span>)
sys<span style="color: #666666">.</span>stdout<span style="color: #666666">.</span>write(<span style="color: #BA2121">"</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">"</span>)
simple_list()
sys<span style="color: #666666">.</span>stdout<span style="color: #666666">.</span>write(<span style="color: #BA2121">"after simple list</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">"</span>)
sys<span style="color: #666666">.</span>stdout<span style="color: #666666">.</span>write(<span style="color: #BA2121">"</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">"</span>)
mdeleted_var()
sys<span style="color: #666666">.</span>stdout<span style="color: #666666">.</span>write(<span style="color: #BA2121">"after manually deleted var</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">"</span>)
sys<span style="color: #666666">.</span>stdout<span style="color: #666666">.</span>write(<span style="color: #BA2121">"</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">"</span>)
self_ref_list()
sys<span style="color: #666666">.</span>stdout<span style="color: #666666">.</span>write(<span style="color: #BA2121">"after self ref list</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">"</span>)
gc<span style="color: #666666">.</span>collect()
sys<span style="color: #666666">.</span>stdout<span style="color: #666666">.</span>write(<span style="color: #BA2121">"after gc.collect()</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">"</span>)
sys<span style="color: #666666">.</span>stdout<span style="color: #666666">.</span>write(<span style="color: #BA2121">"</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">"</span>)
internal_exc()
sys<span style="color: #666666">.</span>stdout<span style="color: #666666">.</span>write(<span style="color: #BA2121">"after internal exc</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">"</span>)
<span style="color: #008000; font-weight: bold">try</span>:
exception_func()
<span style="color: #008000; font-weight: bold">except</span> <span style="color: #D2413A; font-weight: bold">Exception</span> <span style="color: #008000; font-weight: bold">as</span> x:
<span style="color: #008000; font-weight: bold">pass</span>
sys<span style="color: #666666">.</span>stdout<span style="color: #666666">.</span>write(<span style="color: #BA2121">"after exception func</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">"</span>)
gc<span style="color: #666666">.</span>collect()
sys<span style="color: #666666">.</span>stdout<span style="color: #666666">.</span>write(<span style="color: #BA2121">"after gc.collect()</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">"</span>)
<span style="color: #008000; font-weight: bold">try</span>:
sys<span style="color: #666666">.</span>exc_clear()
<span style="color: #008000; font-weight: bold">except</span> <span style="color: #D2413A; font-weight: bold">AttributeError</span>:
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #008000; font-weight: bold">else</span>:
sys<span style="color: #666666">.</span>stdout<span style="color: #666666">.</span>write(<span style="color: #BA2121">"after sys.exc_clear()</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">"</span>)
sys<span style="color: #666666">.</span>stdout<span style="color: #666666">.</span>write(<span style="color: #BA2121">"</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">"</span>)
cycle_refs()
sys<span style="color: #666666">.</span>stdout<span style="color: #666666">.</span>write(<span style="color: #BA2121">"after cycle refs</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">"</span>)
gc<span style="color: #666666">.</span>collect()
sys<span style="color: #666666">.</span>stdout<span style="color: #666666">.</span>write(<span style="color: #BA2121">"after gc.collect()</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">"</span>)
sys<span style="color: #666666">.</span>stdout<span style="color: #666666">.</span>write(<span style="color: #BA2121">"</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">"</span>)
d <span style="color: #666666">=</span> DestTest(<span style="color: #BA2121">"module var"</span>)
</pre></div></span><span style="line-height:100%;display:none" id="42dea6a4228d11e2b765bc7737dae05d"><pre><font face="courier">import gc
import sys
class DestTest(object):
def __init__(self, val):
self.val = val
def __del__(self):
sys.stdout.write(str(self) + '.__del__()\n')
def __str__(self):
return "DestTest({})".format(self.val)
def __repr__(self):
return "DestTest({})".format(self.val)
def simple_var():
d = DestTest("simple var")
def mdeleted_var():
d = DestTest("manyally deleted var")
del d
def simple_list():
a = [DestTest("simple list")]
def internal_exc():
try:
d = DestTest("exception_func")
raise IndexError()
except:
pass
def exception_func():
d = DestTest("exception_func")
raise IndexError()
def self_ref_list():
a = [DestTest("self-ref list")]
a.append(a)
def cycle_refs():
d1 = DestTest("cycle_ref_obj1")
d2 = DestTest("cycle_ref_obj2")
d3 = DestTest("free_obj")
d1.ref = d2
d2.ref = d1
d2.ref2 = d3
simple_var()
sys.stdout.write("after simple var\n")
sys.stdout.write("\n")
simple_list()
sys.stdout.write("after simple list\n")
sys.stdout.write("\n")
mdeleted_var()
sys.stdout.write("after manually deleted var\n")
sys.stdout.write("\n")
self_ref_list()
sys.stdout.write("after self ref list\n")
gc.collect()
sys.stdout.write("after gc.collect()\n")
sys.stdout.write("\n")
internal_exc()
sys.stdout.write("after internal exc\n")
try:
exception_func()
except Exception as x:
pass
sys.stdout.write("after exception func\n")
gc.collect()
sys.stdout.write("after gc.collect()\n")
try:
sys.exc_clear()
except AttributeError:
pass
else:
sys.stdout.write("after sys.exc_clear()\n")
sys.stdout.write("\n")
cycle_refs()
sys.stdout.write("after cycle refs\n")
gc.collect()
sys.stdout.write("after gc.collect()\n")
sys.stdout.write("\n")
d = DestTest("module var")</font></pre></span><p style="text-indent:20px"> В идеальном мире вызов деструктора у объектов класса
DestTest во всех этих функциях должен происходить до выхода из соответствующей функции.
Итак что получается:</p><p style="text-indent:20px">python2.7</p><pre><font face="courier"> DestTest(simple var).__del__()
after simple var
DestTest(simple list).__del__()
after simple list
DestTest(manyally deleted var).__del__()
after manually deleted var
after self ref list
DestTest(self-ref list).__del__()
after gc.collect()
after exception func
after gc.collect()
DestTest(exception_func).__del__()
after sys.exc_clear()
after cycle refs
after gc.collect()
Exception AttributeError: "'NoneType' object has no attribute 'stdout'" in
<bound method DestTest.__del__ of DestTest(module var)> ignored</font></pre><p style="text-indent:20px"> Более-менее. Деструктор у циклических ссылок не был вызван, как и ожидалось.
Для уничтожения объектов, связанных с исключением, дошедшем до уровня модуля
приходится вызывать sys.clear_exc(), в остальных случаях с исключениями все ок.
Интересно, что питон освободил sys.stdout раньше, чем переменную d, в итоге чего ее деструктор
отработал не корректно (впрочем это поведение не всегда повторяется).</p><p style="text-indent:20px">python3.3</p><pre><font face="courier"> DestTest(simple var).__del__()
after simple var
DestTest(simple list).__del__()
after simple list
DestTest(manyally deleted var).__del__()
after manually deleted var
after self ref list
DestTest(self-ref list).__del__()
after gc.collect()
DestTest(exception_func).__del__()
after exception func
after gc.collect()
after cycle refs
after gc.collect()
Exception AttributeError: "'NoneType' object has no attribute 'stdout'" in
<bound method DestTest.__del__ of DestTest(module var)> ignore</font></pre><p style="text-indent:20px">Почти то-же самое, что и 2.7, только sys.exc_clear() больше не нужно.</p><p style="text-indent:20px">ironpython2.7</p><pre><font face="courier"> after simple var
after simple list
after manually deleted var
after self ref list
DestTest(self-ref list).__del__()
DestTest(manyally deleted var).__del__()
DestTest(simple list).__del__()
DestTest(simple var).__del__()
after gc.collect()
after exception func
DestTest(exception_func).__del__()
after gc.collect()
after sys.exc_clear()
after cycle refs
DestTest(free_obj).__del__()
DestTest(cycle_ref_obj2).__del__()
DestTest(cycle_ref_obj1).__del__()
after gc.collect()
DestTest(module var).__del__()</font></pre><p style="text-indent:20px">Удаляет все, но только при сборке мусора, до тех пор все объекты живут где-то в памяти.</p><p style="text-indent:20px"> И - встречаем победителя. Сама лаконичность или
мир всем вашим деструкторам от нашей Java и копирующего сборщика мусора:
jython2.7a2 - Java HotSpot(TM) Client VM (Oracle Corporation) on java1.7.0_07</p><pre><font face="courier"> after simple var
after simple list
after manually deleted var
after self ref list
after gc.collect()
after exception func
after gc.collect()
after sys.exc_clear()
after cycle refs
after gc.collect()</font></pre><p style="text-indent:20px">Правда в ява есть и другие варианты сборщика мусора, может там чуть по-лучше.</p><p style="text-indent:20px"> А вывод все тот-же. По возможности - используйте with. По невозможности -
попробуйте поправить код, так что-бы можно было использовать with. Иначе -
аккуратно вызывайте деструкторы руками</p><p style="text-indent:20px">P.S. Как я люблю, когда мне предлагают делать какую-то рутинную работу в коде
"аккуратно".</p>Ссылки:<br> <a name="этом"><a href="http://koder-ua.blogspot.com/2012/10/python-with.html">koder-ua.blogspot.com/2012/10/python-with.html</a></a><br> <a name="арены"><a href="http://en.wikipedia.org/wiki/Region-based_memory_management">en.wikipedia.org/wiki/Region-based_memory_management</a></a><br><p style="text-indent:20px">Исходники этого и других постов со скриптами лежат тут - <a href="https://github.com/koder-ua/python-lectures">github.com/koder-ua</a>. При использовании их, пожалуйста, ссылайтесь на <a href="http://koder-ua.blogspot.com/">koder-ua.blogspot.com</a>.</p>
<script type="text/javascript">
function on_hidabble_click()
{
var me = $(this);
var hide_id = me.attr("objtohide");
var controlled_object = $('#' + hide_id);
controlled_object.toggle();
if ( controlled_object.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".hidder").click(on_hidabble_click);
function on_double_hidabble_click()
{
var me = $(this);
var hide_id1 = me.attr("objtohide1");
var hide_id2 = me.attr("objtohide2");
var controlled_object1 = $('#' + hide_id1);
var controlled_object2 = $('#' + hide_id2);
controlled_object1.toggle();
controlled_object2.toggle();
if ( controlled_object1.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".dhidder").click(on_double_hidabble_click);
</script>
Anonymoushttp://www.blogger.com/profile/06645078574760734586noreply@blogger.com0tag:blogger.com,1999:blog-1174489715777430743.post-80856148069987455392012-10-28T18:25:00.000+02:002012-10-28T18:25:02.918+02:00Зачем в python with<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<p style="text-indent:20px"> Долгое время при работе с файлами из python я писал
примерно следующий код:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="f607541a211b11e298c414feb5b819a0" objtohide2="f607dae8211b11e298c414feb5b819a0" >Без подсветки синтаксиса</a><br><span id="f607541a211b11e298c414feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">some_func</span>(fname):
fd <span style="color: #666666">=</span> <span style="color: #008000">open</span>(fname)
some_data_processing(fd<span style="color: #666666">.</span>read())
<span style="color: #008000; font-weight: bold">return</span> result
</pre></div></span><span style="line-height:100%;display:none" id="f607dae8211b11e298c414feb5b819a0"><pre><font face="courier">def some_func(fname):
fd = open(fname)
some_data_processing(fd.read())
return result</font></pre></span><p style="text-indent:20px"> Тут предполагается, что в любом случае при выходе из функции
переменная fd уничтожится и вместе с ней закроется файл и все будут жить
долго и счастливо.</p><p style="text-indent:20px"> Но что будет если в some_data_processing произойдет исключение?</p><a name='more'></a><p style="text-indent:20px">Например так:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="f6098d48211b11e298c414feb5b819a0" objtohide2="f60a174a211b11e298c414feb5b819a0" >Без подсветки синтаксиса</a><br><span id="f6098d48211b11e298c414feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">sys</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">TestClass</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__del__</span>(<span style="color: #008000">self</span>):
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"I'm deleted"</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">data_process</span>():
obj <span style="color: #666666">=</span> TestClass()
<span style="color: #008000; font-weight: bold">raise</span> <span style="color: #D2413A; font-weight: bold">IndexError</span>()
<span style="color: #008000; font-weight: bold">try</span>:
data_process()
<span style="color: #008000; font-weight: bold">except</span>:
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"In exception handler"</span>
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"after except"</span>
</pre></div></span><span style="line-height:100%;display:none" id="f60a174a211b11e298c414feb5b819a0"><pre><font face="courier">import sys
class TestClass(object):
def __del__(self):
print "I'm deleted"
def data_process():
obj = TestClass()
raise IndexError()
try:
data_process()
except:
print "In exception handler"
print "after except"</font></pre></span><p style="text-indent:20px">На консоли появляется:</p><pre><font face="courier"> In exception handler
after except
I'm deleted</font></pre><p style="text-indent:20px"> Почему-то "In exception handler" и "after except" выводятся раньше
"I'm deleted".</p><p style="text-indent:20px"> Первая проблема в том, что вместе с исключением питон хранит и трейс стека,
содержащий все фреймы вплоть до породившего исключение.
А внутри фрейма живет f_locals - словарь локальных переменных, и именно
он имеет ссылку на экземпляр класса TestClass. Таким образом до окончания
обработки исключения obj будет жить точно. Почему же "after except" появляется
раньше чем "I'm deleted"? Было бы логично чистить трейс после успешного выхода из блока try.
Дело в том что 2.X питон не всегда чистит внутренние структуры
после обработки исключения и в общем случае вы должны явно вызывать функцию <a href="http://docs.python.org/2/library/sys.html#sys.exc_clear">sys.exc_clear</a>
чтобы очистить их. Когда я подошел с этим вопросом к Larry Hastings (одному из
основных разработчиков ядра питона) ему потребовалось около 20ти минут, что-бы понять что
происходит и найти в документации sys.exc_clear.
(Правда стоит отметить, что он давно использует 3.X, где это поведение стало адекватнее.)
В 3.X это поведение улучшили, и теперь sys.exc_clear автоматически вызывается окончанию
обработки исключения.</p><p style="text-indent:20px"> Кстати, если вы напишете примерно такой код:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="f60b9dcc211b11e298c414feb5b819a0" objtohide2="f60badd0211b11e298c414feb5b819a0" >Без подсветки синтаксиса</a><br><span id="f60b9dcc211b11e298c414feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">try</span>:
data_process()
<span style="color: #008000; font-weight: bold">except</span>:
fr <span style="color: #666666">=</span> sys<span style="color: #666666">.</span>exc_info()[<span style="color: #666666">2</span>]
<span style="color: #008000; font-weight: bold">del</span> fr
</pre></div></span><span style="line-height:100%;display:none" id="f60badd0211b11e298c414feb5b819a0"><pre><font face="courier">try:
data_process()
except:
fr = sys.exc_info()[2]
del fr</font></pre></span><p style="text-indent:20px">то не забудьте удалить fr используя del, как в последней строке - иначе он образует
циклическую ссылку с текущим фреймом и тогда все станет совсем плохо.</p><p style="text-indent:20px"> Стоит отметить, что подобное поведение проявляется не всегда. Например
следующий код исполняется более предсказуемо:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="f60cc15c211b11e298c414feb5b819a0" objtohide2="f60cc6a2211b11e298c414feb5b819a0" >Без подсветки синтаксиса</a><br><span id="f60cc15c211b11e298c414feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">sys</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">TestClass</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__del__</span>(<span style="color: #008000">self</span>):
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"I'm deleted"</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">data_process</span>():
fd <span style="color: #666666">=</span> TestClass()
<span style="color: #008000; font-weight: bold">try</span>:
<span style="color: #008000; font-weight: bold">raise</span> <span style="color: #D2413A; font-weight: bold">IndexError</span>()
<span style="color: #008000; font-weight: bold">except</span>:
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"In internal exception handler"</span>
data_process()
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"after except"</span>
</pre></div></span><span style="line-height:100%;display:none" id="f60cc6a2211b11e298c414feb5b819a0"><pre><font face="courier">import sys
class TestClass(object):
def __del__(self):
print "I'm deleted"
def data_process():
fd = TestClass()
try:
raise IndexError()
except:
print "In internal exception handler"
data_process()
print "after except"</font></pre></span><pre><font face="courier"> In internal exception handler
I'm deleted
after except</font></pre><p style="text-indent:20px"> В общем что-бы гарантированно избавить себя от этих проблем нужно явно
закрывать все файлы и прочие объекты или так:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="f60d630a211b11e298c414feb5b819a0" objtohide2="f60d6832211b11e298c414feb5b819a0" >Без подсветки синтаксиса</a><br><span id="f60d630a211b11e298c414feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">fd <span style="color: #666666">=</span> <span style="color: #008000">open</span>(fname)
<span style="color: #008000; font-weight: bold">try</span>:
process_code()
<span style="color: #008000; font-weight: bold">finally</span>:
fd<span style="color: #666666">.</span>close()
</pre></div></span><span style="line-height:100%;display:none" id="f60d6832211b11e298c414feb5b819a0"><pre><font face="courier">fd = open(fname)
try:
process_code()
finally:
fd.close()</font></pre></span><p style="text-indent:20px">или так:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="f60dad56211b11e298c414feb5b819a0" objtohide2="f60daebe211b11e298c414feb5b819a0" >Без подсветки синтаксиса</a><br><span id="f60dad56211b11e298c414feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">with</span> <span style="color: #008000">open</span>(fname) <span style="color: #008000; font-weight: bold">as</span> fd:
process_code()
</pre></div></span><span style="line-height:100%;display:none" id="f60daebe211b11e298c414feb5b819a0"><pre><font face="courier">with open(fname) as fd:
process_code()</font></pre></span><p style="text-indent:20px">собственное with именно для этого и был сделан. Без его использования вы рискуете
исчерпать лимит на дескрипторы или что-там-еще в
зависимости от объектов. Впрочем это только начало печальной истории, продолжение дальше.</p>Ссылки:<br> <a name="sys.exc_clear"><a href="http://docs.python.org/2/library/sys.html#sys.exc_clear">docs.python.org/2/library/sys.html#sys.exc_clear</a></a><br> <a name="nuitka"><a href="http://nuitka.net/">nuitka.net</a></a><br><p style="text-indent:20px">Исходники этого и других постов со скриптами лежат тут - <a href="https://github.com/koder-ua/python-lectures">github.com/koder-ua</a>. При использовании их, пожалуйста, ссылайтесь на <a href="http://koder-ua.blogspot.com/">koder-ua.blogspot.com</a>.</p>
<script type="text/javascript">
function on_hidabble_click()
{
var me = $(this);
var hide_id = me.attr("objtohide");
var controlled_object = $('#' + hide_id);
controlled_object.toggle();
if ( controlled_object.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".hidder").click(on_hidabble_click);
function on_double_hidabble_click()
{
var me = $(this);
var hide_id1 = me.attr("objtohide1");
var hide_id2 = me.attr("objtohide2");
var controlled_object1 = $('#' + hide_id1);
var controlled_object2 = $('#' + hide_id2);
controlled_object1.toggle();
controlled_object2.toggle();
if ( controlled_object1.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".dhidder").click(on_double_hidabble_click);
</script>
Anonymoushttp://www.blogger.com/profile/06645078574760734586noreply@blogger.com15tag:blogger.com,1999:blog-1174489715777430743.post-23691030692629710172012-09-23T19:56:00.000+03:002012-09-23T20:35:19.251+03:00Установка питона и пакетов<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<p style="text-indent:20px"> В этой статье я попытаюсь описать процесс создания готового python окружения
и работу с пакетами на пользовательском уровне. Статья расcчитана
на новичков (в основном для студентов, слушающих мои курсы).</p><a name='more'></a><p style="text-indent:20px">Задачи обычно возникающие при установки питона и его пакетов:</p><ul><li>Выбор дистрибутив питона и его установка<li>Выбор IDE<li>Поиск и установка пакетов</ul><p style="text-indent:20px">Кроме этого я пробегусь по этим полезным вещам:</p><ul><li><a href="http://pypi.python.org/pypi/virtualenv">virtualenv</a><li>lint'ы<li>ipython<li><a href="http://pythonanywhere.com">pythonanywhere.com</a></ul><br><h3>Выбор дистрибутив питона и его установка</h3><p style="text-indent:20px"> Если вы используете linux, то лучше использовать python идущий в пакетах -
как правило это немного измененный cpython. Для windows можно выбирать между
<a href="http://www.python.org/download/">стандартным</a> питоном и дистрибутивом от <a href="http://www.activestate.com/activepython/downloads">Active State</a>.
Последний содержит расширенную документацию и некоторые дополнительные
библиотеки. Мы не будем рассматривать PyPy/Stackless/etc - ограничимся только
CPython. Дальше нужно сделать выбор между двумя ветками - 3.2/3.3 и 2.7. Пока
что с 2.7 у вас будет меньше проблем, но третья версия по поддержке уже
подбирается достаточно близко. x86 и amd64 версии выбираем по вкусу.
Установка и под windows и совершенно стандартна и не должна вызывать проблем.
В linux питон уже почти 100% установлен.</p><br><h3>Выбор IDE</h3><p style="text-indent:20px"> Динамический характер языка делает написание функциональных IDE достаточно
сложным, а высокая компактность кода и pythonic подход заметно уменьшает в них
необходимость. Так что не сложные проекты можно делать в продвинутых текстовых
редакторах - [notepad++], <a href="http://www.sublimetext.com/">sublime text</a> (или vim/emacs). Хотя новичкам IDE
будут оказывать заметную помошь встроенной подсказкой и каким ни каким
статическим анализом. Из IDE я бы выделил eclipse + <a href="http://pydev.org/">pydev</a> и платные <a href="http://www.jetbrains.com/pycharm/">PyCharm</a>
и <a href="http://www.activestate.com/komodo-ide">KomodoIDE</a>. Также есть <a href="http://pytools.codeplex.com/">Python tools for VS</a>, которые добавляет поддержку
cpython и ironpython в VS2010/VS2012.</p><p style="text-indent:20px">Я бы советовал выбирать между sublime text и eclipse + pydev.</p><br><h3>Поиск и установка пакетов</h3><p style="text-indent:20px"> Пакеты/модули в python это файлы с расширениями py/pyc/pyo/(pyd или so), или
директории с такими файлами. Также весь пакет может быть в одном архиве
(только если пакет не содержит pyd/so файлы). По умолчанию пакеты
устанавливаются в системную папку - PYTHON_ROOT\lib\site-packages для windows и
/usr/local/lib/pythonXX/dist-packages для ubuntu (XX - версия питона,
PYTHON_ROOT - корневая папка установки python, как правило С:\PythonXX)</p><p style="text-indent:20px"> Если вы используете linux, то можно использовать пакеты из дистрибутива -
в Ubuntu/Fedora есть практически все. Иначе искать пакеты в основном стоит на
<a href="http://pypi.python.org/pypi">pypi</a> или с помощью google. Пакеты могут быть в трех основных форматах: архив,
exe/msi, egg.</p><p style="text-indent:20px"> Архив нужно распаковать, в корневой папке должен быть файл setup.py. Если
его там нет, то можно просто скопировать содержимое архива в директорию с
пакетами. Если setup.py есть, то нужно выполнить python setup.py install. При
этом следует использовать тот интерпретатор питона, в который вы хотите
установить пакет. Если пакет не предоставляет модулей написанных на С/С++, то
установка должна пройти без особенных проблем. Иначе python будет пытаться
собрать компилируемые расширения. В linux такой процесс проходит чаще всего
безболезненно (максимум требуется установка пакетов с заголовочными файлами для
для используемых C библиотек), а вот в windows путь компиляции может быть
достаточно трудным.</p><p style="text-indent:20px"> При установке в windows проще использовать уже собранный exe/msi файл.
Для большинства пакетов они доступны на pypi или на сайте библиотеки, также
много бинарных пакетов можно найти на <a href="http://www.lfd.uci.edu/~gohlke/pythonlibs/">pythonlibs</a>. При загрузке обратите
внимание на архитектуру и версию python. Для установки такие пакеты нужно
запустить. Библиотеки не содержащие компилируемого кода уставливаются без
проблем на обеих системах.</p><p style="text-indent:20px"> egg это формат пакетов одного из пакетные менеджеров питона - <a href="http://pypi.python.org/pypi/setuptools">setuptools</a>.
Грубо говоря это zip архив с дополнительной информацией о пакете и его
зависимостях. Более новой и активно развиваемой альтернативой setuptools
является <a href="http://www.pip-installer.org/en/latest/index.html">pip</a>. pip использует код setuptools(или distribute) и не поддерживает
egg. Оба этих менеджера умеют находить пакеты по имени на pypi, по URL и
локально. Поддерживаются разнообразные форматы архивов и автоматическая
установка зависимостей. pip умеет деинсталлировать пакеты и поддерживает
установку из svn/git/mercurial.</p><p style="text-indent:20px">Установка pip - <a href="http://www.pip-installer.org/en/latest/installing.html">www.pip-installer.org/en/latest/installing.html</a></p><ul><li>скачать и запустить <a href="http://python-distribute.org/distribute_setup.py">python-distribute.org/distribute_setup.py</a><li>скачать и запустить <a href="https://raw.github.com/pypa/pip/master/contrib/get-pip.py">raw.github.com/pypa/pip/master/contrib/get-pip.py</a></ul><p style="text-indent:20px">Установка setuptools</p><ul><li>Скачать и запустить <a href="http://peak.telecommunity.com/dist/ez_setup.py">peak.telecommunity.com/dist/ez_setup.py</a></ul><p style="text-indent:20px"> Оба этих менеджера предоставляют команду easy_install, pip
кроме этого предоставляет команду pip.</p><p style="text-indent:20px">Использование (примеры команд без их вывода):</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="e94efea405a411e2a008bc7737dae05d" objtohide2="e94f86c605a411e2a008bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="e94efea405a411e2a008bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">#</span> pip install pylint <span style="color: #408080; font-style: italic"># установим pylint</span>
<span style="color: #000080; font-weight: bold">#</span> easy_install install -U pylint <span style="color: #408080; font-style: italic"># обновить пакет</span>
<span style="color: #000080; font-weight: bold">#</span> pip install --upgrade simplejson
<span style="color: #000080; font-weight: bold">#</span> pip uninstall simplejson <span style="color: #408080; font-style: italic"># удалить</span>
<span style="color: #000080; font-weight: bold">#</span> pip install http://my.package.repo/SomePackage-1.0.4.zip
<span style="color: #000080; font-weight: bold">#</span> pip install git+https://github.com/simplejson/simplejson.git
<span style="color: #000080; font-weight: bold">#</span> pip install svn+svn://svn.zope.org/repos/main/zope.interface/trunk/
</pre></div></span><span style="line-height:100%;display:none" id="e94f86c605a411e2a008bc7737dae05d"><pre><font face="courier"># pip install pylint # установим pylint
# easy_install install -U pylint # обновить пакет
# pip install --upgrade simplejson
# pip uninstall simplejson # удалить
# pip install http://my.package.repo/SomePackage-1.0.4.zip
# pip install git+https://github.com/simplejson/simplejson.git
# pip install svn+svn://svn.zope.org/repos/main/zope.interface/trunk/</font></pre></span><br><h3>virtualenv</h3><p style="text-indent:20px"> <a href="http://pypi.python.org/pypi/virtualenv">virtualenv</a> позволяет делать на одной машине несколько независимых
инсталляций python, каждая из которых имеет свой интерпретатор, набор настроек
и библиотек. Некоторые из таких окружений могут использовать системную папку с
дополнительными пакетами. Кроме этого virtualenv позволяет устанавливать питон
и пакеты пользователям без прав root.</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="e950e5de05a411e2a008bc7737dae05d" objtohide2="e9516d2e05a411e2a008bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="e950e5de05a411e2a008bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">$</span> sudo pip install virtualenv <span style="color: #408080; font-style: italic"># или sudo apt-get install python-virtualenv</span>
<span style="color: #000080; font-weight: bold">$</span> virtualenv --distribute ENV_NAME <span style="color: #408080; font-style: italic"># или python virtualenv.py --distribute ENV_NAME</span>
<span style="color: #808080">--distribute заставить virtualenv установить distribute вместо setuptools.</span>
</pre></div></span><span style="line-height:100%;display:none" id="e9516d2e05a411e2a008bc7737dae05d"><pre><font face="courier">$ sudo pip install virtualenv # или sudo apt-get install python-virtualenv
$ virtualenv --distribute ENV_NAME # или python virtualenv.py --distribute ENV_NAME
--distribute заставить virtualenv установить distribute вместо setuptools.</font></pre></span><p style="text-indent:20px"> Эта команда создаст папку ENV_NAME внутри которой будет интерпретатор python
ENV_NAME/bin/python и каталог для пакетов ENV_NAME/lib/pythonX.X/site-packages.
ENV_NAME/bin/python будет настроен на поиск пакетов в
ENV_NAME/lib/pythonX.X/site-packages. Также virtualenv устанавливает в новое
окружение pip. Что-бы активировать это окружений нужно исполнить скрипт
activate.</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="e9529d0205a411e2a008bc7737dae05d" objtohide2="e952af1805a411e2a008bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="e9529d0205a411e2a008bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">$</span> <span style="color: #008000">source </span>ENV_NAME/bin/activate
<span style="color: #000080; font-weight: bold">></span> ENV_NAME<span style="color: #BB6622; font-weight: bold">\S</span>cripts<span style="color: #BB6622; font-weight: bold">\a</span>ctivate <span style="color: #408080; font-style: italic"># для windows</span>
</pre></div></span><span style="line-height:100%;display:none" id="e952af1805a411e2a008bc7737dae05d"><pre><font face="courier">$ source ENV_NAME/bin/activate
> ENV_NAME\Scripts\activate # для windows</font></pre></span><p style="text-indent:20px"> Теперь команда python будет приводить к запуску питона из
ENV_NAME/bin/python, то же относится и к pip. После окончания работы нужно
выполнить deactivate. virtualenv включили в стандартную библиотеку
начиная с python3.3</p><br><h3>lint'ы</h3><p style="text-indent:20px"> Линтами называют средства статического анализа по имени первой такой
утилиты, которая находила странно написанные участки C кода, потенциально
содержащие ошибки. Из-за динамического характера python сделать для него очень
хороший линт невозможно, а даже просто хороший очень сложно. Ошибки при которых
С программа даже не скомпилируется могут легко загнать в угол python линты. Но
тем не менее значительную часть (а у начинающих - практически все)
ошибок/опечаток они найдут.</p><p style="text-indent:20px"> Три основных lint'а для python это <a href="http://pypi.python.org/pypi/pylint">pylint</a>, <a href="http://pychecker.sourceforge.net/">pychecker</a> и <a href="https://launchpad.net/pyflakes">pyflakes</a>. Из
них pylint, наверное, наиболее сообразительный. Кроме этого он имеет большое
количество настроек, которые позволяют изменить особенности проверок. Также
pylint проверяет стиль кода, используя шаблоны из конфигурационного файла и
собирает полезную статистику. Плюс большая часть IDE и даже sublime имеют
интеграцию с pylint.</p><p style="text-indent:20px"> По умолчанию pylint слишком требовательный так что начинать его
использование стоит с подстройки конфига под себя, кроме этого иногда он дает
ложные срабатывания.</p><p style="text-indent:20px"> Как более легкую альтернативу можно использовать <a href="http://pypi.python.org/pypi/pep8">pep8</a>, проверяющий код на
соответствие основному python <a href="http://www.python.org/dev/peps/pep-0008/">стандарту кодирования</a>.</p><br><h3>ipython</h3><p style="text-indent:20px"> Чуть подробнее о установке ipython. Под linux с правами root все
просто (ubuntu):</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="e953b55205a411e2a008bc7737dae05d" objtohide2="e953baac05a411e2a008bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="e953b55205a411e2a008bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">$</span> sudo apt-get install ipython ipython-doc ipython-notebook ipython-qtconsole python-zmq
</pre></div></span><span style="line-height:100%;display:none" id="e953baac05a411e2a008bc7737dae05d"><pre><font face="courier">$ sudo apt-get install ipython ipython-doc ipython-notebook ipython-qtconsole python-zmq</font></pre></span><p style="text-indent:20px">или</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="e95419b605a411e2a008bc7737dae05d" objtohide2="e9541ee805a411e2a008bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="e95419b605a411e2a008bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">$</span> sudo apt-get install --install-suggests ipython
</pre></div></span><span style="line-height:100%;display:none" id="e9541ee805a411e2a008bc7737dae05d"><pre><font face="courier">$ sudo apt-get install --install-suggests ipython</font></pre></span><p style="text-indent:20px">ipython готов к запуску -</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="e9548d9c05a411e2a008bc7737dae05d" objtohide2="e95492d805a411e2a008bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="e9548d9c05a411e2a008bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">$</span> ipython qtconsole <span style="color: #408080; font-style: italic"># GUI консоль</span>
<span style="color: #000080; font-weight: bold">$</span> ipython notebook <span style="color: #408080; font-style: italic"># Web интерфейс</span>
<span style="color: #000080; font-weight: bold">$</span> ipython <span style="color: #408080; font-style: italic"># консольный интерфейс</span>
</pre></div></span><span style="line-height:100%;display:none" id="e95492d805a411e2a008bc7737dae05d"><pre><font face="courier">$ ipython qtconsole # GUI консоль
$ ipython notebook # Web интерфейс
$ ipython # консольный интерфейс</font></pre></span><p style="text-indent:20px"> Под windows все не так просто - нужно загрузить все пакеты и зависимости
вручную и установить их. pip поможет не сильно, поскольку большая часть пакетов
С расширения с внешними зависимости и собирать их будет лишней сложностью.
Зависимости ipython (поскольку мы не будем использовать pip то их придется
выяснять и устанавливать самостоятельно) можно определить двумя способами -
найти в <a href="http://ipython.org/ipython-doc/stable/install/install.html">документации по установке</a> или пытаться запускать
ipython и смотреть на ошибки импорта. Из документации находим зависимости:</p><ul><li>pyqt или pyside<li>pyzmq<li>tornado<li>pygments<li>pyreadline<li>distribute или setuptools</ul><p style="text-indent:20px"> Бинарные версии всех этих пакетов есть в <a href="http://www.lfd.uci.edu/~gohlke/pythonlibs/">pythonlibs</a>. Загружаем и ставим
в любом порядке. После чего выбираем из:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="e954dd9c05a411e2a008bc7737dae05d" objtohide2="e954df2205a411e2a008bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="e954dd9c05a411e2a008bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">></span> С:<span style="color: #BB6622; font-weight: bold">\P</span>ython2.7<span style="color: #BB6622; font-weight: bold">\S</span>cripts<span style="color: #BB6622; font-weight: bold">\i</span>python.bat qtconsole <span style="color: #408080; font-style: italic"># GUI консоль</span>
<span style="color: #000080; font-weight: bold">></span> С:<span style="color: #BB6622; font-weight: bold">\P</span>ython2.7<span style="color: #BB6622; font-weight: bold">\S</span>cripts<span style="color: #BB6622; font-weight: bold">\i</span>python.bat notebook <span style="color: #408080; font-style: italic"># Web интерфейс</span>
<span style="color: #000080; font-weight: bold">></span> С:<span style="color: #BB6622; font-weight: bold">\P</span>ython2.7<span style="color: #BB6622; font-weight: bold">\S</span>cripts<span style="color: #BB6622; font-weight: bold">\i</span>python.bat <span style="color: #408080; font-style: italic"># консольный интерфейс</span>
</pre></div></span><span style="line-height:100%;display:none" id="e954df2205a411e2a008bc7737dae05d"><pre><font face="courier">> С:\Python2.7\Scripts\ipython.bat qtconsole # GUI консоль
> С:\Python2.7\Scripts\ipython.bat notebook # Web интерфейс
> С:\Python2.7\Scripts\ipython.bat # консольный интерфейс</font></pre></span><br><h3><a href="http://pythonanywhere.com">pythonanywhere.com</a></h3><p style="text-indent:20px"> Если поставить питон совсем никак нельзя, то можно воспользоваться web
консолью на указанном сайте. После регистрации можно бесплатно запустить 2
python/ipython консоли в браузере и пробовать python без установки.</p>Ссылки:<br> <a name="virtualenv"><a href="http://pypi.python.org/pypi/virtualenv">pypi.python.org/pypi/virtualenv</a></a><br> <a name="стандартным"><a href="http://www.python.org/download/">www.python.org/download</a></a><br> <a name="active_python"><a href="http://www.activestate.com/activepython/downloads">www.activestate.com/activepython/downloads</a></a><br> <a name="pypi"><a href="http://pypi.python.org/pypi">pypi.python.org/pypi</a></a><br> <a name="pythonlibs"><a href="http://www.lfd.uci.edu/~gohlke/pythonlibs/">www.lfd.uci.edu/~gohlke/pythonlibs</a></a><br> <a name="pip"><a href="http://www.pip-installer.org/en/latest/index.html">www.pip-installer.org/en/latest/index.html</a></a><br> <a name="ipython_install"><a href="http://ipython.org/ipython-doc/stable/install/install.html">ipython.org/ipython-doc/stable/install/install.html</a></a><br> <a name="pychecker"><a href="http://pychecker.sourceforge.net/">pychecker.sourceforge.net</a></a><br> <a name="Python_tools_for_VS"><a href="http://pytools.codeplex.com/">pytools.codeplex.com</a></a><br> <a name="pyflakes"><a href="https://launchpad.net/pyflakes">launchpad.net/pyflakes</a></a><br> <a name="pep8_pep"><a href="http://www.python.org/dev/peps/pep-0008/">www.python.org/dev/peps/pep-0008</a></a><br> <a name="pylint"><a href="http://pypi.python.org/pypi/pylint">pypi.python.org/pypi/pylint</a></a><br> <a name="pep8"><a href="http://pypi.python.org/pypi/pep8">pypi.python.org/pypi/pep8</a></a><br> <a name="PyCharm"><a href="http://www.jetbrains.com/pycharm/">www.jetbrains.com/pycharm</a></a><br> <a name="pydev"><a href="http://pydev.org/">pydev.org</a></a><br> <a name="setuptools"><a href="http://pypi.python.org/pypi/setuptools">pypi.python.org/pypi/setuptools</a></a><br> <a name="sublime_text"><a href="http://www.sublimetext.com/">www.sublimetext.com</a></a><br> <a name="KomodoIDE"><a href="http://www.activestate.com/komodo-ide">www.activestate.com/komodo-ide</a></a><br> <a name="pythonanywhere.com"><a href="http://pythonanywhere.com">pythonanywhere.com</a></a><br><p style="text-indent:20px">Исходники этого и других постов со скриптами лежат тут - <a href="https://github.com/koder-ua/python-lectures">github.com/koder-ua</a>. При использовании их, пожалуйста, ссылайтесь на <a href="http://koder-ua.blogspot.com/">koder-ua.blogspot.com</a>.</p>
<script type="text/javascript">
function on_hidabble_click()
{
var me = $(this);
var hide_id = me.attr("objtohide");
var controlled_object = $('#' + hide_id);
controlled_object.toggle();
if ( controlled_object.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".hidder").click(on_hidabble_click);
function on_double_hidabble_click()
{
var me = $(this);
var hide_id1 = me.attr("objtohide1");
var hide_id2 = me.attr("objtohide2");
var controlled_object1 = $('#' + hide_id1);
var controlled_object2 = $('#' + hide_id2);
controlled_object1.toggle();
controlled_object2.toggle();
if ( controlled_object1.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".dhidder").click(on_double_hidabble_click);
</script>
Anonymoushttp://www.blogger.com/profile/06645078574760734586noreply@blogger.com58tag:blogger.com,1999:blog-1174489715777430743.post-2175588931322385632012-03-20T04:11:00.000+02:002012-03-20T15:20:45.378+02:00perf : современный Linux профилировщик<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<div class="document" id="perf-linux">
<p>Иногда возникает необходимость заглянуть внутрь программы,
что-бы посмотреть почему она "так тормозит". Естественно начинается все
с высокоуровневых средств htop/atop/nettop/iotop/sar. Но если они
показывают, что производительность упирается в процессор, то необходимо
знать какие функции требуют больше всего вычислительных мощностей.
Это может помочь как переписать свою программу для провышения
производительности, так и подобрать оптимальные настройки для чужой.</p>
<p>Собственно функция профилировщика - найти узкие места в программе и
выдать нам как можно больше информации о том куда уходят процессорные
такты. Можно выделить три основных модели профилирования:</p>
<p><em>Гарантированное профилирование</em> (determine - не нашел как перевести это лучше).
Целевая программа модифицируется
(чаще всего перекомпилируется с определенными флагами - -pg для gcc).
- в код внедряются вызовы библиотеки профилирования,
собирающие информацию о выполняемой программе и передающие ее
профилировщику. Наиболее часто
замеряется время выполнения каждой функции и после тестового прогона программы
можно увидеть вывод, подобный следующему:</p>
<pre class="literal-block">
% cumulative self self total
time seconds seconds calls ms/call ms/call name
33.34 0.02 0.02 7208 0.00 0.00 open
16.67 0.03 0.01 244 0.04 0.12 offtime
16.67 0.04 0.01 8 1.25 1.25 memccpy
16.67 0.05 0.01 7 1.43 1.43 write
</pre>
<p>Очевидным минусом гарантированного профилирования является необходимость
перекомпиляции программы, что неудобно, а иногда и невозможно (если это
не ваша программа, например). Также
гарантированное профилирование значительно влияет на скорость исполнения
программы, делая получение коректного времени исполнения затруднительным.
Типичный представитель - <a class="reference external" href="http://www.cs.utah.edu/dept/old/texinfo/as/gprof_toc.html">gprof</a>,</p>
<a name='more'></a><p><em>Статистическое профилирование</em> (statistic profiling) - исполнение программы
периодически (~100-1000 раз в секунду)
останавливается, управление передается профилировщику, который анализирует
текущий констекст исполнения.
Затем, используя отладочную информацию или таблици символов из исполняемого файла
программы и загруженных библиотек, определяется исполняемая в текущий момент
функция и, если возможно, последовательность вызовов в стеке.
Профилировщик увеличивает счетчик попаданий для активной функции, а после
окончания профилирования можно увидеть сколько раз программа "была поймана"
на исполнении определенных функций.
Идея очень простая - если в некоторой функции <em>A</em> программа
проводит в два раза больше времени, чем в функции <em>B</em>, то в среднем ( при
достаточно длительном исполнении) мы будем в два раза чаще останавливаться
внутри <em>A</em>, чем внутри <em>B</em>. Статистическое профилирование не требует
перекомпиляции программы (хотя некоторые флаги компилятора могут помочь
восстанавливать стек),
значительно меньше гарантированного профилирования влияет на время и
тайминги исполнения, но требует длительного или многократного прогона программы
что бы получить надежные результаты. Типичный представитель - <a class="reference external" href="http://oprofile.sourceforge.net/news/">oprofile</a>.</p>
<p><em>Профилирование на эмуляторе</em> . Идея состоит в написании эмулятора,
очень точно воспроизводящего целевой процессор, включая кеши со всеми
механизмами замещения, декодировщики инструкций, конвееров и прочего.
В общем случае задача похожа на не решаемую, учитываю сложность
современных процессоров и огромное количество факторов, влияющих на
производительность. Кроме этого профилирования на эмуляторе может занимать
ооочень много времени из за накладных расходов на эмуляцию.
Едиственный известный мне представитель/попытка - <a class="reference external" href="http://valgrind.org/">valgrind</a>, хотя <a class="reference external" href="http://software.intel.com/en-us/articles/intel-vtune-amplifier-xe/">VTune</a> от intel
частично использует подобную технику для анализа кода.</p>
<p>По сумме характеристик статистическое профилирование является самым интересным
(IMHO), посколько достаточно просто реализуется, практически не влияет на исполнение
программы, способно профилировать даже те компоненты, для которых нет исходных текстов
(например внешние библиотеки, линкуемые бинарно или с помощую .so файлов).
Если же значительная нагрузка ложится на ядро или внешние сервисы (например
комбинация из postgresql + python) то оно становится вообще
единственным средством, помогающим понять "кто все съел".</p>
<p>Все серьезные современные процессоры имеют встроенную поддержку стат
профилирования через аппаратные <a class="reference external" href="http://en.wikipedia.org/wiki/Hardware_performance_counter">счетчики событий производительности</a>.
Если кратко то в каждом ядре есть несколько регистров (для core 2 - 7),
которые можно настроить на подсчет определенного типа событий, например
тактов процессора, исполненных инструкций, промахов в кеши, TLB и т.п.</p>
<p>Обычно счетчики настраиваются на вызов прерывания при достижении определенного
значения (например 1M тактов CPU), по входу в прерывание управление передается
модулю профилировщика. В линукс до 2.6.31 поддержка аппаратных счетчиков
была в основном в intel vtune и oprofile.</p>
<p>Большим плюсом счетчиков является возможность понять почему
код работает с наблюдаемой скоростью, посколько они позволяют
разложить время исполнения на более мелкие составляющие.</p>
<p>Начнем с динозавра стат профилирования - oprofile:</p>
<a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="1f1c192e723211e1b1bebc7737dae05d" objtohide2="1f1c7a4a723211e1b1bebc7737dae05d">Без подсветки синтаксиса</a>
<br /><span id="1f1c192e723211e1b1bebc7737dae05d">
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">#</span><span style="color: #408080; font-style: italic"># установим отладочные символы для вывода имен функций</span>
<span style="color: #000080; font-weight: bold">#</span> apt-get install oprofile python2.7-dbg python2.7-dev postgresql-9.1-dbg
<span style="color: #000080; font-weight: bold">#</span> apt-get install libc6-dbg zlib1g-dbg libssl1.0.0-dbg
<span style="color: #000080; font-weight: bold">#</span> запускаем модуль
<span style="color: #000080; font-weight: bold">#</span> opcontrol --init
<span style="color: #000080; font-weight: bold">#</span> <span style="color: #19177C">PYTHON</span><span style="color: #666666">=</span>/usr/bin/python2.7
<span style="color: #000080; font-weight: bold">#</span> <span style="color: #19177C">POSTGRES</span><span style="color: #666666">=</span>/usr/lib/postgresql/9.1/bin/postgres
<span style="color: #000080; font-weight: bold">#</span><span style="color: #408080; font-style: italic"># настраиваем профайлер - не следить за ядром, используем</span>
<span style="color: #000080; font-weight: bold">#</span><span style="color: #408080; font-style: italic"># такты процессора в качестве события профилирования, мониторить только</span>
<span style="color: #000080; font-weight: bold">#</span><span style="color: #408080; font-style: italic"># питон, постгрес и библиотеки в их адресном пространстве</span>
<span style="color: #000080; font-weight: bold">#</span> opcontrol --no-vmlinux --event<span style="color: #666666">=</span>CPU_CLK_UNHALTED:1000000 <span style="color: #BB6622; font-weight: bold">\</span>
<span style="color: #808080"> -i "$PYTHON,$POSTGRES" --separate=lib</span>
<span style="color: #000080; font-weight: bold">#</span><span style="color: #408080; font-style: italic"># инициализируем базу - код теста приведен в конце статьи и лежит на git</span>
<span style="color: #000080; font-weight: bold">#</span> python test_psql.py psql fill 100000
<span style="color: #000080; font-weight: bold">#</span><span style="color: #408080; font-style: italic"># запускаем тест - выполнятеся 10M простейших 'select val from test_table where key=some_val'</span>
<span style="color: #000080; font-weight: bold">#</span> opcontrol --start; python test_psql.py psql <span style="color: #008000">read </span>10000000; opcontrol --stop
<span style="color: #000080; font-weight: bold">#</span> opcontrol --dump
<span style="color: #000080; font-weight: bold">#</span> opcontrol --shutdown
<span style="color: #000080; font-weight: bold">#</span> opcontrol --deinit
<span style="color: #000080; font-weight: bold">#</span> opreport -l | less
</pre></div>
</span>
<span style="line-height:100%;display:none" id="1f1c7a4a723211e1b1bebc7737dae05d">
## установим отладочные символы для вывода имен функций
# apt-get install oprofile python2.7-dbg python2.7-dev postgresql-9.1-dbg
# apt-get install libc6-dbg zlib1g-dbg libssl1.0.0-dbg
# запускаем модуль
# opcontrol --init
# PYTHON=/usr/bin/python2.7
# POSTGRES=/usr/lib/postgresql/9.1/bin/postgres
## настраиваем профайлер - не следить за ядром, используем
## такты процессора в качестве события профилирования, мониторить только
## питон, постгрес и библиотеки в их адресном пространстве
# opcontrol --no-vmlinux --event=CPU_CLK_UNHALTED:1000000 \
-i "$PYTHON,$POSTGRES" --separate=lib
## инициализируем базу - код теста приведен в конце статьи и лежит на git
# python test_psql.py psql fill 100000
## запускаем тест - выполнятеся 10M простейших 'select val from test_table where key=some_val'
# opcontrol --start; python test_psql.py psql read 10000000; opcontrol --stop
# opcontrol --dump
# opcontrol --shutdown
# opcontrol --deinit
# opreport -l | less
</span><p>По итогу получаем следующий вывод:</p>
<pre class="literal-block">
CPU: Intel Architectural Perfmon, speed 2001 MHz (estimated)
Counted CPU_CLK_UNHALTED events (Clock cycles when not halted) with a unit mask of 0x00 (No unit mask) count 1000000
samples % image name app name symbol name
119760 2.0987 postgres postgres AllocSetAlloc
103579 1.8152 libc-2.13.so postgres strcoll_l
100119 1.7545 postgres postgres SearchCatCache
96138 1.6848 libcrypto.so.1.0.0 postgres sha1_block_data_order
93466 1.6379 libcrypto.so.1.0.0 python2.7 sha1_block_data_order
92630 1.6233 postgres postgres base_yyparse
89759 1.5730 libc-2.13.so postgres __memcpy_ssse3_back
71632 1.2553 libc-2.13.so postgres _int_malloc
71214 1.2480 libc-2.13.so python2.7 _int_malloc
70112 1.2287 libz.so.1.2.3.4 python2.7 longest_match
66124 1.1588 libcrypto.so.1.0.0 python2.7 _x86_64_AES_decrypt_compact
64521 1.1307 libcrypto.so.1.0.0 postgres _x86_64_AES_decrypt_compact
64130 1.1238 libc-2.13.so python2.7 __memcpy_ssse3_back
56956 0.9981 libc-2.13.so python2.7 malloc
52380 0.9179 libcrypto.so.1.0.0 postgres _x86_64_AES_encrypt_compact
52216 0.9151 libcrypto.so.1.0.0 postgres EVP_MD_CTX_cleanup
49158 0.8615 libcrypto.so.1.0.0 python2.7 _x86_64_AES_encrypt_compact
47041 0.8244 libz.so.1.2.3.4 python2.7 build_tree
46747 0.8192 libcrypto.so.1.0.0 python2.7 EVP_MD_CTX_cleanup
45194 0.7920 libz.so.1.2.3.4 postgres build_tree
42862 0.7511 libc-2.13.so python2.7 free
</pre>
<p>Итого: основное время системы проводит освобождая/выделяя
память, шифруя передаваемую информацию и разбирая sql запросы.</p>
<p>Несмотря на то, что oprofile достаточно удобен его возможностей не хватало
и длительное время Stephane Eranian разрабатывал <a class="reference external" href="http://lwn.net/Articles/300664/">perfmon3</a> - поддержку счетчиков проиводительности
в ядре linux, которая однако никак не попадала в основное ядро....
А потом неожиданно появился Thomas Gleixner и вездесущий Ingo Molnar, которые предложили
<a class="reference external" href="http://lwn.net/Articles/310176/">perf патч</a> для ядра и одновременно <a class="reference external" href="http://lwn.net/Articles/310176/">закидали конфетами</a> perfmon. Чуть позже
Торвальдс <a class="reference external" href="http://lwn.net/Articles/339361/">закидал теми же конфетами</a> разработчиков oprofile и одобрил включение
утилит пользовательского режима для поддержки <a class="reference external" href="https://perf.wiki.kernel.org">perf</a> подсистемы в состав ядра.</p>
<p>В итоге патч был включен в Linux 2.6.31 и сейчас perf,
IMHO, самая удобная утилита для профилирования бинарного кода в linux.
perf входит в состав пакета <em>linux-tools-common</em> (ubuntu). Те, кто использует
последние версии ядра из .... (к которым нет <em>linux-tools-common</em>) могут
скачать исходники соответвующего ядра с kernel.org, перейти в папку
tools/perf, сделать там make и скопировать полученный perf в
/usr/bin/perf_USED_LINUX_KERNEL_VERSION</p>
<a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="1f1fda46723211e1b1bebc7737dae05d" objtohide2="1f203478723211e1b1bebc7737dae05d">Без подсветки синтаксиса</a>
<br /><span id="1f1fda46723211e1b1bebc7737dae05d">
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">$</span> <span style="color: #008000">cd </span>tools/perf
<span style="color: #000080; font-weight: bold">$</span> make
<span style="color: #000080; font-weight: bold">$</span> sudo cp perf /usr/bin/perf_<span style="color: #BA2121">`</span>uname -r<span style="color: #BA2121">`</span>
</pre></div>
</span>
<span style="line-height:100%;display:none" id="1f203478723211e1b1bebc7737dae05d">
$ cd tools/perf
$ make
$ sudo cp perf /usr/bin/perf_`uname -r`
</span><p>По сравнению с oprofile модуль ядра perf получил:</p>
<ul><li><p class="first">более чистый API</p>
</li>
<li><dl class="first docutils"><dt>больше аппаратных счетчиков</dt>
<dd><p class="first last">(сравните "opcontrol --list-events" и "perf list | grep '[Hardware'")</p>
</dd>
</dl></li>
<li><p class="first">поддержку в структурах ядра (теперь при переключении процессов ядро сохраняет
счетчики для старого процесса в его структуре и загружает в регистры процессора
созраненные счетчики нового процесса - это улочшает результаты профилирования в
многопоточной среде)</p>
</li>
<li><p class="first">програмные счетчики - события из ядра: переключения контекстов,
minor/major page fault, счетчики системных вызовов и интеграцию c <a class="reference external" href="http://sourceware.org/systemtap/">systemtap</a>.</p>
</li>
</ul><p>Но конечных пользователей больше интересуют утилиты пользовательского режима, и тут
много всего полезного ( perf невозможно использовать при загруженном модуле oprofile,
так как они используют одни и те же апаратные регистры. Перед использованием perf
необходимо выгрузить oprofile - "oprofile --deinit" ):</p>
<p><em>perf top</em> - показывает в реальном времени какие функции/процессы в
системе генерируют больше всего нагрузки. Например "perf top"
покажеть распределение нагрузки по функциям. "perf top -e L1-dcache-loads" покажет
кто хуже всех относится к кешу процессора "perf top -e dTLB-load-misses,iTLB-load-misses"
покажет промахи в <a class="reference external" href="http://en.wikipedia.org/wiki/Translation_lookaside_buffer">TLB</a> и позволит оценить имеет ли смысл переключаться на
<a class="reference external" href="http://en.wikipedia.org/wiki/Page_%28computer_memory%29#Huge_pages">огромные страници</a>.</p>
<p><em>perf stat cmd</em> генерирует отчет по отдельной команде - аналог утилиты time.</p>
<a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="1f223cdc723211e1b1bebc7737dae05d" objtohide2="1f2242cc723211e1b1bebc7737dae05d">Без подсветки синтаксиса</a>
<br /><span id="1f223cdc723211e1b1bebc7737dae05d">
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">$</span> perf stat ps
<span style="color: #808080"> PID TTY TIME CMD</span>
<span style="color: #808080">19784 pts/2 00:00:00 sudo</span>
<span style="color: #808080">19785 pts/2 00:00:00 perf</span>
<span style="color: #808080">19786 pts/2 00:00:00 ps</span>
<span style="color: #808080"> Performance counter stats for 'ps':</span>
<span style="color: #808080"> 13.633273 task-clock # 0.962 CPUs utilized</span>
<span style="color: #808080"> 9 context-switches # 0.001 M/sec</span>
<span style="color: #808080"> 1 CPU-migrations # 0.000 M/sec</span>
<span style="color: #808080"> 284 page-faults # 0.021 M/sec</span>
<span style="color: #808080"> 21,595,618 cycles # 1.584 GHz [70.79%]</span>
<span style="color: #808080"> 10,181,093 stalled-cycles-frontend # 47.14% frontend cycles idle [70.90%]</span>
<span style="color: #808080"> 6,232,603 stalled-cycles-backend # 28.86% backend cycles idle [80.06%]</span>
<span style="color: #808080"> 31,611,007 instructions # 1.46 insns per cycle</span>
<span style="color: #808080"> # 0.32 stalled cycles per insn</span>
<span style="color: #808080"> 6,798,332 branches # 498.657 M/sec</span>
<span style="color: #808080"> 60,662 branch-misses # 0.89% of all branches [79.08%]</span>
<span style="color: #808080"> 0.014176162 seconds time elapsed</span>
</pre></div>
</span>
<span style="line-height:100%;display:none" id="1f2242cc723211e1b1bebc7737dae05d">
$ perf stat ps
PID TTY TIME CMD
19784 pts/2 00:00:00 sudo
19785 pts/2 00:00:00 perf
19786 pts/2 00:00:00 ps
Performance counter stats for 'ps':
13.633273 task-clock # 0.962 CPUs utilized
9 context-switches # 0.001 M/sec
1 CPU-migrations # 0.000 M/sec
284 page-faults # 0.021 M/sec
21,595,618 cycles # 1.584 GHz [70.79%]
10,181,093 stalled-cycles-frontend # 47.14% frontend cycles idle [70.90%]
6,232,603 stalled-cycles-backend # 28.86% backend cycles idle [80.06%]
31,611,007 instructions # 1.46 insns per cycle
# 0.32 stalled cycles per insn
6,798,332 branches # 498.657 M/sec
60,662 branch-misses # 0.89% of all branches [79.08%]
0.014176162 seconds time elapsed
</span><p><em>perf record cmd</em> исполняет программу и сохраняет итог профилирования
в perf.dat, "perf report" позволит его просмотреть, а "perf annotate"
покажет распределение событий по строкам исходного текста или по ассемблерному листингу.</p>
<p>Также perf "знает" о потоках, позволяя просматривать события для каждого потока в отдельности и
умеет использовать встроенные в процессор средства тонкой настройки работы счетчиков.</p>
<p>В отличии от oprofile я не нашел как заставить perf записывать события только для некоторого
набора бинарных файлов, но есть возможность поместить все необходимые
процессы в отдельную группу(<a class="reference external" href="http://koder-ua.blogspot.com/2012/02/linux.html">cgroup</a>) и мониторить только ее.</p>
<p>Возможности perf на этом не исчерпываются, "perf help" покажет много полезных
инструментов.</p>
<p>Пример использования perf с тем же тестом, что и oprofile:</p>
<a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="1f22bb8a723211e1b1bebc7737dae05d" objtohide2="1f22bd10723211e1b1bebc7737dae05d">Без подсветки синтаксиса</a>
<br /><span id="1f22bb8a723211e1b1bebc7737dae05d">
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">$</span> perf record -e cycles -c 1000000 python test_psql.py psql <span style="color: #008000">read </span>10000000
<span style="color: #000080; font-weight: bold">#</span><span style="color: #408080; font-style: italic"># просыпаемся раз в 1М тактов (~2к раз в секунду)</span>
<span style="color: #808080">............</span>
<span style="color: #000080; font-weight: bold">$</span> perf report
</pre></div>
</span>
<span style="line-height:100%;display:none" id="1f22bd10723211e1b1bebc7737dae05d">
$ perf record -e cycles -c 1000000 python test_psql.py psql read 10000000
## просыпаемся раз в 1М тактов (~2к раз в секунду)
............
$ perf report
</span><pre class="literal-block">
1.59% postgres postgres [.] AllocSetAlloc
1.46% postgres libc-2.13.so [.] __strcoll_l
1.35% postgres postgres [.] SearchCatCache
1.25% postgres libcrypto.so.1.0.0 [.] sha1_block_data_order
1.24% python libcrypto.so.1.0.0 [.] sha1_block_data_order
1.19% postgres libc-2.13.so [.] __memcpy_ssse3_back
1.13% postgres postgres [.] base_yyparse
0.94% python libz.so.1.2.3.4 [.] longest_match
0.94% postgres libc-2.13.so [.] _int_malloc
0.91% python libc-2.13.so [.] _int_malloc
0.87% python libcrypto.so.1.0.0 [.] _x86_64_AES_decrypt_compact
0.85% postgres libcrypto.so.1.0.0 [.] _x86_64_AES_decrypt_compact
0.82% python libc-2.13.so [.] __memcpy_ssse3_back
0.78% python libc-2.13.so [.] malloc
0.69% postgres libcrypto.so.1.0.0 [.] _x86_64_AES_encrypt_compact
0.67% postgres libcrypto.so.1.0.0 [.] EVP_MD_CTX_cleanup
0.67% python libcrypto.so.1.0.0 [.] _x86_64_AES_encrypt_compact
0.66% python libz.so.1.2.3.4 [.] build_tree
0.63% python libcrypto.so.1.0.0 [.] EVP_MD_CTX_cleanup
0.60% postgres libz.so.1.2.3.4 [.] build_tree
0.59% python libc-2.13.so [.] free
0.54% postgres postgres [.] hash_search_with_hash_value
0.53% postgres libz.so.1.2.3.4 [.] _tr_flush_block
0.52% python libz.so.1.2.3.4 [.] _tr_flush_block
0.50% python python2.7 [.] convertitem
.......
</pre>
<p>О накладных расходах: perf замедлял тест примерно на ~0.5%, а oprofile на 2-5%.
Увеличивая порог срабатывания события можно уменьшить влияние профилировшика,
но прийдется соответвенно удлиннить тесты для
сохранения качества результатов.</p>
<p>В итоге perf дейсвительно Замечательный Инструмент, который позволяет не только профилировать
свои программы но и практически незаметно мониторить что происходит в живой системе.</p>
<p>Исходники утилиты для тестов(требуют библиотеку <a class="reference external" href="http://pypi.python.org/pypi/futures">futures</a>):</p>
<a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="1f278886723211e1b1bebc7737dae05d" objtohide2="1f278a3e723211e1b1bebc7737dae05d">Без подсветки синтаксиса</a>
<br /><span id="1f278886723211e1b1bebc7737dae05d">
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">sys</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">time</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">Queue</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">futures</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">psycopg2</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">traceback</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">multiprocessing</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">exec_opers</span>(test_class, start, stop, res_q, ops):
<span style="color: #BA2121; font-style: italic">"""</span>
<span style="color: #BA2121; font-style: italic"> Split test on chunks and put chunk size to result queue</span>
<span style="color: #BA2121; font-style: italic"> when chunk execution finished</span>
<span style="color: #BA2121; font-style: italic"> """</span>
<span style="color: #008000; font-weight: bold">try</span>:
test_obj <span style="color: #666666">=</span> test_class()
stops <span style="color: #666666">=</span> <span style="color: #008000">range</span>(start, stop, ops)[<span style="color: #666666">1</span>:] <span style="color: #666666">+</span> [stop]
curr_start <span style="color: #666666">=</span> start
<span style="color: #008000; font-weight: bold">for</span> curr_stop <span style="color: #AA22FF; font-weight: bold">in</span> stops:
test_obj<span style="color: #666666">.</span>run(curr_start, curr_stop)
res_q<span style="color: #666666">.</span>put(curr_stop <span style="color: #666666">-</span> curr_start)
<span style="color: #008000; font-weight: bold">except</span> <span style="color: #D2413A; font-weight: bold">Exception</span>, x:
traceback<span style="color: #666666">.</span>print_exc()
<span style="color: #008000; font-weight: bold">raise</span>
<span style="color: #008000; font-weight: bold">finally</span>:
<span style="color: #408080; font-style: italic"># None mean thread finish work</span>
res_q<span style="color: #666666">.</span>put(<span style="color: #008000">None</span>)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">do_tests</span>(test_class, size, ops, th_count<span style="color: #666666">=</span><span style="color: #008000">None</span>):
<span style="color: #BA2121">"run some amount of tests in separated threads"</span>
<span style="color: #008000; font-weight: bold">if</span> th_count <span style="color: #AA22FF; font-weight: bold">is</span> <span style="color: #008000">None</span>:
th_count <span style="color: #666666">=</span> multiprocessing<span style="color: #666666">.</span>cpu_count()
res_q <span style="color: #666666">=</span> Queue<span style="color: #666666">.</span>Queue()
num_per_thread <span style="color: #666666">=</span> size <span style="color: #666666">//</span> th_count
<span style="color: #008000; font-weight: bold">with</span> futures<span style="color: #666666">.</span>ThreadPoolExecutor(max_workers<span style="color: #666666">=</span>th_count) <span style="color: #008000; font-weight: bold">as</span> executor:
vals <span style="color: #666666">=</span> [(i <span style="color: #666666">*</span> num_per_thread,
(i <span style="color: #666666">+</span> <span style="color: #666666">1</span>) <span style="color: #666666">*</span> num_per_thread)
<span style="color: #008000; font-weight: bold">for</span> i <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">range</span>(th_count)]
<span style="color: #008000; font-weight: bold">for</span> start, stop <span style="color: #AA22FF; font-weight: bold">in</span> vals:
executor<span style="color: #666666">.</span>submit(exec_opers, test_class, start, stop, res_q, ops)
left <span style="color: #666666">=</span> th_count
done <span style="color: #666666">=</span> <span style="color: #666666">0</span>
ppers <span style="color: #666666">=</span> <span style="color: #666666">0</span>
pval <span style="color: #666666">=</span> <span style="color: #666666">0</span>
ptime <span style="color: #666666">=</span> time<span style="color: #666666">.</span>time()
PERC_PER_STEP <span style="color: #666666">=</span> <span style="color: #666666">10</span>
<span style="color: #008000; font-weight: bold">while</span> left <span style="color: #666666">!=</span> <span style="color: #666666">0</span>:
<span style="color: #008000; font-weight: bold">try</span>:
val <span style="color: #666666">=</span> res_q<span style="color: #666666">.</span>get(timeout<span style="color: #666666">=0.1</span>)
<span style="color: #008000; font-weight: bold">if</span> val <span style="color: #AA22FF; font-weight: bold">is</span> <span style="color: #008000">None</span>:
left <span style="color: #666666">-=</span> <span style="color: #666666">1</span>
<span style="color: #008000; font-weight: bold">else</span>:
done <span style="color: #666666">+=</span> val
np <span style="color: #666666">=</span> <span style="color: #008000">int</span>(done <span style="color: #666666">*</span> (<span style="color: #666666">100</span> <span style="color: #666666">/</span> PERC_PER_STEP) <span style="color: #666666">/</span> size)
<span style="color: #008000; font-weight: bold">if</span> np <span style="color: #666666">!=</span> ppers:
ntime <span style="color: #666666">=</span> time<span style="color: #666666">.</span>time()
<span style="color: #008000; font-weight: bold">if</span> ntime <span style="color: #666666">-</span> ptime <span style="color: #666666"><</span> <span style="color: #666666">1E-3</span>:
speed <span style="color: #666666">=</span> <span style="color: #BA2121">"Too fast"</span>
<span style="color: #008000; font-weight: bold">else</span>:
speed <span style="color: #666666">=</span> <span style="color: #008000">int</span>((done <span style="color: #666666">-</span> pval) <span style="color: #666666">/</span> (ntime <span style="color: #666666">-</span> ptime))
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"{0}</span><span style="color: #BB6688; font-weight: bold">% d</span><span style="color: #BA2121">one. Performance - {1} ops/sec"</span><span style="color: #666666">.</span>format(
<span style="color: #008000">int</span>(done <span style="color: #666666">*</span> <span style="color: #666666">100</span> <span style="color: #666666">/</span> size), speed)
ptime <span style="color: #666666">=</span> ntime
pval <span style="color: #666666">=</span> done
ppers <span style="color: #666666">=</span> np
<span style="color: #008000; font-weight: bold">except</span> Queue<span style="color: #666666">.</span>Empty:
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">PSQLBase</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>):
<span style="color: #008000">self</span><span style="color: #666666">.</span>conn, <span style="color: #008000">self</span><span style="color: #666666">.</span>curr <span style="color: #666666">=</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>conn_and_cursor()
<span style="color: #008000">self</span><span style="color: #666666">.</span>curr<span style="color: #666666">.</span>execute(<span style="color: #BA2121">"select count(*) from test_table"</span>)
<span style="color: #008000">self</span><span style="color: #666666">.</span>db_sz <span style="color: #666666">=</span> <span style="color: #008000">int</span>(<span style="color: #008000">self</span><span style="color: #666666">.</span>curr<span style="color: #666666">.</span>fetchone()[<span style="color: #666666">0</span>])
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__del__</span>(<span style="color: #008000">self</span>):
<span style="color: #008000">self</span><span style="color: #666666">.</span>curr<span style="color: #666666">.</span>close()
<span style="color: #008000">self</span><span style="color: #666666">.</span>conn<span style="color: #666666">.</span>close()
<span style="color: #AA22FF">@classmethod</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">conn_and_cursor</span>(cls):
conn <span style="color: #666666">=</span> psycopg2<span style="color: #666666">.</span>connect(<span style="color: #BA2121">"host=localhost dbname=test user=test password=test"</span>)
<span style="color: #008000; font-weight: bold">return</span> conn, conn<span style="color: #666666">.</span>cursor()
<span style="color: #AA22FF">@classmethod</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">clear_data</span>(cls):
conn, curr <span style="color: #666666">=</span> cls<span style="color: #666666">.</span>conn_and_cursor()
curr<span style="color: #666666">.</span>execute(<span style="color: #BA2121">"delete from test_table"</span>)
conn<span style="color: #666666">.</span>commit()
curr<span style="color: #666666">.</span>close()
conn<span style="color: #666666">.</span>close()
<span style="color: #AA22FF">@classmethod</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">insert_data</span>(cls, sz, step<span style="color: #666666">=1000</span>):
conn, curr <span style="color: #666666">=</span> cls<span style="color: #666666">.</span>conn_and_cursor()
<span style="color: #008000; font-weight: bold">for</span> i <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">range</span>(sz <span style="color: #666666">/</span> step):
vals <span style="color: #666666">=</span> [(<span style="color: #008000">str</span>(i <span style="color: #666666">*</span> step <span style="color: #666666">+</span> j), <span style="color: #BA2121">'test_data'</span>) <span style="color: #008000; font-weight: bold">for</span> j <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">range</span>(step)]
curr<span style="color: #666666">.</span>executemany(<span style="color: #BA2121">"insert into test_table (key, val) values (</span><span style="color: #BB6688; font-weight: bold">%s</span><span style="color: #BA2121">, </span><span style="color: #BB6688; font-weight: bold">%s</span><span style="color: #BA2121">)"</span>, vals)
conn<span style="color: #666666">.</span>commit()
curr<span style="color: #666666">.</span>close()
conn<span style="color: #666666">.</span>close()
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">PSQLRead</span>(PSQLBase):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">run</span>(<span style="color: #008000">self</span>, start, stop):
<span style="color: #008000; font-weight: bold">for</span> i <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">xrange</span>(start, stop):
<span style="color: #008000">self</span><span style="color: #666666">.</span>curr<span style="color: #666666">.</span>execute(<span style="color: #BA2121">"select val from test_table where key=</span><span style="color: #BB6688; font-weight: bold">%s</span><span style="color: #BA2121">"</span>, (<span style="color: #008000">str</span>(i <span style="color: #666666">%</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>db_sz),))
<span style="color: #008000">self</span><span style="color: #666666">.</span>curr<span style="color: #666666">.</span>fetchall()
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">PSQLWrite</span>(PSQLBase):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">run</span>(<span style="color: #008000">self</span>, start, stop):
<span style="color: #008000; font-weight: bold">for</span> i <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">xrange</span>(start, stop):
<span style="color: #008000">self</span><span style="color: #666666">.</span>curr<span style="color: #666666">.</span>execute(<span style="color: #BA2121">"insert into test_table (key, val) values (</span><span style="color: #BB6688; font-weight: bold">%s</span><span style="color: #BA2121">, </span><span style="color: #BB6688; font-weight: bold">%s</span><span style="color: #BA2121">)"</span>, (<span style="color: #008000">str</span>(i), <span style="color: #BA2121">'test data'</span>))
<span style="color: #008000">self</span><span style="color: #666666">.</span>conn<span style="color: #666666">.</span>commit()
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">main</span>(argv<span style="color: #666666">=</span><span style="color: #008000">None</span>):
<span style="color: #008000; font-weight: bold">if</span> argv <span style="color: #AA22FF; font-weight: bold">is</span> <span style="color: #008000">None</span>:
argv <span style="color: #666666">=</span> sys<span style="color: #666666">.</span>argv[<span style="color: #666666">1</span>:]
tp <span style="color: #666666">=</span> argv[<span style="color: #666666">0</span>]
<span style="color: #008000; font-weight: bold">if</span> tp <span style="color: #666666">==</span> <span style="color: #BA2121">'psql'</span>:
Base, R, W <span style="color: #666666">=</span> PSQLBase, PSQLRead, PSQLWrite
argv <span style="color: #666666">=</span> argv[<span style="color: #666666">1</span>:]
<span style="color: #008000; font-weight: bold">if</span> argv[<span style="color: #666666">0</span>] <span style="color: #666666">!=</span> <span style="color: #BA2121">'clear'</span>:
sz <span style="color: #666666">=</span> <span style="color: #008000">int</span>(argv[<span style="color: #666666">1</span>])
<span style="color: #008000; font-weight: bold">if</span> argv[<span style="color: #666666">0</span>] <span style="color: #666666">==</span> <span style="color: #BA2121">'clear'</span>:
Base<span style="color: #666666">.</span>clear_data()
<span style="color: #008000; font-weight: bold">elif</span> argv[<span style="color: #666666">0</span>] <span style="color: #666666">==</span> <span style="color: #BA2121">'fill'</span>:
Base<span style="color: #666666">.</span>insert_data(sz)
<span style="color: #008000; font-weight: bold">elif</span> argv[<span style="color: #666666">0</span>] <span style="color: #666666">==</span> <span style="color: #BA2121">'read'</span>:
do_tests(R, sz, <span style="color: #666666">1000</span>)
<span style="color: #008000; font-weight: bold">elif</span> argv[<span style="color: #666666">0</span>] <span style="color: #666666">==</span> <span style="color: #BA2121">'write'</span>:
do_tests(W, sz, <span style="color: #666666">50</span>)
<span style="color: #008000; font-weight: bold">else</span>:
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"Unknown cmd '</span><span style="color: #BB6688; font-weight: bold">%s</span><span style="color: #BA2121">'"</span> <span style="color: #666666">%</span> argv[<span style="color: #666666">0</span>]
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #666666">1</span>
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #666666">0</span>
<span style="color: #008000; font-weight: bold">if</span> __name__ <span style="color: #666666">==</span> <span style="color: #BA2121">"__main__"</span>:
sys<span style="color: #666666">.</span>exit(main(sys<span style="color: #666666">.</span>argv[<span style="color: #666666">1</span>:]))
</pre></div>
</span>
<span style="line-height:100%;display:none" id="1f278a3e723211e1b1bebc7737dae05d">
import sys
import time
import Queue
import futures
import psycopg2
import traceback
import multiprocessing
def exec_opers(test_class, start, stop, res_q, ops):
"""
Split test on chunks and put chunk size to result queue
when chunk execution finished
"""
try:
test_obj = test_class()
stops = range(start, stop, ops)[1:] + [stop]
curr_start = start
for curr_stop in stops:
test_obj.run(curr_start, curr_stop)
res_q.put(curr_stop - curr_start)
except Exception, x:
traceback.print_exc()
raise
finally:
# None mean thread finish work
res_q.put(None)
def do_tests(test_class, size, ops, th_count=None):
"run some amount of tests in separated threads"
if th_count is None:
th_count = multiprocessing.cpu_count()
res_q = Queue.Queue()
num_per_thread = size // th_count
with futures.ThreadPoolExecutor(max_workers=th_count) as executor:
vals = [(i * num_per_thread,
(i + 1) * num_per_thread)
for i in range(th_count)]
for start, stop in vals:
executor.submit(exec_opers, test_class, start, stop, res_q, ops)
left = th_count
done = 0
ppers = 0
pval = 0
ptime = time.time()
PERC_PER_STEP = 10
while left != 0:
try:
val = res_q.get(timeout=0.1)
if val is None:
left -= 1
else:
done += val
np = int(done * (100 / PERC_PER_STEP) / size)
if np != ppers:
ntime = time.time()
if ntime - ptime < 1E-3:
speed = "Too fast"
else:
speed = int((done - pval) / (ntime - ptime))
print "{0}% done. Performance - {1} ops/sec".format(
int(done * 100 / size), speed)
ptime = ntime
pval = done
ppers = np
except Queue.Empty:
pass
class PSQLBase(object):
def __init__(self):
self.conn, self.curr = self.conn_and_cursor()
self.curr.execute("select count(*) from test_table")
self.db_sz = int(self.curr.fetchone()[0])
def __del__(self):
self.curr.close()
self.conn.close()
@classmethod
def conn_and_cursor(cls):
conn = psycopg2.connect("host=localhost dbname=test user=test password=test")
return conn, conn.cursor()
@classmethod
def clear_data(cls):
conn, curr = cls.conn_and_cursor()
curr.execute("delete from test_table")
conn.commit()
curr.close()
conn.close()
@classmethod
def insert_data(cls, sz, step=1000):
conn, curr = cls.conn_and_cursor()
for i in range(sz / step):
vals = [(str(i * step + j), 'test_data') for j in range(step)]
curr.executemany("insert into test_table (key, val) values (%s, %s)", vals)
conn.commit()
curr.close()
conn.close()
class PSQLRead(PSQLBase):
def run(self, start, stop):
for i in xrange(start, stop):
self.curr.execute("select val from test_table where key=%s", (str(i % self.db_sz),))
self.curr.fetchall()
class PSQLWrite(PSQLBase):
def run(self, start, stop):
for i in xrange(start, stop):
self.curr.execute("insert into test_table (key, val) values (%s, %s)", (str(i), 'test data'))
self.conn.commit()
def main(argv=None):
if argv is None:
argv = sys.argv[1:]
tp = argv[0]
if tp == 'psql':
Base, R, W = PSQLBase, PSQLRead, PSQLWrite
argv = argv[1:]
if argv[0] != 'clear':
sz = int(argv[1])
if argv[0] == 'clear':
Base.clear_data()
elif argv[0] == 'fill':
Base.insert_data(sz)
elif argv[0] == 'read':
do_tests(R, sz, 1000)
elif argv[0] == 'write':
do_tests(W, sz, 50)
else:
print "Unknown cmd '%s'" % argv[0]
return 1
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
</span>
<p>И скрипта для профилирования:</p>
<a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="1f2817a6723211e1b1bebc7737dae05d" objtohide2="1f281922723211e1b1bebc7737dae05d">Без подсветки синтаксиса</a>
<br /><span id="1f2817a6723211e1b1bebc7737dae05d">
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #408080; font-style: italic">#!/bin/bash</span>
<span style="color: #008000">set</span> -x
<span style="color: #19177C">PROFILER</span><span style="color: #666666">=</span>perf
<span style="color: #19177C">PYTHON</span><span style="color: #666666">=</span>/usr/bin/python2.7
<span style="color: #19177C">POSTGRES</span><span style="color: #666666">=</span>/usr/lib/postgresql/9.1/bin/postgres
<span style="color: #19177C">OK</span><span style="color: #666666">=</span>0
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #666666">[</span> <span style="color: #BA2121">"$PROFILER"</span> <span style="color: #666666">==</span> <span style="color: #BA2121">"oprofile"</span> <span style="color: #666666">]</span> ; <span style="color: #008000; font-weight: bold">then</span>
<span style="color: #008000; font-weight: bold"> </span><span style="color: #19177C">OK</span><span style="color: #666666">=</span>1
opcontrol --init
opcontrol --reset
opcontrol --callgraph<span style="color: #666666">=</span>10 --no-vmlinux --event<span style="color: #666666">=</span>CPU_CLK_UNHALTED:1000000 -i <span style="color: #BA2121">"$PYTHON,$POSTGRES"</span> --separate<span style="color: #666666">=</span>lib
opcontrol --start
<span style="color: #008000; font-weight: bold">fi</span>
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #666666">[</span> <span style="color: #BA2121">"$PROFILER"</span> <span style="color: #666666">==</span> <span style="color: #BA2121">"perf"</span> <span style="color: #666666">]</span> ; <span style="color: #008000; font-weight: bold">then</span>
<span style="color: #008000; font-weight: bold"> </span><span style="color: #19177C">OK</span><span style="color: #666666">=</span>1
rm perf.data perf.data.old 2>&1 >/dev/null
<span style="color: #008000; font-weight: bold">fi</span>
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #666666">[</span> <span style="color: #BA2121">"0"</span> <span style="color: #666666">==</span> <span style="color: #BA2121">"$OK"</span> <span style="color: #666666">]</span> ; <span style="color: #008000; font-weight: bold">then</span>
<span style="color: #008000; font-weight: bold"> </span><span style="color: #008000">echo</span> <span style="color: #BA2121">"Unknown profiler $PROFILER"</span> >&2
<span style="color: #008000">exit </span>1
<span style="color: #008000; font-weight: bold">fi</span>
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #666666">[</span> <span style="color: #BA2121">"$PROFILER"</span> <span style="color: #666666">==</span> <span style="color: #BA2121">"oprofile"</span> <span style="color: #666666">]</span> ; <span style="color: #008000; font-weight: bold">then</span>
<span style="color: #19177C">$PYTHON</span> test_psql.py <span style="color: #19177C">$@</span>
<span style="color: #008000; font-weight: bold">else</span>
<span style="color: #008000; font-weight: bold"> </span>perf record -a -g <span style="color: #19177C">$PYTHON</span> test_psql.py <span style="color: #19177C">$@</span>
<span style="color: #008000; font-weight: bold">fi</span>
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #666666">[</span> <span style="color: #BA2121">"$PROFILER"</span> <span style="color: #666666">==</span> <span style="color: #BA2121">"oprofile"</span> <span style="color: #666666">]</span> ; <span style="color: #008000; font-weight: bold">then</span>
<span style="color: #008000; font-weight: bold"> </span>opcontrol --dump
opcontrol --shutdown
opcontrol --deinit
<span style="color: #008000; font-weight: bold">fi</span>
</pre></div>
</span>
<span style="line-height:100%;display:none" id="1f281922723211e1b1bebc7737dae05d">
#!/bin/bash
set -x
PROFILER=perf
PYTHON=/usr/bin/python2.7
POSTGRES=/usr/lib/postgresql/9.1/bin/postgres
OK=0
if [ "$PROFILER" == "oprofile" ] ; then
OK=1
opcontrol --init
opcontrol --reset
opcontrol --callgraph=10 --no-vmlinux --event=CPU_CLK_UNHALTED:1000000 -i "$PYTHON,$POSTGRES" --separate=lib
opcontrol --start
fi
if [ "$PROFILER" == "perf" ] ; then
OK=1
rm perf.data perf.data.old 2>&1 >/dev/null
fi
if [ "0" == "$OK" ] ; then
echo "Unknown profiler $PROFILER" >&2
exit 1
fi
if [ "$PROFILER" == "oprofile" ] ; then
$PYTHON test_psql.py $@
else
perf record -a -g $PYTHON test_psql.py $@
fi
if [ "$PROFILER" == "oprofile" ] ; then
opcontrol --dump
opcontrol --shutdown
opcontrol --deinit
fi
</span><p>P.S. Приведенная утилита ни в коем случае не предназначается в текущем варианте для тестов
PostgreSQL или чего либо еще. Ее единственная задача - показать работу профилировщика.</p>
<p>Исходники этого и других постов со скриптами лежат тут - <a class="reference external" href="https://github.com/koder-ua/python-lectures">my blog at github</a>
При использовании их, пожалуйста, ссылайтесь на <a class="reference external" href="http://koder-ua.blogspot.com/">koder-ua.blogspot.com</a></p>
</span></div>
<script type="text/javascript">
function on_hidabble_click()
{
var me = $(this);
var hide_id = me.attr("objtohide");
var controlled_object = $('#' + hide_id);
controlled_object.toggle();
if ( controlled_object.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".hidder").click(on_hidabble_click);
function on_double_hidabble_click()
{
var me = $(this);
var hide_id1 = me.attr("objtohide1");
var hide_id2 = me.attr("objtohide2");
var controlled_object1 = $('#' + hide_id1);
var controlled_object2 = $('#' + hide_id2);
controlled_object1.toggle();
controlled_object2.toggle();
if ( controlled_object1.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".dhidder").click(on_double_hidabble_click);
</script>
Anonymoushttp://www.blogger.com/profile/06645078574760734586noreply@blogger.com4tag:blogger.com,1999:blog-1174489715777430743.post-10828852822106824882012-02-21T04:58:00.000+02:002012-02-21T04:58:01.501+02:00Генерируем внешние API по-питоновски<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<div class="document" id="api">
<p>В python есть негласное правило - никогда не повторяйся.
Чаще всего если в программе приходиться писать почти одно и то-же два раза, значит
вы что-то сделали не так. Я приведу пример, как можно
автоматизировать генерацию внешних API таким образом, что
достаточно будет в одном месте в удобной и универсальной форме
описать поддерживаемые вызовы, а все внешнее API для этих
вызовов сделает написаный один раз код.</p>
<p>Итак мы пишем серверный компонент программы, который должен
контролироваться внешними утилитами. Типичные варианты управления:</p>
<ul class="simple"><li>CLI - административный интерфейс командной строки, так-же удобен для разработки</li>
<li>REST - для других языков, WebUI & Co</li>
<li>RCP в каком-то виде (thrift, PyRo, etc)</li>
</ul><p>Нам нужна библиотека, которая позволит один раз задать интерфейсы API функций,
сгенерирует по ним интерфейсы для всех внешних API, будет автоматически
проверять входящие параметры и сделает удобочитаемую документацию.
Для начала хватит.</p>
<a name='more'></a><p>Любая библиотека проектируется отталкиваясь от примеров ее использования.</p>
<a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="bb47f7e85c3711e19705bc7737dae05d" objtohide2="bb4842985c3711e19705bc7737dae05d">Без подсветки синтаксиса</a>
<br /><span id="bb47f7e85c3711e19705bc7737dae05d">
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Add</span>(APICallBase):
<span style="color: #BA2121">"Add two integers"</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Params</span>(<span style="color: #008000">object</span>):
params <span style="color: #666666">=</span> Param([<span style="color: #008000">int</span>], <span style="color: #BA2121">"list of integers to make a sum"</span>)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">execute</span>(<span style="color: #008000">self</span>):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">sum</span>(<span style="color: #008000">self</span><span style="color: #666666">.</span>params)
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Sub</span>(APICallBase):
<span style="color: #BA2121">"Substitute two integers"</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Params</span>(<span style="color: #008000">object</span>):
params <span style="color: #666666">=</span> Param((<span style="color: #008000">int</span>, <span style="color: #008000">int</span>), <span style="color: #BA2121">"substitute second int from first"</span>)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">execute</span>(<span style="color: #008000">self</span>):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>params[<span style="color: #666666">0</span>] <span style="color: #666666">-</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>params[<span style="color: #666666">1</span>]
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Ping</span>(APICallBase):
<span style="color: #BA2121">"Ping host"</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Params</span>(<span style="color: #008000">object</span>):
ip <span style="color: #666666">=</span> Param(IPAddr, <span style="color: #BA2121">"ip addr to ping"</span>)
num_pings <span style="color: #666666">=</span> Param(<span style="color: #008000">int</span>, <span style="color: #BA2121">"number of pings"</span>, default<span style="color: #666666">=3</span>)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">execute</span>(<span style="color: #008000">self</span>):
res <span style="color: #666666">=</span> subprocess<span style="color: #666666">.</span>check_stdout(<span style="color: #BA2121">'ping -c {0} {1}'</span><span style="color: #666666">.</span>format(<span style="color: #008000">self</span><span style="color: #666666">.</span>num_pings,
<span style="color: #008000">self</span><span style="color: #666666">.</span>ip))
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">sum</span>(<span style="color: #008000">map</span>(<span style="color: #008000">float</span>, re<span style="color: #666666">.</span>findall(<span style="color: #BA2121">r'time=(\d+\.?\d*)'</span>, out))) <span style="color: #666666">/</span> \
<span style="color: #008000">self</span><span style="color: #666666">.</span>num_pings
</pre></div>
</span>
<span style="line-height:100%;display:none" id="bb4842985c3711e19705bc7737dae05d">
class Add(APICallBase):
"Add two integers"
class Params(object):
params = Param([int], "list of integers to make a sum")
def execute(self):
return sum(self.params)
class Sub(APICallBase):
"Substitute two integers"
class Params(object):
params = Param((int, int), "substitute second int from first")
def execute(self):
return self.params[0] - self.params[1]
class Ping(APICallBase):
"Ping host"
class Params(object):
ip = Param(IPAddr, "ip addr to ping")
num_pings = Param(int, "number of pings", default=3)
def execute(self):
res = subprocess.check_stdout('ping -c {0} {1}'.format(self.num_pings,
self.ip))
return sum(map(float, re.findall(r'time=(\d+\.?\d*)', out))) / \
self.num_pings
</span><p>Это желаемое описание API. Каждый API вызов наследует класс <strong>APICallBase</strong>,
определяет внутренний класс <strong>Params</strong>, где экземплярами класса <strong>Param</strong> описывает
параметры вызова и перегружает вызов <strong>execute</strong>, в котором выполняется вся работа.
Этой информации более чем достаточно, что-бы сгенерировать все API
и документацию пользуясь интроспекцией и генерацией объектов на лету.</p>
<p>Начнем с базы - нужно уметь находить все классы, унаследованные от <strong>APICallBase</strong>.
Это можно сделать через <a class="reference external" href="http://koder-ua.blogspot.com/2011/12/blog-post.html">метаклассы</a></p>
<a hided_text="Показать код" visible_text="Скрыть код" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="hidder" objtohide="bb4bfb685c3711e19705bc7737dae05d">Показать код</a><br /><span style="display:none" id="bb4bfb685c3711e19705bc7737dae05d">
<a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="bb4c4f325c3711e19705bc7737dae05d" objtohide2="bb4ca1085c3711e19705bc7737dae05d">Без подсветки синтаксиса</a>
<br /><span id="bb4c4f325c3711e19705bc7737dae05d">
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">APIMeta</span>(<span style="color: #008000">type</span>):
<span style="color: #408080; font-style: italic"># список всех API вызовов</span>
api_classes <span style="color: #666666">=</span> []
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__new__</span>(cls, name, bases, clsdict):
new_cls <span style="color: #666666">=</span> <span style="color: #008000">super</span>(APIMeta, cls)<span style="color: #666666">.</span>__new__(cls, name, bases, clsdict)
<span style="color: #408080; font-style: italic"># пропускаем APICallBase</span>
<span style="color: #008000; font-weight: bold">if</span> name <span style="color: #666666">!=</span> <span style="color: #BA2121">'APICallBase'</span>:
<span style="color: #008000">self</span><span style="color: #666666">.</span>api_classes<span style="color: #666666">.</span>append(new_cls)
<span style="color: #408080; font-style: italic"># all_params итерирует по всем параметрам этого вызова</span>
<span style="color: #408080; font-style: italic"># передаем в параметры имена атрибутов, которым они присвоены</span>
<span style="color: #408080; font-style: italic"># таким образом мы избегаем дублирования имени 'ip' в след строке</span>
<span style="color: #408080; font-style: italic"># ip = Param(IPAddr, "ip addr to ping")</span>
<span style="color: #408080; font-style: italic"># и других таких-же</span>
<span style="color: #008000; font-weight: bold">for</span> name, param <span style="color: #AA22FF; font-weight: bold">in</span> new_cls<span style="color: #666666">.</span>Params<span style="color: #666666">.</span>__dict__<span style="color: #666666">.</span>items():
param<span style="color: #666666">.</span>name <span style="color: #666666">=</span> name
<span style="color: #008000; font-weight: bold">return</span> new_cls
<span style="color: #408080; font-style: italic"># базовый класс для всех API вызовов</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">APICallBase</span>(<span style="color: #008000">object</span>):
__metaclass__ <span style="color: #666666">=</span> APIMeta
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>, <span style="color: #666666">**</span>dt):
<span style="color: #008000">self</span><span style="color: #666666">.</span>_consume(dt)
<span style="color: #AA22FF">@classmethod</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">name</span>(cls):
<span style="color: #008000; font-weight: bold">return</span> cls<span style="color: #666666">.</span>__name__<span style="color: #666666">.</span>lower()
<span style="color: #AA22FF">@classmethod</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">all_params</span>(cls):
<span style="color: #008000; font-weight: bold">return</span> cls<span style="color: #666666">.</span>Params<span style="color: #666666">.</span>__dict__<span style="color: #666666">.</span>values()
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">rest_url</span>(<span style="color: #008000">self</span>):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #BA2121">'/{0}'</span><span style="color: #666666">.</span>format(<span style="color: #008000">self</span><span style="color: #666666">.</span>name())
<span style="color: #AA22FF">@classmethod</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">from_dict</span>(cls, data):
obj <span style="color: #666666">=</span> cls<span style="color: #666666">.</span>__new__(cls)
obj<span style="color: #666666">.</span>_consume(data)
<span style="color: #008000; font-weight: bold">return</span> obj
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">_consume</span>(<span style="color: #008000">self</span>, data, from_strings<span style="color: #666666">=</span><span style="color: #008000">False</span>):
<span style="color: #408080; font-style: italic"># этот метод заполняет экземпляр команды из словаря параметров</span>
<span style="color: #408080; font-style: italic"># и проводит все необходимые проверки параметров</span>
required_param_names <span style="color: #666666">=</span> <span style="color: #008000">set</span>()
all_param_names <span style="color: #666666">=</span> <span style="color: #008000">set</span>()
<span style="color: #008000; font-weight: bold">for</span> param <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>all_params():
<span style="color: #008000; font-weight: bold">if</span> param<span style="color: #666666">.</span>required():
required_param_names<span style="color: #666666">.</span>add(param<span style="color: #666666">.</span>name)
all_param_names<span style="color: #666666">.</span>add(param<span style="color: #666666">.</span>name)
<span style="color: #408080; font-style: italic"># проверяем наличие лишних параметров data</span>
extra_params <span style="color: #666666">=</span> <span style="color: #008000">set</span>(data<span style="color: #666666">.</span>keys()) <span style="color: #666666">-</span> all_param_names
<span style="color: #008000; font-weight: bold">if</span> extra_params <span style="color: #666666">!=</span> <span style="color: #008000">set</span>():
<span style="color: #008000; font-weight: bold">raise</span> <span style="color: #D2413A; font-weight: bold">ValueError</span>(<span style="color: #BA2121">"Extra parameters {0} for cmd {1}"</span><span style="color: #666666">.</span>format(
<span style="color: #BA2121">','</span><span style="color: #666666">.</span>join(extra_params), <span style="color: #008000">self</span><span style="color: #666666">.</span>__class__<span style="color: #666666">.</span>__name__))
<span style="color: #408080; font-style: italic"># проверяем наличие в data всех необходимых параметров</span>
missed_params <span style="color: #666666">=</span> required_param_names <span style="color: #666666">-</span> <span style="color: #008000">set</span>(data<span style="color: #666666">.</span>keys())
<span style="color: #008000; font-weight: bold">if</span> missed_params <span style="color: #666666">!=</span> <span style="color: #008000">set</span>():
<span style="color: #008000; font-weight: bold">raise</span> <span style="color: #D2413A; font-weight: bold">ValueError</span>(<span style="color: #BA2121">"Missed parameters {0} for cmd {1}"</span><span style="color: #666666">.</span>format(
<span style="color: #BA2121">','</span><span style="color: #666666">.</span>join(missed_params), <span style="color: #008000">self</span><span style="color: #666666">.</span>__class__<span style="color: #666666">.</span>__name__))
<span style="color: #408080; font-style: italic"># проверяем значение параметра или пребразовываем его из строки</span>
<span style="color: #408080; font-style: italic"># (прошедшей из CLI) в целевой тип</span>
parsed_data <span style="color: #666666">=</span> {}
<span style="color: #008000; font-weight: bold">for</span> param <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>all_params():
<span style="color: #008000; font-weight: bold">try</span>:
val <span style="color: #666666">=</span> data[param<span style="color: #666666">.</span>name]
<span style="color: #008000; font-weight: bold">except</span> <span style="color: #D2413A; font-weight: bold">KeyError</span>:
parsed_data[param<span style="color: #666666">.</span>name] <span style="color: #666666">=</span> param<span style="color: #666666">.</span>default
<span style="color: #008000; font-weight: bold">if</span> from_strings:
parsed_data[param<span style="color: #666666">.</span>name] <span style="color: #666666">=</span> param<span style="color: #666666">.</span>from_cli(val)
<span style="color: #008000; font-weight: bold">else</span>:
param<span style="color: #666666">.</span>validate(val)
parsed_data[param<span style="color: #666666">.</span>name] <span style="color: #666666">=</span> val
<span style="color: #408080; font-style: italic"># обновляем аттрибуты и возвращает объект</span>
<span style="color: #008000">self</span><span style="color: #666666">.</span>__dict__<span style="color: #666666">.</span>update(parsed_data)
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">self</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">to_dict</span>(<span style="color: #008000">self</span>):
res <span style="color: #666666">=</span> {}
<span style="color: #008000; font-weight: bold">for</span> param <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>all_params():
res[param<span style="color: #666666">.</span>name] <span style="color: #666666">=</span> <span style="color: #008000">getattr</span>(<span style="color: #008000">self</span>, param<span style="color: #666666">.</span>name)
<span style="color: #008000; font-weight: bold">return</span> res
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">execute</span>(<span style="color: #008000">self</span>):
<span style="color: #408080; font-style: italic"># базовый метод для выполнения работы</span>
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__str__</span>(<span style="color: #008000">self</span>):
res <span style="color: #666666">=</span> <span style="color: #BA2121">"{0}({{0}})"</span><span style="color: #666666">.</span>format(<span style="color: #008000">self</span><span style="color: #666666">.</span>__class__<span style="color: #666666">.</span>__name__)
params <span style="color: #666666">=</span> [<span style="color: #BA2121">"{0}={1!r}"</span><span style="color: #666666">.</span>format(param<span style="color: #666666">.</span>name, <span style="color: #008000">getattr</span>(<span style="color: #008000">self</span>, param<span style="color: #666666">.</span>name))
<span style="color: #008000; font-weight: bold">for</span> param <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>all_params()]
<span style="color: #008000; font-weight: bold">return</span> res<span style="color: #666666">.</span>format(<span style="color: #BA2121">', '</span><span style="color: #666666">.</span>join(params))
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__repr__</span>(<span style="color: #008000">self</span>):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">str</span>(<span style="color: #008000">self</span>)
</pre></div>
</span>
<span style="line-height:100%;display:none" id="bb4ca1085c3711e19705bc7737dae05d">
class APIMeta(type):
# список всех API вызовов
api_classes = []
def __new__(cls, name, bases, clsdict):
new_cls = super(APIMeta, cls).__new__(cls, name, bases, clsdict)
# пропускаем APICallBase
if name != 'APICallBase':
self.api_classes.append(new_cls)
# all_params итерирует по всем параметрам этого вызова
# передаем в параметры имена атрибутов, которым они присвоены
# таким образом мы избегаем дублирования имени 'ip' в след строке
# ip = Param(IPAddr, "ip addr to ping")
# и других таких-же
for name, param in new_cls.Params.__dict__.items():
param.name = name
return new_cls
# базовый класс для всех API вызовов
class APICallBase(object):
__metaclass__ = APIMeta
def __init__(self, **dt):
self._consume(dt)
@classmethod
def name(cls):
return cls.__name__.lower()
@classmethod
def all_params(cls):
return cls.Params.__dict__.values()
def rest_url(self):
return '/{0}'.format(self.name())
@classmethod
def from_dict(cls, data):
obj = cls.__new__(cls)
obj._consume(data)
return obj
def _consume(self, data, from_strings=False):
# этот метод заполняет экземпляр команды из словаря параметров
# и проводит все необходимые проверки параметров
required_param_names = set()
all_param_names = set()
for param in self.all_params():
if param.required():
required_param_names.add(param.name)
all_param_names.add(param.name)
# проверяем наличие лишних параметров data
extra_params = set(data.keys()) - all_param_names
if extra_params != set():
raise ValueError("Extra parameters {0} for cmd {1}".format(
','.join(extra_params), self.__class__.__name__))
# проверяем наличие в data всех необходимых параметров
missed_params = required_param_names - set(data.keys())
if missed_params != set():
raise ValueError("Missed parameters {0} for cmd {1}".format(
','.join(missed_params), self.__class__.__name__))
# проверяем значение параметра или пребразовываем его из строки
# (прошедшей из CLI) в целевой тип
parsed_data = {}
for param in self.all_params():
try:
val = data[param.name]
except KeyError:
parsed_data[param.name] = param.default
if from_strings:
parsed_data[param.name] = param.from_cli(val)
else:
param.validate(val)
parsed_data[param.name] = val
# обновляем аттрибуты и возвращает объект
self.__dict__.update(parsed_data)
return self
def to_dict(self):
res = {}
for param in self.all_params():
res[param.name] = getattr(self, param.name)
return res
def execute(self):
# базовый метод для выполнения работы
pass
def __str__(self):
res = "{0}({{0}})".format(self.__class__.__name__)
params = ["{0}={1!r}".format(param.name, getattr(self, param.name))
for param in self.all_params()]
return res.format(', '.join(params))
def __repr__(self):
return str(self)
</span>
</span><p>Классы для типов данных, используемых в <strong>Params</strong></p>
<a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="bb4d83c05c3711e19705bc7737dae05d" objtohide2="bb4d855a5c3711e19705bc7737dae05d">Без подсветки синтаксиса</a>
<br /><span id="bb4d83c05c3711e19705bc7737dae05d">
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #408080; font-style: italic"># базовый класс для типов данных</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">DataType</span>(<span style="color: #008000">object</span>):
<span style="color: #408080; font-style: italic"># проверить, про val принадлежит к денному типу</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">validate</span>(<span style="color: #008000">self</span>, val):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">True</span>
<span style="color: #408080; font-style: italic"># преобразовать val из формата для командной строки</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">from_cli</span>(<span style="color: #008000">self</span>, val):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">None</span>
<span style="color: #408080; font-style: italic"># параметры для парсера CLI</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">arg_parser_opts</span>(<span style="color: #008000">self</span>):
<span style="color: #008000; font-weight: bold">return</span> {}
<span style="color: #408080; font-style: italic"># список параметров определенного типа</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">ListType</span>(DataType):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>, dtype):
<span style="color: #008000">self</span><span style="color: #666666">.</span>dtype <span style="color: #666666">=</span> get_data_type(dtype)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">validate</span>(<span style="color: #008000">self</span>, val):
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #AA22FF; font-weight: bold">not</span> <span style="color: #008000">isinstance</span>(val, (<span style="color: #008000">list</span>, <span style="color: #008000">tuple</span>)):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">False</span>
<span style="color: #008000; font-weight: bold">for</span> curr_item <span style="color: #AA22FF; font-weight: bold">in</span> val:
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #AA22FF; font-weight: bold">not</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>dtype<span style="color: #666666">.</span>valid(curr_item):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">False</span>
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">True</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">from_cli</span>(<span style="color: #008000">self</span>, val):
<span style="color: #008000; font-weight: bold">return</span> [<span style="color: #008000">self</span><span style="color: #666666">.</span>dtype<span style="color: #666666">.</span>from_cli(curr_item) <span style="color: #008000; font-weight: bold">for</span> curr_item <span style="color: #AA22FF; font-weight: bold">in</span> val]
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">arg_parser_opts</span>(<span style="color: #008000">self</span>):
opts <span style="color: #666666">=</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>dtype<span style="color: #666666">.</span>arg_parser_opts()
opts[<span style="color: #BA2121">'nargs'</span>] <span style="color: #666666">=</span> <span style="color: #BA2121">'*'</span>
<span style="color: #008000; font-weight: bold">return</span> opts
<span style="color: #408080; font-style: italic"># целое число</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">IntType</span>(DataType):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">validate</span>(<span style="color: #008000">self</span>, val):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">isinstance</span>(val, <span style="color: #008000">int</span>)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">from_cli</span>(<span style="color: #008000">self</span>, val):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">int</span>(val)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">arg_parser_opts</span>(<span style="color: #008000">self</span>):
<span style="color: #008000; font-weight: bold">return</span> {<span style="color: #BA2121">'type'</span>: <span style="color: #008000">int</span>}
</pre></div>
</span>
<span style="line-height:100%;display:none" id="bb4d855a5c3711e19705bc7737dae05d">
# базовый класс для типов данных
class DataType(object):
# проверить, про val принадлежит к денному типу
def validate(self, val):
return True
# преобразовать val из формата для командной строки
def from_cli(self, val):
return None
# параметры для парсера CLI
def arg_parser_opts(self):
return {}
# список параметров определенного типа
class ListType(DataType):
def __init__(self, dtype):
self.dtype = get_data_type(dtype)
def validate(self, val):
if not isinstance(val, (list, tuple)):
return False
for curr_item in val:
if not self.dtype.valid(curr_item):
return False
return True
def from_cli(self, val):
return [self.dtype.from_cli(curr_item) for curr_item in val]
def arg_parser_opts(self):
opts = self.dtype.arg_parser_opts()
opts['nargs'] = '*'
return opts
# целое число
class IntType(DataType):
def validate(self, val):
return isinstance(val, int)
def from_cli(self, val):
return int(val)
def arg_parser_opts(self):
return {'type': int}
</span><p>Итак переходим к генерации API. Для начала - CLI</p>
<a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="bb4e95125c3711e19705bc7737dae05d" objtohide2="bb4e971a5c3711e19705bc7737dae05d">Без подсветки синтаксиса</a>
<br /><span id="bb4e95125c3711e19705bc7737dae05d">
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">get_arg_parser</span>():
parser <span style="color: #666666">=</span> argparse<span style="color: #666666">.</span>ArgumentParser()
subparsers <span style="color: #666666">=</span> parser<span style="color: #666666">.</span>add_subparsers()
<span style="color: #008000; font-weight: bold">for</span> call <span style="color: #AA22FF; font-weight: bold">in</span> APIMeta<span style="color: #666666">.</span>api_classes():
<span style="color: #408080; font-style: italic"># для каждого вызова - свой вложенный парсер</span>
sub_parser <span style="color: #666666">=</span> subparsers<span style="color: #666666">.</span>add_parser(call<span style="color: #666666">.</span>name(),
help<span style="color: #666666">=</span>call<span style="color: #666666">.</span>__doc__)
sub_parser<span style="color: #666666">.</span>set_defaults(cmd_class<span style="color: #666666">=</span>call)
<span style="color: #408080; font-style: italic"># проходим по всем параметрам и добавляем для них опции в CLI</span>
<span style="color: #008000; font-weight: bold">for</span> param <span style="color: #AA22FF; font-weight: bold">in</span> call<span style="color: #666666">.</span>all_params():
opts <span style="color: #666666">=</span> {<span style="color: #BA2121">'help'</span>:param<span style="color: #666666">.</span>help}
<span style="color: #408080; font-style: italic"># значение по умолчанию, если оно есть</span>
<span style="color: #408080; font-style: italic"># _NoDef это специальный класс, что-бы отличать значение</span>
<span style="color: #408080; font-style: italic"># None и полное отсутствие параметра</span>
<span style="color: #008000; font-weight: bold">if</span> param<span style="color: #666666">.</span>default <span style="color: #AA22FF; font-weight: bold">is</span> <span style="color: #AA22FF; font-weight: bold">not</span> _NoDef:
opts[<span style="color: #BA2121">'default'</span>] <span style="color: #666666">=</span> param<span style="color: #666666">.</span>default
opts<span style="color: #666666">.</span>update(param<span style="color: #666666">.</span>arg_parser_opts())
sub_parser<span style="color: #666666">.</span>add_argument(<span style="color: #BA2121">'--'</span> <span style="color: #666666">+</span> param<span style="color: #666666">.</span>name<span style="color: #666666">.</span>replace(<span style="color: #BA2121">'_'</span>, <span style="color: #BA2121">'-'</span>),
<span style="color: #666666">**</span>opts)
<span style="color: #008000; font-weight: bold">return</span> parser, subparsers
</pre></div>
</span>
<span style="line-height:100%;display:none" id="bb4e971a5c3711e19705bc7737dae05d">
def get_arg_parser():
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
for call in APIMeta.api_classes():
# для каждого вызова - свой вложенный парсер
sub_parser = subparsers.add_parser(call.name(),
help=call.__doc__)
sub_parser.set_defaults(cmd_class=call)
# проходим по всем параметрам и добавляем для них опции в CLI
for param in call.all_params():
opts = {'help':param.help}
# значение по умолчанию, если оно есть
# _NoDef это специальный класс, что-бы отличать значение
# None и полное отсутствие параметра
if param.default is not _NoDef:
opts['default'] = param.default
opts.update(param.arg_parser_opts())
sub_parser.add_argument('--' + param.name.replace('_', '-'),
**opts)
return parser, subparsers
</span><p>REST API с помощью <a class="reference external" href="http://tools.cherrypy.org/">CherryPy</a></p>
<a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="bb4f11685c3711e19705bc7737dae05d" objtohide2="bb4f12ee5c3711e19705bc7737dae05d">Без подсветки синтаксиса</a>
<br /><span id="bb4f11685c3711e19705bc7737dae05d">
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">cherrypy</span> <span style="color: #008000; font-weight: bold">as</span> <span style="color: #0000FF; font-weight: bold">cp</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">get_cherrypy_server</span>():
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Server</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #408080; font-style: italic"># замыкание-обработчик для команды</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">call_me</span>(cmd_class):
<span style="color: #408080; font-style: italic"># обмениваться данными будем через json</span>
<span style="color: #AA22FF">@cp.tools.json_out</span>()
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">do_call</span>(<span style="color: #008000">self</span>, opts):
cmd <span style="color: #666666">=</span> cmd_class<span style="color: #666666">.</span>from_dict(json<span style="color: #666666">.</span>loads(opts))
<span style="color: #008000; font-weight: bold">return</span> cmd<span style="color: #666666">.</span>execute()
<span style="color: #008000; font-weight: bold">return</span> do_call
<span style="color: #408080; font-style: italic"># добавляем к классу Server по методу для каждой команды</span>
<span style="color: #408080; font-style: italic"># CherryPy будет их вызывать для обработки REST запросов</span>
<span style="color: #008000; font-weight: bold">for</span> call <span style="color: #AA22FF; font-weight: bold">in</span> APIMeta<span style="color: #666666">.</span>all_classes(APICallBase):
<span style="color: #008000">setattr</span>(Server,
call<span style="color: #666666">.</span>name(),
cp<span style="color: #666666">.</span>expose(call_me(call)))
<span style="color: #008000; font-weight: bold">return</span> Server
</pre></div>
</span>
<span style="line-height:100%;display:none" id="bb4f12ee5c3711e19705bc7737dae05d">
import cherrypy as cp
def get_cherrypy_server():
class Server(object):
pass
# замыкание-обработчик для команды
def call_me(cmd_class):
# обмениваться данными будем через json
@cp.tools.json_out()
def do_call(self, opts):
cmd = cmd_class.from_dict(json.loads(opts))
return cmd.execute()
return do_call
# добавляем к классу Server по методу для каждой команды
# CherryPy будет их вызывать для обработки REST запросов
for call in APIMeta.all_classes(APICallBase):
setattr(Server,
call.name(),
cp.expose(call_me(call)))
return Server
</span><p>CherryPy довольно интересный веб-сервер, который использует интроспекцию
и атрибуты классов для обработки HTTP запросов. Запрос вида
<a class="reference external" href="http://localhost:8080/xyz?a=1&b=2">http://localhost:8080/xyz?a=1&b=2</a> приведет к вызову <strong>Server.xyz(a="1", b="2")</strong>,
если такой есть и проброшен в web через <strong>cherrypy.expose</strong>.</p>
<p>Завершающий аккорд - функция main</p>
<a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="bb4ff3265c3711e19705bc7737dae05d" objtohide2="bb4ff4ac5c3711e19705bc7737dae05d">Без подсветки синтаксиса</a>
<br /><span id="bb4ff3265c3711e19705bc7737dae05d">
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">main</span>(argv<span style="color: #666666">=</span><span style="color: #008000">None</span>):
<span style="color: #408080; font-style: italic"># наполняем парсер CLI и разбираем командную строку</span>
argv <span style="color: #666666">=</span> argv <span style="color: #008000; font-weight: bold">if</span> argv <span style="color: #AA22FF; font-weight: bold">is</span> <span style="color: #AA22FF; font-weight: bold">not</span> <span style="color: #008000">None</span> <span style="color: #008000; font-weight: bold">else</span> sys<span style="color: #666666">.</span>argv
parser, subparsers <span style="color: #666666">=</span> get_arg_parser()
sub_parser <span style="color: #666666">=</span> subparsers<span style="color: #666666">.</span>add_parser(<span style="color: #BA2121">'start-server'</span>,
help<span style="color: #666666">=</span><span style="color: #BA2121">"Start REST server"</span>)
sub_parser<span style="color: #666666">.</span>set_defaults(cmd_class<span style="color: #666666">=</span><span style="color: #BA2121">'start-server'</span>)
res <span style="color: #666666">=</span> parser<span style="color: #666666">.</span>parse_args(argv)
cmd_cls <span style="color: #666666">=</span> res<span style="color: #666666">.</span>cmd_class
<span style="color: #408080; font-style: italic"># если пришел запрос на запуск сервера</span>
<span style="color: #008000; font-weight: bold">if</span> cmd_cls <span style="color: #666666">==</span> <span style="color: #BA2121">'start-server'</span>:
rest_server <span style="color: #666666">=</span> get_cherrypy_server()
cp<span style="color: #666666">.</span>quickstart(rest_server())
<span style="color: #008000; font-weight: bold">else</span>:
<span style="color: #408080; font-style: italic"># иначе конструируем объек-команду</span>
<span style="color: #008000; font-weight: bold">for</span> opt <span style="color: #AA22FF; font-weight: bold">in</span> cmd_cls<span style="color: #666666">.</span>all_params():
data <span style="color: #666666">=</span> {}
<span style="color: #008000; font-weight: bold">try</span>:
data[opt<span style="color: #666666">.</span>name] <span style="color: #666666">=</span> <span style="color: #008000">getattr</span>(res, opt<span style="color: #666666">.</span>name<span style="color: #666666">.</span>replace(<span style="color: #BA2121">'_'</span>, <span style="color: #BA2121">'-'</span>))
<span style="color: #008000; font-weight: bold">except</span> <span style="color: #D2413A; font-weight: bold">AttributeError</span>:
<span style="color: #008000; font-weight: bold">pass</span>
cmd <span style="color: #666666">=</span> cmd_cls<span style="color: #666666">.</span>from_dict(data)
<span style="color: #408080; font-style: italic"># если не определена переменная окружения REST_SERVER_URL</span>
rest_url <span style="color: #666666">=</span> os<span style="color: #666666">.</span>environ<span style="color: #666666">.</span>get(<span style="color: #BA2121">'REST_SERVER_URL'</span>, <span style="color: #008000">None</span>)
<span style="color: #008000; font-weight: bold">if</span> rest_url <span style="color: #AA22FF; font-weight: bold">is</span> <span style="color: #008000">None</span>:
<span style="color: #408080; font-style: italic"># исполняем локально</span>
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"Local exec"</span>
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"Res ="</span>, cmd<span style="color: #666666">.</span>execute()
<span style="color: #008000; font-weight: bold">else</span>:
<span style="color: #408080; font-style: italic"># иначе исполняем на сервере</span>
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"Remote exec"</span>
params <span style="color: #666666">=</span> urllib<span style="color: #666666">.</span>urlencode({<span style="color: #BA2121">'opts'</span>: json<span style="color: #666666">.</span>dumps(cmd<span style="color: #666666">.</span>to_dict())})
res <span style="color: #666666">=</span> urllib2<span style="color: #666666">.</span>urlopen(<span style="color: #BA2121">"http://{0}{1}?{2}"</span><span style="color: #666666">.</span>format(rest_url,
cmd<span style="color: #666666">.</span>rest_url(),
params))<span style="color: #666666">.</span>read()
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"Res ="</span>, json<span style="color: #666666">.</span>loads(res)
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #666666">0</span>
</pre></div>
</span>
<span style="line-height:100%;display:none" id="bb4ff4ac5c3711e19705bc7737dae05d">
def main(argv=None):
# наполняем парсер CLI и разбираем командную строку
argv = argv if argv is not None else sys.argv
parser, subparsers = get_arg_parser()
sub_parser = subparsers.add_parser('start-server',
help="Start REST server")
sub_parser.set_defaults(cmd_class='start-server')
res = parser.parse_args(argv)
cmd_cls = res.cmd_class
# если пришел запрос на запуск сервера
if cmd_cls == 'start-server':
rest_server = get_cherrypy_server()
cp.quickstart(rest_server())
else:
# иначе конструируем объек-команду
for opt in cmd_cls.all_params():
data = {}
try:
data[opt.name] = getattr(res, opt.name.replace('_', '-'))
except AttributeError:
pass
cmd = cmd_cls.from_dict(data)
# если не определена переменная окружения REST_SERVER_URL
rest_url = os.environ.get('REST_SERVER_URL', None)
if rest_url is None:
# исполняем локально
print "Local exec"
print "Res =", cmd.execute()
else:
# иначе исполняем на сервере
print "Remote exec"
params = urllib.urlencode({'opts': json.dumps(cmd.to_dict())})
res = urllib2.urlopen("http://{0}{1}?{2}".format(rest_url,
cmd.rest_url(),
params)).read()
print "Res =", json.loads(res)
return 0
</span><p>Пробуем:</p>
<a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="bb51d1785c3711e19705bc7737dae05d" objtohide2="bb51d3125c3711e19705bc7737dae05d">Без подсветки синтаксиса</a>
<br /><span id="bb51d1785c3711e19705bc7737dae05d">
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">$</span> python api.py -h
<span style="color: #808080">usage: api.py [-h] {add,sub,ping,start-server} ...</span>
<span style="color: #808080">positional arguments:</span>
<span style="color: #808080"> {add,sub,ping,start-server}</span>
<span style="color: #808080"> add Add two integers</span>
<span style="color: #808080"> sub Substitute two integers</span>
<span style="color: #808080"> ping Ping host</span>
<span style="color: #808080"> start-server Start REST server</span>
<span style="color: #808080">optional arguments:</span>
<span style="color: #808080"> -h, --help show this help message and exit</span>
<span style="color: #000080; font-weight: bold">$</span> python api.py add --params 1 3
<span style="color: #808080">Local exec</span>
<span style="color: #808080">Res = 4</span>
<span style="color: #000080; font-weight: bold">$</span> <span style="color: #008000">export </span><span style="color: #19177C">REST_SERVER_URL</span><span style="color: #666666">=</span>localhost:8080
<span style="color: #000080; font-weight: bold">$</span> python api.py add --params 1 3
<span style="color: #808080">Remote exec</span>
<span style="color: #808080">Res = 4</span>
</pre></div>
</span>
<span style="line-height:100%;display:none" id="bb51d3125c3711e19705bc7737dae05d">
$ python api.py -h
usage: api.py [-h] {add,sub,ping,start-server} ...
positional arguments:
{add,sub,ping,start-server}
add Add two integers
sub Substitute two integers
ping Ping host
start-server Start REST server
optional arguments:
-h, --help show this help message and exit
$ python api.py add --params 1 3
Local exec
Res = 4
$ export REST_SERVER_URL=localhost:8080
$ python api.py add --params 1 3
Remote exec
Res = 4
</span><p>Идея очень простая, так что особенно писать нечего - код говорит сам за себя.
Более полный вариант можно найти на <a class="reference external" href="https://github.com/koder-ua/python-lectures/blob/master/posts/api.py">koder github</a>. Основная мысль - вынос каждой команды
в отдельный класс и описание всех ее параметров в виде, удобном для интроспекции.
Похожим на описанный образом можно генерировать логику для <a class="reference external" href="https://bitbucket.org/jespern/django-piston/wiki/Home">django piston</a>,
html документацию по всем параметрам, отличия между версиями API для различных версий
сервера и другое, как это делается на нашем текущем проекте.</p>
<p>Исходники этого и других постов со скриптами лежат тут - <a class="reference external" href="https://github.com/koder-ua/python-lectures">my blog at github</a>
При использовании их, пожалуйста, ссылайтесь на <a class="reference external" href="http://koder-ua.blogspot.com/">koder-ua.blogspot.com</a></p>
</div>
<script type="text/javascript">
function on_hidabble_click()
{
var me = $(this);
var hide_id = me.attr("objtohide");
var controlled_object = $('#' + hide_id);
controlled_object.toggle();
if ( controlled_object.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".hidder").click(on_hidabble_click);
function on_double_hidabble_click()
{
var me = $(this);
var hide_id1 = me.attr("objtohide1");
var hide_id2 = me.attr("objtohide2");
var controlled_object1 = $('#' + hide_id1);
var controlled_object2 = $('#' + hide_id2);
controlled_object1.toggle();
controlled_object2.toggle();
if ( controlled_object1.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".dhidder").click(on_double_hidabble_click);
</script>Anonymoushttp://www.blogger.com/profile/06645078574760734586noreply@blogger.com3tag:blogger.com,1999:blog-1174489715777430743.post-46251565180412080132012-02-11T02:50:00.000+02:002012-02-11T02:50:19.057+02:00Использование виртуальных машин для автоматического тестировани<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<p style="text-indent:20px"> В системном программирования достаточно часто возникает ситуация, когда значительная часть функциональности программы перекладывается на внешние компоненты. Типичный пример - операции с iptables, дисковыми образами и виртуальными машинами. Классически для тестирования такого кода используются массовые моки, отрезающие тестируемый код от всех внешних зависимостей.</p><p style="text-indent:20px"> При очевидных достоинствах (полная независимость тестов от внешнего мира, скорость исполнения, etc) у моков есть некоторое количество недостатков - самый главный это переход от тестирования того что должно быть сделано к тестирования того как это сделано. Если нужно проверить функцию, которая настраивает проброс порта, то вместо тестирования результата (правильного прохождения пакетов) проверяется, что iptables вызвалась с правильными параметрами.</p><a name='more'></a><p style="text-indent:20px"> По итогу юнит тест проверяет не правильность работы кода, а является отражением его структуры. Такой тест помогает обеспечить постоянную проверку на отсутствие <b>AttributeError</b> и ему подобных (python), но на этом его полезность оканчивается. Учитывая желание менеджера и/или заказчика получить заветные X% покрытия ситуация становится совсем идиотской. Несколько последних проектов, в которых я учавствовал были именно такие - тонкая прослойка из python, связывающая вместе БД, REST, xen, iptables и еще горстку linux утилит в небольшой специализированный клауд. По итогу заметная часть UT требует переписывания после каждого рефакторинга, потому как изменилось взаимодействие с внешними компонентами. То что должно поощрять рефакторинг и улучшение кода становится одним из главных его тормозов.</p><p style="text-indent:20px"> Частично эта ситуация отражает объективный факт - мы не можем позволить юнит-тестам бесконтрольтно модифицировать файловую систему на локальной машине, изменять правила прохождения пакетов или ip маршруты. Дополнительный минус - рабочая машина разработчика не всегда соответствует требованиям к конечному серверу.</p><p style="text-indent:20px"> Решение совершенно очевидное - использовать для тестов виртуальные машины и проводить тесты на необходимой конфигурации + исключить бОльшую часть моков из тестов.</p><p style="text-indent:20px">Итого - что хотелось получить:</p><ul><li>исполнять отдельные юнит-тесты на виртуальных машинах или группах машин<li>интеграция с <a href="http://readthedocs.org/docs/nose/en/latest/">nosetests</a> и <a href="http://pypi.python.org/pypi/coverage">coverage</a><li>максимально простое использование<li>высокая скорость - юнит-тесты должны исполняться быстро</ul><p style="text-indent:20px">Как хотелось это использовать:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="3e73d330544a11e19852bc7737dae05d" objtohide2="3e74308c544a11e19852bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="3e73d330544a11e19852bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #AA22FF">@on_vm</span>(<span style="color: #BA2121">'worker-1'</span>)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">test_iptables</span>():
make_iptables_rules()
check_packages_goes_ok()
<span style="color: #AA22FF">@on_vm</span>(<span style="color: #BA2121">'worker-2'</span>)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">test_something</span>():
make_something()
check_something_works()
</pre></div></span><span style="line-height:100%;display:none" id="3e74308c544a11e19852bc7737dae05d"><pre><font face="courier">@on_vm('worker-1')
def test_iptables():
make_iptables_rules()
check_packages_goes_ok()
@on_vm('worker-2')
def test_something():
make_something()
check_something_works()</font></pre></span><p style="text-indent:20px"> Доводить идею до рабочего варианта в рамках внутреннего проекта взялись интерны <a href="http://mirantis.com/">нашей компании</a> - <a href="https://github.com/ogirOK">Игорь Гарагатый</a> и <a href="https://github.com/anakriya">Настя Криштопа</a>.</p><p style="text-indent:20px"> Для начала было решено реализовать достаточно простой вариант: перед исполнением каждого теста, требующего виртуальную машину, запускалась соответствующая vm, на нее копировался код и тесты, запускались тесты и их результаты тестов возвращались назад на хост машину. Если тест выбросит исключение оно должно передаваться назад на хост и выбрасываться из локального теста - nose не должен замечать разницы между локальным и удаленным исполнением теста.</p><p style="text-indent:20px"> В итоге были выбраны два варианта - <a href="http://koder-ua.blogspot.com/2012/01/lxc.html">LXC</a> и KVM. LXC позволяет запустить виртуальную машину менее чем за секунду и не требует аппаратной поддержки виртуализации, а KVM это более надежный вариант, позволяющий запускать виртуальные машины любых конфигураций (LXC использует ядро хост системы, поэтому поднять в нем другую версию ядра или другую OS невозможно).</p><p style="text-indent:20px"> Хотелось иметь в vm файловую систему доступную для записи, но возвращаемую в начальное состояние после окончания теста. Для kvm это естественным образом решается возможностями qcow2, который позволяет сохранять все изменения в отдельный файл, не трогая оригинальный образ. Для LXC же нужна была файловая система с поддержкой снимков и быстрым откатом к ним. После рассмотрения btrfs, LVM+XFS и aufs решили остановиться на первом варианте.</p><p style="text-indent:20px">Что в итоге получилось:</p><ul><li>Пользователь подготавливает образы и конфигурации виртуальных машин, которые будут использоваться для UT<li>Оборачивает отдельные тесты декоратором on_vm с указанием на какой конфигурации его запускать<li>nosetests unitTests<li>profit (итоги тестов и coverage)</ul><p style="text-indent:20px">Примерная схема работы:</p><ul><li>Декоратор on_vm создает отдельный процесс, для поднятия ВМ и запускает поток, слушающий результаты на сокете<li>test_executor.py создает с помощью libvirt необходимую конфигурацию vm, предварительно сделав слепок btrfs или подключив qcow2 файл для сохранения изменений (в зависимости от типа виртуальной машины)<li>test_executor.py дожидается окончания запуска vm, копирует туда необходимые файлы и запускает только выбранные тест на исполнение, предварительно выставив переменные окружения<li>on_vm по переменным окружения определяет, что это реальных запуск и исполняет тест<li>при возникновении ошибки она сериализуется и передается на хост<li>итоги теста передаются на хост вместе с результатами покрытия<li>процесс на хосте принимает результаты, гасит vm, откатывает состояние образа и имитирует локальное исполнение теста.</ul><p style="text-indent:20px">На текущий момент результат пока в состоянии альфа готовности, еще много чего хотелось бы добавить (иммитацию правильного времени исполнения, повторное использование уже запущенных vm, поднятие групп vm с определенными сетевыми настройками), но текущая реализация уже готова для проб. Код можно найти тут <a href="https://github.com/koder-ua/vm_ut">vm_ut</a>.</p>Ссылки:<br> <a name="vm_ut"><a href="https://github.com/koder-ua/vm_ut">github.com/koder-ua/vm_ut</a></a><br> <a name="LXC"><a href="http://koder-ua.blogspot.com/2012/01/lxc.html">koder-ua.blogspot.com/2012/01/lxc.html</a></a><br> <a name="нашей_компании"><a href="http://mirantis.com/">mirantis.com</a></a><br> <a name="Игорь_Гарагатый"><a href="https://github.com/ogirOK">github.com/ogirOK</a></a><br> <a name="Настя_Криштопа"><a href="https://github.com/anakriya">github.com/anakriya</a></a><br> <a name="coverage"><a href="http://pypi.python.org/pypi/coverage">pypi.python.org/pypi/coverage</a></a><br> <a name="nosetests"><a href="http://readthedocs.org/docs/nose/en/latest/">readthedocs.org/docs/nose/en/latest</a></a><br><p style="text-indent:20px">Исходники этого и других постов со скриптами лежат тут - <a href="https://github.com/koder-ua/python-lectures">github.com/koder-ua</a>. При использовании их, пожалуйста, ссылайтесь на <a href="http://koder-ua.blogspot.com/">koder-ua.blogspot.com</a>.</p>
<script type="text/javascript">
function on_hidabble_click()
{
var me = $(this);
var hide_id = me.attr("objtohide");
var controlled_object = $('#' + hide_id);
controlled_object.toggle();
if ( controlled_object.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".hidder").click(on_hidabble_click);
function on_double_hidabble_click()
{
var me = $(this);
var hide_id1 = me.attr("objtohide1");
var hide_id2 = me.attr("objtohide2");
var controlled_object1 = $('#' + hide_id1);
var controlled_object2 = $('#' + hide_id2);
controlled_object1.toggle();
controlled_object2.toggle();
if ( controlled_object1.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".dhidder").click(on_double_hidabble_click);
</script>Anonymoushttp://www.blogger.com/profile/06645078574760734586noreply@blogger.com2tag:blogger.com,1999:blog-1174489715777430743.post-67647676208207679312012-02-10T02:17:00.000+02:002012-02-10T13:52:32.138+02:00Запуск процессов в linux с ограничением ресурсов<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<br><p style="text-indent:20px"> Иногда хочется ограничить максимальное количество ресурсов доступных процессу. Последней пинком стали юнит-тесты из текущего проекта - из-за ошибок они несколько раз съели все 8Gb ОЗУ и отправили систему в глубокий своп, откуда она со скрипом доставалась минут 15. Полная виртуализация таких случаях избыточна - нужно что-то по легче. В linux для этого есть <a href="http://www.mjmwired.net/kernel/Documentation/cgroups.txt">cgroups</a> (control groups) - они позволяют поместить процесс (вместе со всеми его потомками) в контейнер, имеющий доступ только к части ресурсов системы. На самом деле cgroups умеют больше, чем просто ограничение ресурсов - тут и счетчики производительности и другая статистика.</p><p style="text-indent:20px"> cgroups можно манипулировать вручную или с помощью <a href="http://libcg.sourceforge.net/">libcgroup</a>. Последний способ значительно удобнее и по нему есть отличная <a href="http://docs.redhat.com/docs/en-US/Red_Hat_Enterprise_Linux/6/html/Resource_Management_Guide/">документация</a> от redhat. Она обязательна к прочтению - есть несколько не очевидных моментов (для пользователей ubuntu - в этом дистрибутиве по умолчанию cgroups монтируются в /sys/fs/cgroups).</p><a name='more'></a><p style="text-indent:20px"> Краткий пересказ документации на примере ubuntu/libcgroup.</p><p style="text-indent:20px"> сgroups основываются на контроллерах и иерархии групп. Процессы входят в группы, группы организовываются в деревья, где подгруппы получают часть ресурсов родительских групп, а контроллеры управляют ресурсами. Контроллер может быть привязан или только к одной иерархии или к нескольким если он в каждой единственный контроллер, а каждый процесс может быть привязан только к одной группе в каждой иерархии, но может быть привязан к другим группам в других иерархиях. Деревья групп отображаются на файловую систему и все работа с ними идет через специальные файлы.</p><p style="text-indent:20px"> Основные контроллеры позволяют управлять привязкой процессов к ядрам, ограничивать максимальную долю процессорного времени, объем используемой памяти и свопа и использование пропускной способности сети, блочных устройств и другое.</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="903429be537c11e184a7bc7737dae05d" objtohide2="903464e2537c11e184a7bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="903429be537c11e184a7bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">#</span> apt-get install cgroup-bin
</pre></div></span><span style="line-height:100%;display:none" id="903464e2537c11e184a7bc7737dae05d"><pre><font face="courier"># apt-get install cgroup-bin</font></pre></span><p style="text-indent:20px"> После установки в системе появится сервис cgconfig, который автоматически создает иерархию групп и контроллеров по описанию из файла /etc/cgconfig.cfg. После правки файла сервис необходимо перезапустить. Иногда он не может корректно перезапуститься из-за не отмонтированных при останове контроллеров. Лечится это ручным отмонтированием контроллеров:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="9034ffba537c11e184a7bc7737dae05d" objtohide2="90353c00537c11e184a7bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="9034ffba537c11e184a7bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">$</span> sudo service cgconfig restart
<span style="color: #808080">[sudo] password for koder: </span>
<span style="color: #808080">stop: Job failed while stopping</span>
<span style="color: #000080; font-weight: bold">$</span> lscgroups
<span style="color: #808080">cpuset:/</span>
<span style="color: #808080">cpuset:/sysdefault</span>
<span style="color: #000080; font-weight: bold">$</span> rm -rf /sys/fs/cgroup/cpuset/sysdefault
<span style="color: #000080; font-weight: bold">$</span> umount /sys/fs/cgroup/cpuset
<span style="color: #000080; font-weight: bold">$</span> umount /sys/fs/cgroup
<span style="color: #000080; font-weight: bold">#</span> <span style="color: #408080; font-style: italic"># теперь можно перезапускать</span>
</pre></div></span><span style="line-height:100%;display:none" id="90353c00537c11e184a7bc7737dae05d"><pre><font face="courier">$ sudo service cgconfig restart
[sudo] password for koder:
stop: Job failed while stopping
$ lscgroups
cpuset:/
cpuset:/sysdefault
$ rm -rf /sys/fs/cgroup/cpuset/sysdefault
$ umount /sys/fs/cgroup/cpuset
$ umount /sys/fs/cgroup
# # теперь можно перезапускать</font></pre></span><p style="text-indent:20px"> Второй интересный сервис - cgred. Он умеет автоматически распределять процессы по группам в зависимости от правил, описанных в /etc/cgrules.conf. По умолчанию не запущен.</p><p style="text-indent:20px"> После установки cgroup-bin в /sys/fs/cgroup появится группы из стартового конфига:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="90359d8a537c11e184a7bc7737dae05d" objtohide2="9035a352537c11e184a7bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="90359d8a537c11e184a7bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">$</span> ls /sys/fs/cgroup
<span style="color: #808080">cpu cpuacct devices freezer memory</span>
</pre></div></span><span style="line-height:100%;display:none" id="9035a352537c11e184a7bc7737dae05d"><pre><font face="courier">$ ls /sys/fs/cgroup
cpu cpuacct devices freezer memory</font></pre></span><p style="text-indent:20px"> К каждой иерархии привязан один контроллер с соответствующим именем. Что-бы поместаить процесс под управление одного из контроллеров нужно выполнить 3 шага:</p><ul><li>Сделать группу. sudo mkdir /sys/fs/cgroup/memory/test создаст группу test под управлением контроллера memory. В папке /sys/fs/cgroup/memory/test тут же появятся специальные файлы для управления и мониторинга группы.<li>Настроить контроллер. Некоторые контроллеры готовы к работе сразу, а некотрые требуют предварительной настройки. Правила настройки контроллеров не очень очевидны и описаны в документации. Для memory минимально необходимо записать ограничение в файл limit_in_bytes.</ul><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="9035d840537c11e184a7bc7737dae05d" objtohide2="9035da3e537c11e184a7bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="9035d840537c11e184a7bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">$</span> sudo <span style="color: #008000">echo </span>4M > /sys/fs/cgroup/memory/test/limit_in_bytes
</pre></div></span><span style="line-height:100%;display:none" id="9035da3e537c11e184a7bc7737dae05d"><pre><font face="courier">$ sudo echo 4M > /sys/fs/cgroup/memory/test/limit_in_bytes</font></pre></span><p style="text-indent:20px">Теперь процессы в группе могут использовать не больше 4M ОЗУ на всех.</p><ul><li>Перенести процесс в группу - для этого нужно записать его pid в файл /sys/fs/cgroup/memory/test/tasks.</ul><p style="text-indent:20px"> Когда группа станет не нужна ее можно удалить через <b>rm -rf</b>. Что-бы создать подгруппу нужно сделать новую папку в папке группы.</p><p style="text-indent:20px"> cgroups-bin предлагает чуть более удобный интерфейс для управления группами:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="90362b7e537c11e184a7bc7737dae05d" objtohide2="90362d5e537c11e184a7bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="90362b7e537c11e184a7bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">#</span> cgcreate -g memory:/test <span style="color: #408080; font-style: italic"># создать группу tt под управлением контроллера memory</span>
<span style="color: #000080; font-weight: bold">#</span> lscgroup memory:/
<span style="color: #808080">memory:///</span>
<span style="color: #808080">memory:///test</span>
<span style="color: #808080">memory:///sysdefault</span>
<span style="color: #000080; font-weight: bold">#</span> cgset -r memory.limit_in_bytes<span style="color: #666666">=</span>4M memory:test <span style="color: #408080; font-style: italic"># устанавливаем limit</span>
<span style="color: #000080; font-weight: bold">#</span> cgexec -g memory:test python -c <span style="color: #BA2121">"range(10000000000)"</span> <span style="color: #408080; font-style: italic"># пытаемся сделать всем плохо</span>
<span style="color: #808080">Killed</span>
<span style="color: #000080; font-weight: bold">#</span> cgdelete memory:test <span style="color: #408080; font-style: italic"># удалить группу</span>
<span style="color: #000080; font-weight: bold">#</span> lscgroup memory:/
<span style="color: #808080">memory:///</span>
<span style="color: #808080">memory:///sysdefault</span>
</pre></div></span><span style="line-height:100%;display:none" id="90362d5e537c11e184a7bc7737dae05d"><pre><font face="courier"># cgcreate -g memory:/test # создать группу tt под управлением контроллера memory
# lscgroup memory:/
memory:///
memory:///test
memory:///sysdefault
# cgset -r memory.limit_in_bytes=4M memory:test # устанавливаем limit
# cgexec -g memory:test python -c "range(10000000000)" # пытаемся сделать всем плохо
Killed
# cgdelete memory:test # удалить группу
# lscgroup memory:/
memory:///
memory:///sysdefault</font></pre></span><p style="text-indent:20px"> Чтобы не делать это каждый раз руками можно настроить cgred или написать автоматизирующий скрипт; я сделал себе такой:</p><a hided_text="Показать код" visible_text="Скрыть код" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="hidder" objtohide="903645d2537c11e184a7bc7737dae05d">Показать код</a><br><span style="display:none" id="903645d2537c11e184a7bc7737dae05d"><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="90379bb2537c11e184a7bc7737dae05d" objtohide2="90379e3c537c11e184a7bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="90379bb2537c11e184a7bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #408080; font-style: italic">#!/bin/bash</span>
<span style="color: #408080; font-style: italic">#set -x</span>
<span style="color: #19177C">USAGE</span><span style="color: #666666">=</span><span style="color: #BA2121">"Usage: `basename $0` [-h] [-m MEM_LIMIT_IN_MEGS] [-c CPUS] [-i IOLIMIT] [-u USER] CMD"</span>
<span style="color: #19177C">cgroup_mount</span><span style="color: #666666">=</span>/sys/fs/cgroup
<span style="color: #19177C">GROUP</span><span style="color: #666666">=</span>app_cell_1
<span style="color: #008000; font-weight: bold">while</span> <span style="color: #666666">[</span> <span style="color: #008000">true</span> <span style="color: #666666">]</span> ; <span style="color: #008000; font-weight: bold">do</span>
<span style="color: #008000; font-weight: bold"> </span><span style="color: #19177C">OK</span><span style="color: #666666">=</span>1
<span style="color: #008000; font-weight: bold">for </span>cgroup in <span style="color: #BA2121">`</span>lscgroup | awk -F: <span style="color: #BA2121">'{print $1}'</span> | uniq<span style="color: #BA2121">`</span>; <span style="color: #008000; font-weight: bold">do</span>
<span style="color: #008000; font-weight: bold"> if</span> <span style="color: #666666">[</span> -d <span style="color: #19177C">$cgroup_mount</span>/<span style="color: #19177C">$cgroup</span>/<span style="color: #19177C">$GROUP</span> <span style="color: #666666">]</span> ; <span style="color: #008000; font-weight: bold">then</span>
<span style="color: #008000; font-weight: bold"> </span><span style="color: #19177C">OK</span><span style="color: #666666">=</span>0
<span style="color: #008000">break</span>
<span style="color: #008000"> </span><span style="color: #008000; font-weight: bold">fi</span>
<span style="color: #008000; font-weight: bold"> done</span>
<span style="color: #008000; font-weight: bold"> if</span> <span style="color: #666666">((</span> <span style="color: #19177C">$OK</span><span style="color: #666666">==</span>1 <span style="color: #666666">))</span> ; <span style="color: #008000; font-weight: bold">then</span>
<span style="color: #008000; font-weight: bold"> </span><span style="color: #008000">break</span>
<span style="color: #008000"> </span><span style="color: #008000; font-weight: bold">fi</span>
<span style="color: #008000; font-weight: bold"> </span><span style="color: #19177C">GROUP</span><span style="color: #666666">=</span>app_cell_<span style="color: #19177C">$RANDOM</span>
<span style="color: #008000; font-weight: bold">done</span>
<span style="color: #19177C">MEMLIMIT</span><span style="color: #666666">=</span>
<span style="color: #19177C">CPUS</span><span style="color: #666666">=</span>
<span style="color: #19177C">USER</span><span style="color: #666666">=</span>
<span style="color: #19177C">IOLIMIT</span><span style="color: #666666">=</span>
<span style="color: #008000; font-weight: bold">while </span><span style="color: #008000">getopts </span>hm:c:i:u: OPT; <span style="color: #008000; font-weight: bold">do</span>
<span style="color: #008000; font-weight: bold"> case</span> <span style="color: #BA2121">"$OPT"</span> in
h<span style="color: #666666">)</span>
<span style="color: #008000">echo</span> <span style="color: #19177C">$USAGE</span>
<span style="color: #008000">exit </span>0
;;
c<span style="color: #666666">)</span>
<span style="color: #19177C">CPUS</span><span style="color: #666666">=</span><span style="color: #19177C">$OPTARG</span>
<span style="color: #008000">echo</span> <span style="color: #BA2121">"CPUS = $CPUS"</span>
;;
i<span style="color: #666666">)</span>
<span style="color: #19177C">IOLIMIT</span><span style="color: #666666">=</span><span style="color: #19177C">$OPTARG</span>
;;
m<span style="color: #666666">)</span>
<span style="color: #19177C">MEMLIMIT</span><span style="color: #666666">=</span><span style="color: #19177C">$OPTARG</span>
;;
u<span style="color: #666666">)</span>
<span style="color: #19177C">USER</span><span style="color: #666666">=</span><span style="color: #19177C">$OPTARG</span>
;;
<span style="color: #BB6622; font-weight: bold">\?</span><span style="color: #666666">)</span>
<span style="color: #008000">echo</span> <span style="color: #19177C">$USAGE</span> >&2
<span style="color: #008000">exit </span>1
;;
<span style="color: #008000; font-weight: bold">esac</span>
<span style="color: #008000; font-weight: bold">done </span>
<span style="color: #008000">shift</span> <span style="color: #008000; font-weight: bold">$((</span><span style="color: #19177C">$OPTIND</span> <span style="color: #666666">-</span> <span style="color: #666666">1</span><span style="color: #008000; font-weight: bold">))</span>
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #666666">[</span> <span style="color: #19177C">$# </span>-eq 0 <span style="color: #666666">]</span>; <span style="color: #008000; font-weight: bold">then</span>
<span style="color: #008000; font-weight: bold"> </span><span style="color: #008000">echo</span> <span style="color: #19177C">$USAGE</span> >&2
<span style="color: #008000">exit </span>1
<span style="color: #008000; font-weight: bold">fi</span>
<span style="color: #19177C">CMD</span><span style="color: #666666">=</span><span style="color: #19177C">$@</span>
<span style="color: #408080; font-style: italic">#cgdelete memory:/$GROUP 2>/dev/null</span>
<span style="color: #408080; font-style: italic">#cgdelete cpuset:/$GROUP 2>/dev/null</span>
<span style="color: #408080; font-style: italic">#cgdelete blkio:/$GROUP 2>/dev/null</span>
<span style="color: #19177C">CGEXEC_OPT</span><span style="color: #666666">=</span>
<span style="color: #19177C">LIMITS</span><span style="color: #666666">=</span>0
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #666666">[</span> -n <span style="color: #BA2121">"$MEMLIMIT"</span> <span style="color: #666666">]</span> ; <span style="color: #008000; font-weight: bold">then</span>
<span style="color: #008000; font-weight: bold"> </span><span style="color: #19177C">LIMITS</span><span style="color: #666666">=</span>1
cgcreate -g memory:/<span style="color: #19177C">$GROUP</span>
cgset -r memory.limit_in_bytes<span style="color: #666666">=</span><span style="color: #19177C">$MEMLIMIT</span> <span style="color: #19177C">$GROUP</span>
cgset -r memory.memsw.limit_in_bytes<span style="color: #666666">=</span><span style="color: #19177C">$MEMLIMIT</span> <span style="color: #19177C">$GROUP</span>
<span style="color: #19177C">CGEXEC_OPT</span><span style="color: #666666">=</span><span style="color: #BA2121">"$CGEXEC_OPT -g memory:$GROUP"</span>
<span style="color: #008000; font-weight: bold">fi</span>
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #666666">[</span> -n <span style="color: #BA2121">"$CPUS"</span> <span style="color: #666666">]</span> ; <span style="color: #008000; font-weight: bold">then</span>
<span style="color: #008000; font-weight: bold"> </span><span style="color: #19177C">LIMITS</span><span style="color: #666666">=</span>1
cgcreate -g cpuset:/<span style="color: #19177C">$GROUP</span>
cgset -r cpuset.cpus<span style="color: #666666">=</span><span style="color: #19177C">$CPUS</span> <span style="color: #19177C">$GROUP</span>
cgset -r cpuset.mems<span style="color: #666666">=</span>0 <span style="color: #19177C">$GROUP</span>
<span style="color: #19177C">CGEXEC_OPT</span><span style="color: #666666">=</span><span style="color: #BA2121">"$CGEXEC_OPT -g cpuset:$GROUP"</span>
<span style="color: #008000; font-weight: bold">fi</span>
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #666666">[</span> -n <span style="color: #BA2121">"$IOLIMIT"</span> <span style="color: #666666">]</span> ; <span style="color: #008000; font-weight: bold">then</span>
<span style="color: #008000; font-weight: bold"> </span><span style="color: #008000">echo</span> <span style="color: #BA2121">"IO limits not supported yet"</span> >&2
<span style="color: #008000; font-weight: bold">fi</span>
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #666666">((</span> <span style="color: #19177C">$LIMITS</span><span style="color: #666666">==</span>0 <span style="color: #666666">))</span> ; <span style="color: #008000; font-weight: bold">then</span>
<span style="color: #008000; font-weight: bold"> </span><span style="color: #008000">echo</span> <span style="color: #BA2121">"At least one limit should be set"</span> >&2
<span style="color: #008000">echo</span> <span style="color: #19177C">$USAGE</span> >&2
<span style="color: #008000">exit </span>1
<span style="color: #008000; font-weight: bold">fi</span>
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #666666">[</span> -e <span style="color: #BA2121">"$USER"</span> <span style="color: #666666">]</span> ; <span style="color: #008000; font-weight: bold">then</span>
<span style="color: #008000; font-weight: bold"> </span>cgexec <span style="color: #19177C">$CGEXEC_OPT</span> <span style="color: #19177C">$CMD</span>
<span style="color: #008000; font-weight: bold">else</span>
<span style="color: #008000; font-weight: bold"> </span>cgexec <span style="color: #19177C">$CGEXEC_OPT</span> su <span style="color: #BA2121">"$USER"</span> -c <span style="color: #BA2121">"$CMD"</span>
<span style="color: #008000; font-weight: bold">fi</span>
</pre></div></span><span style="line-height:100%;display:none" id="90379e3c537c11e184a7bc7737dae05d"><pre><font face="courier">#!/bin/bash
#set -x
USAGE="Usage: `basename $0` [-h] [-m MEM_LIMIT_IN_MEGS] [-c CPUS] [-i IOLIMIT] [-u USER] CMD"
cgroup_mount=/sys/fs/cgroup
GROUP=app_cell_1
while [ true ] ; do
OK=1
for cgroup in `lscgroup | awk -F: '{print $1}' | uniq`; do
if [ -d $cgroup_mount/$cgroup/$GROUP ] ; then
OK=0
break
fi
done
if (( $OK==1 )) ; then
break
fi
GROUP=app_cell_$RANDOM
done
MEMLIMIT=
CPUS=
USER=
IOLIMIT=
while getopts hm:c:i:u: OPT; do
case "$OPT" in
h)
echo $USAGE
exit 0
;;
c)
CPUS=$OPTARG
echo "CPUS = $CPUS"
;;
i)
IOLIMIT=$OPTARG
;;
m)
MEMLIMIT=$OPTARG
;;
u)
USER=$OPTARG
;;
\?)
echo $USAGE >&2
exit 1
;;
esac
done
shift $(($OPTIND - 1))
if [ $# -eq 0 ]; then
echo $USAGE >&2
exit 1
fi
CMD=$@
#cgdelete memory:/$GROUP 2>/dev/null
#cgdelete cpuset:/$GROUP 2>/dev/null
#cgdelete blkio:/$GROUP 2>/dev/null
CGEXEC_OPT=
LIMITS=0
if [ -n "$MEMLIMIT" ] ; then
LIMITS=1
cgcreate -g memory:/$GROUP
cgset -r memory.limit_in_bytes=$MEMLIMIT $GROUP
cgset -r memory.memsw.limit_in_bytes=$MEMLIMIT $GROUP
CGEXEC_OPT="$CGEXEC_OPT -g memory:$GROUP"
fi
if [ -n "$CPUS" ] ; then
LIMITS=1
cgcreate -g cpuset:/$GROUP
cgset -r cpuset.cpus=$CPUS $GROUP
cgset -r cpuset.mems=0 $GROUP
CGEXEC_OPT="$CGEXEC_OPT -g cpuset:$GROUP"
fi
if [ -n "$IOLIMIT" ] ; then
echo "IO limits not supported yet" >&2
fi
if (( $LIMITS==0 )) ; then
echo "At least one limit should be set" >&2
echo $USAGE >&2
exit 1
fi
if [ -e "$USER" ] ; then
cgexec $CGEXEC_OPT $CMD
else
cgexec $CGEXEC_OPT su "$USER" -c "$CMD"
fi</font></pre></span></span><p style="text-indent:20px">Используется так:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="9037fcce537c11e184a7bc7737dae05d" objtohide2="9037ffda537c11e184a7bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="9037fcce537c11e184a7bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">$</span> sudo ./incontainer -u koder -m 1G -c 0-1 nosetests
<span style="color: #000080; font-weight: bold">#</span> <span style="color: #408080; font-style: italic">#nosetests ограничен 1 гигабайтом ОЗУ и только двумя ядрами.</span>
</pre></div></span><span style="line-height:100%;display:none" id="9037ffda537c11e184a7bc7737dae05d"><pre><font face="courier">$ sudo ./incontainer -u koder -m 1G -c 0-1 nosetests
# #nosetests ограничен 1 гигабайтом ОЗУ и только двумя ядрами.</font></pre></span><p style="text-indent:20px"> Скрипт каждый раз создает новую группу вида app_cell_случайное_число, которые нужно периодически удалять, когда в них не останется ни одного процесса. Например, так:</p><a hided_text="Показать код" visible_text="Скрыть код" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="hidder" objtohide="90381a9c537c11e184a7bc7737dae05d">Показать код</a><br><span style="display:none" id="90381a9c537c11e184a7bc7737dae05d"><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="9038a494537c11e184a7bc7737dae05d" objtohide2="9038a6b0537c11e184a7bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="9038a494537c11e184a7bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #408080; font-style: italic">#!/bin/bash</span>
<span style="color: #19177C">cgroup_mount</span><span style="color: #666666">=</span>/sys/fs/cgroup
<span style="color: #008000; font-weight: bold">for </span>cgroup in <span style="color: #BA2121">`</span>lscgroup | awk -F: <span style="color: #BA2121">'{print $1}'</span> | uniq<span style="color: #BA2121">`</span> ; <span style="color: #008000; font-weight: bold">do</span>
<span style="color: #008000; font-weight: bold"> for </span>group in <span style="color: #BA2121">`</span>ls -1d <span style="color: #19177C">$cgroup_mount</span>/<span style="color: #19177C">$cgroup</span>/app_cell_* 2>/dev/null<span style="color: #BA2121">`</span> ; <span style="color: #008000; font-weight: bold">do</span>
<span style="color: #408080; font-style: italic">#group=$cgroup_mount/$cgroup/$_group</span>
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #666666">[</span> -d <span style="color: #19177C">$group</span> <span style="color: #666666">]</span> ; <span style="color: #008000; font-weight: bold">then</span>
<span style="color: #008000; font-weight: bold"> </span><span style="color: #19177C">TC</span><span style="color: #666666">=</span><span style="color: #BA2121">`</span>cat <span style="color: #19177C">$group</span>/tasks | wc -l<span style="color: #BA2121">`</span>
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #666666">((</span> <span style="color: #19177C">$TC</span><span style="color: #666666">==</span>0 <span style="color: #666666">))</span> ; <span style="color: #008000; font-weight: bold">then</span>
<span style="color: #008000; font-weight: bold"> </span><span style="color: #19177C">gname</span><span style="color: #666666">=</span><span style="color: #19177C">$cgroup</span>:/<span style="color: #BA2121">`</span>basename <span style="color: #19177C">$group</span><span style="color: #BA2121">`</span>
<span style="color: #008000">echo</span> <span style="color: #BA2121">"Group $gname is empty - clear it"</span>
cgdelete -r <span style="color: #19177C">$gname</span>
<span style="color: #008000; font-weight: bold">fi</span>
<span style="color: #008000; font-weight: bold"> fi</span>
<span style="color: #008000; font-weight: bold"> done</span>
<span style="color: #008000; font-weight: bold">done</span>
</pre></div></span><span style="line-height:100%;display:none" id="9038a6b0537c11e184a7bc7737dae05d"><pre><font face="courier">#!/bin/bash
cgroup_mount=/sys/fs/cgroup
for cgroup in `lscgroup | awk -F: '{print $1}' | uniq` ; do
for group in `ls -1d $cgroup_mount/$cgroup/app_cell_* 2>/dev/null` ; do
#group=$cgroup_mount/$cgroup/$_group
if [ -d $group ] ; then
TC=`cat $group/tasks | wc -l`
if (( $TC==0 )) ; then
gname=$cgroup:/`basename $group`
echo "Group $gname is empty - clear it"
cgdelete -r $gname
fi
fi
done
done</font></pre></span></span><p style="text-indent:20px">P.S. Ковыряясь в cgroup наткнулся на очень интересный системный <a href="http://www.kernel.org/doc/man-pages/online/pages/man2/prctl.2.html">вызов</a></p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="903a2062537c11e184a7bc7737dae05d" objtohide2="903a21e8537c11e184a7bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="903a2062537c11e184a7bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">prctl(PR_SET_SECCOMP, <span style="color: #666666">0</span>, <span style="color: #666666">0</span>, <span style="color: #666666">0</span>, <span style="color: #666666">0</span>)
</pre></div></span><span style="line-height:100%;display:none" id="903a21e8537c11e184a7bc7737dae05d"><pre><font face="courier">prctl(PR_SET_SECCOMP, 0, 0, 0, 0)</font></pre></span><p style="text-indent:20px"> После него текущий процесс не может делать никакие системные вызовы, кроме записи/чтения в уже открытые файлы, _exit и sigreturn. Появился в 2.6.33, вместе с cgroups отличная идея для интерпретации/валидации потенциально опасных данных, например для реализации интерпретаторов, получающих скрипты из не доверенных источников.</p>Ссылки:<br> <a name="cgroups"><a href="http://www.mjmwired.net/kernel/Documentation/cgroups.txt">www.mjmwired.net/kernel/Documentation/cgroups.txt</a></a><br> <a name="libcgroup"><a href="http://libcg.sourceforge.net/">libcg.sourceforge.net</a></a><br> <a name="redhat_cgroups_docs"><a href="http://docs.redhat.com/docs/en-US/Red_Hat_Enterprise_Linux/6/html/Resource_Management_Guide/">docs.redhat.com/docs/en-US/Red_Hat_Enterprise_Linux/6/html/Resource_Management_Guide</a></a><br> <a name="prctl"><a href="http://www.kernel.org/doc/man-pages/online/pages/man2/prctl.2.html">www.kernel.org/doc/man-pages/online/pages/man2/prctl.2.html</a></a><br><p style="text-indent:20px">Исходники этого и других постов со скриптами лежат тут - <a href="https://github.com/koder-ua/python-lectures">github.com/koder-ua</a>. При использовании их, пожалуйста, ссылайтесь на <a href="http://koder-ua.blogspot.com/">koder-ua.blogspot.com</a>.</p>
<script type="text/javascript">
function on_hidabble_click()
{
var me = $(this);
var hide_id = me.attr("objtohide");
var controlled_object = $('#' + hide_id);
controlled_object.toggle();
if ( controlled_object.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".hidder").click(on_hidabble_click);
function on_double_hidabble_click()
{
var me = $(this);
var hide_id1 = me.attr("objtohide1");
var hide_id2 = me.attr("objtohide2");
var controlled_object1 = $('#' + hide_id1);
var controlled_object2 = $('#' + hide_id2);
controlled_object1.toggle();
controlled_object2.toggle();
if ( controlled_object1.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".dhidder").click(on_double_hidabble_click);
</script>Anonymoushttp://www.blogger.com/profile/06645078574760734586noreply@blogger.com1tag:blogger.com,1999:blog-1174489715777430743.post-350441479502714932012-01-16T04:05:00.000+02:002012-01-17T06:23:42.985+02:00LXC - виртуализация без виртуализации<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<p style="text-indent:20px"> Таблица снизу это обзор текущих систем виртуализации с позиции того, насколько большую часть хост системы виртуальные машины используют напрямую:</p><pre><font face="courier"> Матрешка виртуализации
==========================================================================================
Используется Название Примеры:
совместно
с хостом
==========================================================================================
ничего Эмуляция QEMU, Boсsh
------------------------------------------------------------------------------------------
CPU Виртуализация KVM, VmWare, XEN, Hyper-V
------------------------------------------------------------------------------------------
Аппаратура Intel VT-d, SR-IOV Может использоваться совместно
с системой виртуализации (kvm)
------------------------------------------------------------------------------------------
Драйвера Паравиртуализация XEN, VirtIO, VMWare tools
------------------------------------------------------------------------------------------
Ядро OS Контейнеры LXC, Solaris Zones, OpenVZ, Linux VServer
==========================================================================================</font></pre><p style="text-indent:20px"> В первой графе - слои системы "компьютер + ОС" - чем ниже, тем более высокий уровень (что-то типа уровней ISO для сетевого стека ). Средняя графа - название модели виртуализации. Третья графа - типичные примеры. Чем ниже, тем больше компонентов гостевые системы используют от хост-системы напрямую, тем меньше нагрузка на гипервизор и тем выше скорость работы.</p><p style="text-indent:20px"> Я немного расскажу о контейнерах вообще и <a href="http://lxc.sourceforge.net/">LXC</a> (LinuX Containers) в частности. Контейнеры (или виртуализация уровня операционной системы)- это группы процессов, изолированные от остальной системы, возможно с наложенными ограничениями, и имеющие доступ только к некоторой части ресурсов. Процессы из контейнера "видят" и могут напрямую взаимодействовать только с процессами из того же контейнера, им доступна только часть аппаратуры, а корень файловой системы контейнера с помощью chroot сдвинут в глубь файловой системы хоста (например, в /var/lib/lxc/my_container_1). Виртуализация всех необходимых подсистем ядра (таблицы монтирования, PID, маршруты IP, etc) позволяет контейнеру выглядеть как "нормальная" виртуалка.</p><a name='more'></a><p style="text-indent:20px"> Какие же плюсы и минусы есть у контейнера по сравнению с "полноценной" vm? Начнем с минусов - все контейнеры и хост система делять одну копию ядра и из этого вытекают очевидные недостатки:</p><ul><li>Безопасность - компрометация ОС в контейнере равноценна компрометации и хост ОС<li>Ограниченость вариантов окружения - только та же ОС, только та же версия ядра, даже запустить другой дистрибутив - может быть проблемой, поскольку часто дистрибутивы имеют модифицированные версии ядра, а родные утилиты и glibc могут полагаться на наличие в ядре определенных изменений.<li>Поддержка миграции, точек восстановления и сброса состояния на диск отсутствует в некоторых контейнерах, поскольку требует совсем другого типа взаимодействия с приложениями.</ul><p style="text-indent:20px"> Ко всему этому LXC добавляет проблемы с user id. root в контейнере имеет UID == 0, т.е. совпадает с root в хост системе. В некоторых случаях это создает проблемы безопасности, о чем я скажу еще раз ниже.</p><p style="text-indent:20px"> Теперь о плюсах:</p><ul><li>Не нужна аппаратная поддержка виртуализации (вообще никакая). Т.е. все это работает "из коробки" на любом процессоре ( а объем работы для портирования кода на новую архитектуру несравнимо меньше объема работы по портированию туда-же гипервизора типа KVM/XEN, KVM вот уже <a href="http://wiki.ncl.cs.columbia.edu/wiki/KVMARM:MainPage">тянут</a> на ARM 3 года). В частности это означает, что контейнеры работаю внутри виртуальных машин. Все, кто хотели попробовать Linux виртуализацию, но не хотели ставить Linux "на голое железо" - LXC это для вас; LXC будет работать в Linux'е, установленном в vmWare.<li>Возможна вложенная виртуализация. Даже, например, LXC в КVM/XEN в LXC и т.д. (если процессор "умеет" KVM). Главное, что нативная виртуализация не должна встречаться более одного раза (если она не поддерживает вложенность).<li>Быстрый (доли секунды) запуск. Фактически запускаются только необходимые для работы контейнера приложения. Ядро, драйвера, инициализация железа, etc - это все не нужно. Так же быстро он и гасится.<li>Нет потерь производительности CPU и дисковых операций - почти все, что доступно в контейнере, работает со скоростью хост системы. Несмотря на некоторые потери по скорости в сетевом стеке, связанные с избыточным копированием данных и невозможностью <a href="http://en.wikipedia.org/wiki/TCP_offload_engine">tcp offload</a> для <a href="http://wiki.openvz.org/Virtual_Ethernet_device">veth</a>, в целом все <a href="https://www.iip.net.pl/sites/default/files/41/Benchmarking%20the%20performance%20of%20virtualized%20routers.pdf">несравнимо лучше</a>, чем в xen/kvm (правда из тестов не ясно - использовалась ли там virtio, по идее kvm c ним не должен так сильно проседать по скорости). Где-то там в ядре есть, конечно, какие-то проверки или индексации массивов по номеру контейнера, но это все мелочь. То-же относится и к вложенным контейнерам.<li>Не нужно выделять оперативную память под ядро ОС и всё прилагающееся (видеопамять, дисковые буферы, etc). Из "лишних" потребителей остаются init и другие стартовые сервисы.<li>Нет дополнительных виртуальных устройств(таймеры, монитор, другое), вызывающих постоянное пробуждение гостевых драйверов, съедающих несколько процентов CPU на пустом месте.<li>Поддерживается запуск в контейнере отдельных приложений, а не полной системы - "chroot на стероидах" (по меньшей мере это умеет LXC).</ul><p style="text-indent:20px"> По итогу контейнеры позволяют поднять на весьма стандартном десктопе несколько <a href="http://tr.opensuse.org/OpenVZ_virtualization#Density">сотен</a> виртуальных машин c ssh и apache, сохраняя достаточную производительность.</p><p style="text-indent:20px"> Чем же особенно интересен LXC? Как можно понять из приведенного описания для реализации контейнера нужно внести в ядро OC очень значительные изменения. Именно поэтому долгое время в базовом ядре (vanilla kernel) контейнеров не было. И это не смотря на то, что OpenVZ/VServer появились не менее 6 лет назад и очень широко использовались (и использутся) VPS провайдерами. Все они, хотя и являются на сегодня более полноценной реализацией контейнеров чем LXC, не используют другие подсистемы ядра, а изобретают свои велосипеды (хотя благодаря LXC ситуация и у них улучшается). Видимо именно высокий уровень повторного использования кода привел к тому что LXC находится в основной ветке ядра уже достаточно давно (2.6.29+).</p><p style="text-indent:20px"> LXC является относительно небольшой надстройкой над <a href="http://en.wikipedia.org/wiki/Cgroups">cgroups</a>, <a href="http://eos.aristanetworks.com/2011/06/linux-namespaces-at-arista/">namespaces</a> и <a href="http://linux.die.net/man/7/capabilities">capabilities</a>. В итоге процессы из контейнера доступны для управления из хост системы всеми стандартными средствами и утилитами. С пользовательской позиции LXC несравнимо удобнее интеграцией в основное ядро линукс и (как результат) доступностью "из коробки" во всех дистрибутивах и(почти) всех ядрах без мороки с патчами, компиляцией и перезагрузками.</p><p style="text-indent:20px"> Из минусов конткретно LXC - его нужно аккуратно настраивать, иначе в него могут попасть "лишние" устройства и тогда он "распорядится" ими по своему усмотрению. Остались еще проблемы в безопасности, связанные с тем, что не все участки ядра Linux переведены на capabilities, и местами остались сравнения вида <b>0 == uid</b>. Из-за этого криво настроенный контейнер при старте может погасить XServer или выключить звук (udev постарается). Так-же есть <a href="http://en.gentoo-wiki.com/wiki/LXC#MAJOR_Temporary_Problems_with_LXC_-_READ_THIS">проблема</a> безопасности с sysfs. Из некритичного - dmesg общий с хостом и прочие мелочи. Также иногда разобраться "чего ему надо" требует больше времени, чем следовало бы.</p><p style="text-indent:20px"> Но все это временное - LXC серьезно продвигается Canonical, уже есть <a href="https://code.launchpad.net/~zulcss/nova/nova-lxc">поддержка</a> в openstack, на сайтах всех основных дистрибутивов есть <a href="http://wiki.debian.org/LXC">примеры</a> по <a href="http://en.gentoo-wiki.com/wiki/LXC">созданию</a> контейнеров.</p><p style="text-indent:20px"> Немного про технологии, лежащие в основе LXC. cgroups позволяет ограничить ресурсы, доступные процессу - процессорные ядра, процессорное время, ОЗУ, нагрузку на сеть, диски и другое; namespaces виртуализирует системные ресурсы - таблицу монтирования, PID, средства межпроцессного взаимодействия, сетевые интерфейсы, таблицы маршрутизации и прочее; capabilities - это система ролевого доступа, которая позволяет разделить абсолютные административные привилегии пользователя root на отдельные части (например: право использовать RAW сокеты, право загружать модули и т.д. ) и выдавать только те права, которые реально нужно приложению. Все эти возможности/ограничения наследуются дочерними процессами, а доступные через cgroups ресурсы делятся между ними.</p><br><h3>Практика</h3><p style="text-indent:20px"> О создании контейнеров можно подробно прочитать в сети, отдельно стоит отметить <a href="http://habrahabr.ru/blogs/virtualization/130522/">эту</a> статью - она будет особенно полезна windows пользователям, остальные могут смело пролистывать процесс создания Linux VM в VirtualBox. Ссылки из сети - <a href="http://lxc.sourceforge.net/man/lxc.html">1</a>, <a href="http://lxc.teegra.net/">2</a>, <a href="http://nigel.mcnie.name/blog/a-five-minute-guide-to-linux-containers-for-debian">debian</a>, <a href="http://www.markus-gattol.name/ws/linux_containers.html">3</a>, <a href="http://blog.mraw.org/2011/04/05/Running_X_from_LXC/">запуск X</a>, <a href="https://help.ubuntu.com/community/LXC">ubuntu</a>. Так что подробно останавливаться на установке не буду (все примеры для ubuntu 11.10):</p><pre><font face="courier"> # oneiric.cfg
lxc.utsname = test
lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = virbr0 # воспользуемся default сетью из libvirt
# для этого libvirt должна быть установлена
# см посты "облако на коленке"
lxc.network.hwaddr = 00:44:01:61:78:22
lxc.network.ipv4 = 192.168.122.190/24
lxc.network.name = test_vm</font></pre><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="6bf319a640c211e192b4bc7737dae05d" objtohide2="6bf344d040c211e192b4bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="6bf319a640c211e192b4bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">#</span> <span style="color: #408080; font-style: italic"># lxc-create использует debootstrap для создания минималистического</span>
<span style="color: #000080; font-weight: bold">#</span> <span style="color: #408080; font-style: italic"># образа ОС в /var/lib/lxc/test/rootfs</span>
<span style="color: #000080; font-weight: bold">#</span> lxc-create -n <span style="color: #008000">test</span> -f oneiric.cfg -t ubuntu -- --release oneiric
<span style="color: #808080">debootstrap is /usr/sbin/debootstrap</span>
<span style="color: #808080">Checking cache download in /var/cache/lxc/oneiric/rootfs-amd64 ... </span>
<span style="color: #808080">Copy /var/cache/lxc/oneiric/rootfs-amd64 to /var/lib/lxc/test/rootfs ... </span>
<span style="color: #808080">Copying rootfs to /var/lib/lxc/test/rootfs ...Please change root-password !</span>
<span style="color: #808080">Reading package lists... Done</span>
<span style="color: #808080">Building dependency tree... Done</span>
<span style="color: #808080">The following NEW packages will be installed:</span>
<span style="color: #808080"> lxcguest </span>
<span style="color: #000080; font-weight: bold">#</span> <span style="color: #408080; font-style: italic"># ........</span>
<span style="color: #808080">Setting up lxcguest (0.7.5-0ubuntu8) ...</span>
<span style="color: #808080">'ubuntu' template installed</span>
<span style="color: #808080">'test' created</span>
<span style="color: #000080; font-weight: bold">#</span> ls /var/lib/lxc/test/rootfs/
<span style="color: #808080">bin boot dev etc home lib lib64 media mnt opt proc root run sbin selinux srv sys tmp usr var</span>
</pre></div></span><span style="line-height:100%;display:none" id="6bf344d040c211e192b4bc7737dae05d"><pre><font face="courier"># # lxc-create использует debootstrap для создания минималистического
# # образа ОС в /var/lib/lxc/test/rootfs
# lxc-create -n test -f oneiric.cfg -t ubuntu -- --release oneiric
debootstrap is /usr/sbin/debootstrap
Checking cache download in /var/cache/lxc/oneiric/rootfs-amd64 ...
Copy /var/cache/lxc/oneiric/rootfs-amd64 to /var/lib/lxc/test/rootfs ...
Copying rootfs to /var/lib/lxc/test/rootfs ...Please change root-password !
Reading package lists... Done
Building dependency tree... Done
The following NEW packages will be installed:
lxcguest
# # ........
Setting up lxcguest (0.7.5-0ubuntu8) ...
'ubuntu' template installed
'test' created
# ls /var/lib/lxc/test/rootfs/
bin boot dev etc home lib lib64 media mnt opt proc root run sbin selinux srv sys tmp usr var</font></pre></span><p style="text-indent:20px"> Мы получили в /var/lib/lxc/test/rootfs корень файловой системы нашего контейнера. В принципе можно сделать туда chroot и провести все приготовления для старта. lxc-create по умолчанию помещает все контейнеры в /var/lib/lxc, из соображений удобства примеров мы их там и оставим.</p><p style="text-indent:20px"> Также в /var/lib/lxc/test есть два полезных файла - config и fstab. Первый содержит конфигурацию контейнера, а второй - описание точек монтирования. Часть конфурации скопирована из oneiric.cfg, а часть заполнена lxc-create параметрами по умолчанию:</p><a hided_text="Показать код" visible_text="Скрыть код" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="hidder" objtohide="6bf38f7640c211e192b4bc7737dae05d">Показать код</a><br><span style="display:none" id="6bf38f7640c211e192b4bc7737dae05d"><pre><font face="courier"> # скопированно из oneiric.cfg
lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = virbr0
lxc.network.hwaddr = 11:b2:c3:d4:e5:f7
lxc.network.ipv4 = 192.168.122.190/24
lxc.network.name = test_vm
lxc.utsname = test
# значения по умолчанию
lxc.tty = 4
lxc.pts = 1024
lxc.rootfs = /var/lib/lxc/test/rootfs
lxc.mount = /var/lib/lxc/test/fstab
lxc.arch = amd64
# устройства, которые будут доступны из контейнера
lxc.cgroup.devices.deny = a
# Allow any mknod (but not using the node)
lxc.cgroup.devices.allow = c *:* m
lxc.cgroup.devices.allow = b *:* m
# /dev/null and zero
lxc.cgroup.devices.allow = c 1:3 rwm
lxc.cgroup.devices.allow = c 1:5 rwm
# consoles
lxc.cgroup.devices.allow = c 5:1 rwm
lxc.cgroup.devices.allow = c 5:0 rwm
#lxc.cgroup.devices.allow = c 4:0 rwm
#lxc.cgroup.devices.allow = c 4:1 rwm
# /dev/{,u}random
lxc.cgroup.devices.allow = c 1:9 rwm
lxc.cgroup.devices.allow = c 1:8 rwm
lxc.cgroup.devices.allow = c 136:* rwm
lxc.cgroup.devices.allow = c 5:2 rwm
# rtc
lxc.cgroup.devices.allow = c 254:0 rwm
#fuse
lxc.cgroup.devices.allow = c 10:229 rwm</font></pre></span><p style="text-indent:20px"> Отдельно интересна группа с пробросом устройств - модифицируя ее можно выдавать контейнеры разнообразные возможности. Например для того чтобы разрешить использование kvm из контейнера нужно "пропустить" в него /dev/kvm</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="6bf3f66e40c211e192b4bc7737dae05d" objtohide2="6bf4234640c211e192b4bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="6bf3f66e40c211e192b4bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">#</span> ls -l /dev/kvm
<span style="color: #808080">crw-rw----+ 1 root kvm 10, 232 2012-01-11 21:14 /dev/kvm</span>
<span style="color: #000080; font-weight: bold">#</span> <span style="color: #408080; font-style: italic">#/dev/kvm это символьное устройство с id (10, 232)</span>
<span style="color: #000080; font-weight: bold">#</span> mknod /var/lib/lxc/test/rootfs/dev/kvm c 10 232
</pre></div></span><span style="line-height:100%;display:none" id="6bf4234640c211e192b4bc7737dae05d"><pre><font face="courier"># ls -l /dev/kvm
crw-rw----+ 1 root kvm 10, 232 2012-01-11 21:14 /dev/kvm
# #/dev/kvm это символьное устройство с id (10, 232)
# mknod /var/lib/lxc/test/rootfs/dev/kvm c 10 232</font></pre></span><p style="text-indent:20px">Теперь дописываем в config строку</p><pre><font face="courier"> lxc.cgroup.devices.allow = c 10:232 rwm</font></pre><p style="text-indent:20px"> Обратите внимание - это должно быть сделано <b>до первого запуска</b> контейнера. После первого запуска пробросить устройства уже не получится (по крайней мере этим способом).</p><p style="text-indent:20px"> Не забываем убрать из контейнера запуск udev. Устройства в нем будут управляться хостовым udev демоном, а локальный будет только пакостить. В ubuntu для этого делаем rm %CONTAINER_ROOT%/etc/init/udev.conf (привет, <a href="http://upstart.ubuntu.com/">upstart</a>).</p><p style="text-indent:20px"> Запускаем контейнер и смотрим, что вышло:</p><a hided_text="Показать код" visible_text="Скрыть код" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="hidder" objtohide="6bf44d3a40c211e192b4bc7737dae05d">Показать код</a><br><span style="display:none" id="6bf44d3a40c211e192b4bc7737dae05d"><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="6bf4b63a40c211e192b4bc7737dae05d" objtohide2="6bf4b7ac40c211e192b4bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="6bf4b63a40c211e192b4bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">#</span> lxc-start -d -n <span style="color: #008000">test</span>
<span style="color: #000080; font-weight: bold">#</span> lxc-ls
<span style="color: #808080">kvm_in_lxc oneiric oneiric_br test # вообще все доступные контейнеры</span>
<span style="color: #808080">test</span>
<span style="color: #000080; font-weight: bold">#</span> ip addr show
<span style="color: #000080; font-weight: bold">#</span> <span style="color: #408080; font-style: italic">#.....</span>
<span style="color: #808080"> # # это сетевой адаптер, ведущий в контейнер</span>
<span style="color: #808080"> 22: vethcSEf3D: <BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master virbr0 state UP qlen 1000</span>
<span style="color: #808080">link/ether d2:ea:5e:ba:3f:1b brd ff:ff:ff:ff:ff:ff</span>
<span style="color: #808080">inet6 fe80::d0ea:5eff:feba:3f1b/64 scope link </span>
<span style="color: #808080"> valid_lft forever preferred_lft forever</span>
<span style="color: #000080; font-weight: bold">#</span> brctl show
<span style="color: #808080">bridge name bridge id STP enabled interfaces</span>
<span style="color: #808080">br0 8000.000000000000 no</span>
<span style="color: #808080">virbr0 8000.d2ea5eba3f1b yes vethcSEf3D</span>
<span style="color: #000080; font-weight: bold">#</span> ps -eo pid,cgroup,user,args | grep cpuset:/test
<span style="color: #000080; font-weight: bold">#</span> <span style="color: #408080; font-style: italic"># показываем все процессы с информацией о cgroup</span>
<span style="color: #000080; font-weight: bold">#</span> <span style="color: #408080; font-style: italic"># и фильтруем только те, что относятся в группе test типа cpuset</span>
<span style="color: #808080">22526 6:cpuset:/test?5:freezer:/ root /sbin/init</span>
<span style="color: #808080">22562 6:cpuset:/test?5:freezer:/ root /sbin/plymouthd --mode=boot --attach-to-session</span>
<span style="color: #808080">22593 6:cpuset:/test?5:freezer:/ root /usr/sbin/sshd -D</span>
<span style="color: #808080">22594 6:cpuset:/test?5:freezer:/ syslog rsyslogd -c5</span>
<span style="color: #808080">22617 6:cpuset:/test?5:freezer:/ root sshd: root@pts/1 </span>
<span style="color: #808080">22630 6:cpuset:/test?5:freezer:/ root -bash</span>
<span style="color: #808080">22715 6:cpuset:/sysdefault?5:fre root grep --color=auto cpuset:/test</span>
<span style="color: #000080; font-weight: bold">#</span> <span style="color: #408080; font-style: italic"># это процессы из контейнера, обратите внимание на PID</span>
<span style="color: #000080; font-weight: bold">#</span> <span style="color: #408080; font-style: italic"># логинимся в контейнер</span>
<span style="color: #000080; font-weight: bold">#</span> ssh root@192.168.122.190
<span style="color: #808080">root@192.168.122.190's password: </span>
<span style="color: #808080">Welcome to Ubuntu 11.10 (GNU/Linux 3.1.3-030103-generic x86_64)</span>
<span style="color: #808080"> * Documentation: https://help.ubuntu.com/</span>
<span style="color: #808080">Last login: Thu Jan 12 22:08:53 2012 from 192.168.122.1</span>
<span style="color: #000080; font-weight: bold">root@test:~#</span> <span style="color: #408080; font-style: italic"># мы в контейнере</span>
<span style="color: #000080; font-weight: bold">root@test:~#</span> ps -eo pid,cgroup,user,args
<span style="color: #808080"> PID CGROUP USER COMMAND</span>
<span style="color: #808080"> 1 6:cpuset:/test?5:freezer:/ root /sbin/init</span>
<span style="color: #808080"> 11 6:cpuset:/test?5:freezer:/ root /sbin/plymouthd --mode=boot --attach-to-session</span>
<span style="color: #808080"> 42 6:cpuset:/test?5:freezer:/ root /usr/sbin/sshd -D</span>
<span style="color: #808080"> 43 6:cpuset:/test?5:freezer:/ syslog rsyslogd -c5</span>
<span style="color: #808080"> 49 6:cpuset:/test?5:freezer:/ root sshd: root@pts/1 </span>
<span style="color: #808080"> 61 6:cpuset:/test?5:freezer:/ root -bash</span>
<span style="color: #808080"> 81 6:cpuset:/test?5:freezer:/ root ps -eo pid,cgroup,user,args</span>
<span style="color: #000080; font-weight: bold">#</span> <span style="color: #408080; font-style: italic"># PID виртуализированы, а вот cgroup - нет</span>
<span style="color: #000080; font-weight: bold">root@test:~#</span> ip addr
<span style="color: #808080">24: test_vm: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000</span>
<span style="color: #808080"> link/ether 00:44:01:61:78:22 brd ff:ff:ff:ff:ff:ff</span>
<span style="color: #808080"> inet 192.168.122.190/24 brd 192.168.122.255 scope global test_vm</span>
<span style="color: #808080"> inet6 fe80::244:1ff:fe61:7822/64 scope link </span>
<span style="color: #808080"> valid_lft forever preferred_lft forever</span>
<span style="color: #808080">26: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN </span>
<span style="color: #808080"> link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00</span>
<span style="color: #808080"> inet 127.0.0.1/8 scope host lo</span>
<span style="color: #808080"> inet6 ::1/128 scope host </span>
<span style="color: #808080"> valid_lft forever preferred_lft forever</span>
<span style="color: #000080; font-weight: bold">#</span> <span style="color: #408080; font-style: italic"># сетевые интерфейсы тоже только те, что нужно</span>
</pre></div></span><span style="line-height:100%;display:none" id="6bf4b7ac40c211e192b4bc7737dae05d"><pre><font face="courier"># lxc-start -d -n test
# lxc-ls
kvm_in_lxc oneiric oneiric_br test # вообще все доступные контейнеры
test
# ip addr show
# #.....
# # это сетевой адаптер, ведущий в контейнер
22: vethcSEf3D: <BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master virbr0 state UP qlen 1000
link/ether d2:ea:5e:ba:3f:1b brd ff:ff:ff:ff:ff:ff
inet6 fe80::d0ea:5eff:feba:3f1b/64 scope link
valid_lft forever preferred_lft forever
# brctl show
bridge name bridge id STP enabled interfaces
br0 8000.000000000000 no
virbr0 8000.d2ea5eba3f1b yes vethcSEf3D
# ps -eo pid,cgroup,user,args | grep cpuset:/test
# # показываем все процессы с информацией о cgroup
# # и фильтруем только те, что относятся в группе test типа cpuset
22526 6:cpuset:/test?5:freezer:/ root /sbin/init
22562 6:cpuset:/test?5:freezer:/ root /sbin/plymouthd --mode=boot --attach-to-session
22593 6:cpuset:/test?5:freezer:/ root /usr/sbin/sshd -D
22594 6:cpuset:/test?5:freezer:/ syslog rsyslogd -c5
22617 6:cpuset:/test?5:freezer:/ root sshd: root@pts/1
22630 6:cpuset:/test?5:freezer:/ root -bash
22715 6:cpuset:/sysdefault?5:fre root grep --color=auto cpuset:/test
# # это процессы из контейнера, обратите внимание на PID
# # логинимся в контейнер
# ssh root@192.168.122.190
root@192.168.122.190's password:
Welcome to Ubuntu 11.10 (GNU/Linux 3.1.3-030103-generic x86_64)
* Documentation: https://help.ubuntu.com/
Last login: Thu Jan 12 22:08:53 2012 from 192.168.122.1
root@test:~# # мы в контейнере
root@test:~# ps -eo pid,cgroup,user,args
PID CGROUP USER COMMAND
1 6:cpuset:/test?5:freezer:/ root /sbin/init
11 6:cpuset:/test?5:freezer:/ root /sbin/plymouthd --mode=boot --attach-to-session
42 6:cpuset:/test?5:freezer:/ root /usr/sbin/sshd -D
43 6:cpuset:/test?5:freezer:/ syslog rsyslogd -c5
49 6:cpuset:/test?5:freezer:/ root sshd: root@pts/1
61 6:cpuset:/test?5:freezer:/ root -bash
81 6:cpuset:/test?5:freezer:/ root ps -eo pid,cgroup,user,args
# # PID виртуализированы, а вот cgroup - нет
root@test:~# ip addr
24: test_vm: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
link/ether 00:44:01:61:78:22 brd ff:ff:ff:ff:ff:ff
inet 192.168.122.190/24 brd 192.168.122.255 scope global test_vm
inet6 fe80::244:1ff:fe61:7822/64 scope link
valid_lft forever preferred_lft forever
26: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
# # сетевые интерфейсы тоже только те, что нужно</font></pre></span></span><p style="text-indent:20px"> В итоге мы получили изолированную виртуальную машину. Ради интереса - по информации от <b>free</b> весь контейнер вместе с залогиненным ssh клиентом занимает "аж" 4М ОЗУ. Останавливается контейнер через 'lxc-stop'/'lxc-destroy'.</p><br><h3>Хранение образов</h3><p style="text-indent:20px"> О файловой системе: LXC критикуют за отсутствие дисковых квот для контейнеров, но эта проблема легко решается с помощью LVM, а возможность overcommita обеспечивает <a href="http://kernelnewbies.org/Linux_3.2#head-782dd8358611718f0d8468ee7034c760ba5a20d3">thin provisioning</a>, который появился в 3.2 ядре. А вот возможность копирования при записи, которая очень полезна для VM, требует дополнительных телодвижений. Хочется то же, что qcow2 формат дает виртуальным машинам kvm/xen - вместо полного образа на каждую VM - один большой базовый образ с установленной системой и множество маленьких diff'ов, по одному на виртуалку.</p><ul><li><a href="http://koder-ua.blogspot.com/2012/01/libvirt-co-3.html">использовать</a> qcow2 через qemu-nbd<li>модифицируемые снимки а btrfs<li>модифицируемые снимки в LVM2<li>наслаиваемые файловые системы - aufs</ul><p style="text-indent:20px"> Первый вариант достаточно простой для тех, кто уже использовал qcow2 в kvm/xen, но неудобен по производительности. Второй и третий вариант похожи - и lvm2 и btrfs умеют создавать модифицируемые снимки, описывающие состояние файловой системы на некоторый момент времени. На каждую виртуальную машину можно делать отдельный снимок с базового образа. При этом все изменения будут записываться в снимок, а оригинальный образ модифицироваться не будет. В случае с LVM также можно модифицировать оригинальный образ, не затрагивая снимки.</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="6bf50fd640c211e192b4bc7737dae05d" objtohide2="6bf5115240c211e192b4bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="6bf50fd640c211e192b4bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">#</span> lvcreate -s -n disk-snapshot /dev/vg0/disk -L 5G
</pre></div></span><span style="line-height:100%;display:none" id="6bf5115240c211e192b4bc7737dae05d"><pre><font face="courier"># lvcreate -s -n disk-snapshot /dev/vg0/disk -L 5G</font></pre></span><p style="text-indent:20px"> Эта команда создает снимок /dev/vg0/disk-snapshot с LVM раздела /dev/vg0/disk, где 5Гб места зарезервировано под хранение измененных, по отношению к /dev/vg0/disk, блоков. В дальнейшем количество свободного месте можно изменить командой <b>lvextend</b>. Если на /dev/vg0/disk была установлена система, то, после монтирования, /dev/vg0/disk-snapshot может использоваться для старта виртуалки.</p><p style="text-indent:20px"> Еще один вариант - наслаиваемые файловые системы - aufs и другие, я рассмотрю только aufs. Она позволяет примонтировать в одну точку несколько файловых систем, называемых ветками. В отличии от стандартного поведения, когда смонтированная позднее файловая система полностью закрывает смонтированную ранее, aufs позволяет "видеть" нижние ветки, если файлы из них не были перекрыты файлами с такими-же именами в более поздних ветках. При этом чтение будет производиться из самой верхней ветки, имеющей данный файл, а запись - в зависимости от настроек при монтировании. Если ветка, в которой найден необходимый файл, защищена от записи, то файл будет скопирован в первую вверх ветвь, в которую можно писать. Фактически это cow на уровне файлов, а не блоков. Из очевидные минусов - даже небольшие изменения объемного файла приведут к его полному дублированию.</p><p style="text-indent:20px"> aufs не управляет хранением файлов на блочных устройствах, а использует для этого драйверы файловых систем веток, перераспределяя между ними запросы и копируя при необходимости файлы. Так что aufs монтируют не блочные устройства, а папки.</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="6bf5509a40c211e192b4bc7737dae05d" objtohide2="6bf5520240c211e192b4bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="6bf5509a40c211e192b4bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">#</span> mkdir /tmp/rw
<span style="color: #000080; font-weight: bold">#</span> mount -t aufs -o <span style="color: #19177C">br</span><span style="color: #666666">=</span>/tmp/rw<span style="color: #666666">=</span>rw:/home/user<span style="color: #666666">=</span>ro none /tmp/aufs
</pre></div></span><span style="line-height:100%;display:none" id="6bf5520240c211e192b4bc7737dae05d"><pre><font face="courier"># mkdir /tmp/rw
# mount -t aufs -o br=/tmp/rw=rw:/home/user=ro none /tmp/aufs</font></pre></span><p style="text-indent:20px"> Эта команда монтирует папки /home/user и /tmp/rw в папку /tmp/aufs. /tmp/user монтируется в режиме "только для чтения", так в /tmp/aufs будет видна домашняя папка, но все изменения будут попадать в /tmp/rw. P.S. aufs не включена в основную ветку ядра, и модулей для 3.1 в ubuntu еще нет, так что пользователям последней ubuntu этот вариант попробовать не удастся.</p><br><h3>libvirt</h3><p style="text-indent:20px"> Формально libvirt <a href="http://libvirt.org/drvlxc.html">поддерживает</a> LXC, но работоспособность этого решения и полнота поддержки оставляет желать лучшего. Libvirt не использует стандартные утилиты LXC, а создает контейнеры самостоятельно. <s>В итоге на некоторых комбинациях ядер/libvirt</s> <s>запущенные контейнеры оказываются полностью не работоспособными</s> <s>(хотя lxc-start работает "на ура")</s> Нашел у себя ошибку, так что остался без примера:</p><pre><font face="courier"> #/var/log/libvirt/lxc/test.log
......
01:48:38.544: 28327: error : lxcFdForward:289 : read of fd 9 failed: Input/output error
... и тут еще сотни тысяч таких записей .......</font></pre><p style="text-indent:20px"> Фикс для этой ошибки вроде как был внесен <a href="https://lists.ubuntu.com/archives/ubuntu-server-bugs/2011-February/051503.html">еще год назад</a>, но она раз за разом проявляется снова. Также контейнер, запущенный с помощью lxc-start при попытке последующих запусков из libvirt вообще не создает сетевые интерфейсы. libvirt полуофициально не поддерживает старт с /sbin/init - необходимо использовать собственные скрипты; нет поддержки нормального проброса устройств, установки адреса и параметров на сетевой адаптер и др. Все это, естественно, следует читать как "у меня не получилось", хотя масса не отвеченных <a href="http://www.redhat.com/archives/libvir-list/2010-January/msg00218.html">вопросов</a> говорит, что не только у меня.</p><p style="text-indent:20px">Пример конфигурации для запуска lxc из libvirt:</p><a hided_text="Показать код" visible_text="Скрыть код" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="hidder" objtohide="6bf581c840c211e192b4bc7737dae05d">Показать код</a><br><span style="display:none" id="6bf581c840c211e192b4bc7737dae05d"><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="6bf5f23e40c211e192b4bc7737dae05d" objtohide2="6bf5f3b040c211e192b4bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="6bf5f23e40c211e192b4bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #BC7A00"><?xml version="1.0" encoding="utf-8" ?></span>
<span style="color: #008000; font-weight: bold"><domain</span> <span style="color: #7D9029">type=</span><span style="color: #BA2121">"lxc"</span><span style="color: #008000; font-weight: bold">></span>
<span style="color: #008000; font-weight: bold"><name></span>test<span style="color: #008000; font-weight: bold"></name></span>
<span style="color: #008000; font-weight: bold"><memory></span>1048576<span style="color: #008000; font-weight: bold"></memory></span>
<span style="color: #008000; font-weight: bold"><os></span>
<span style="color: #008000; font-weight: bold"><type></span>exe<span style="color: #008000; font-weight: bold"></type></span>
<span style="color: #008000; font-weight: bold"><init></span>/sbin/init<span style="color: #008000; font-weight: bold"></init></span>
<span style="color: #008000; font-weight: bold"></os></span>
<span style="color: #008000; font-weight: bold"><vcpu></span>1<span style="color: #008000; font-weight: bold"></vcpu></span>
<span style="color: #008000; font-weight: bold"><clock</span> <span style="color: #7D9029">offset=</span><span style="color: #BA2121">"utc"</span><span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><on_poweroff></span>destroy<span style="color: #008000; font-weight: bold"></on_poweroff></span>
<span style="color: #008000; font-weight: bold"><on_reboot></span>restart<span style="color: #008000; font-weight: bold"></on_reboot></span>
<span style="color: #008000; font-weight: bold"><on_crash></span>destroy<span style="color: #008000; font-weight: bold"></on_crash></span>
<span style="color: #008000; font-weight: bold"><devices></span>
<span style="color: #008000; font-weight: bold"><emulator></span>/usr/lib/libvirt/libvirt_lxc<span style="color: #008000; font-weight: bold"></emulator></span>
<span style="color: #008000; font-weight: bold"><filesystem</span> <span style="color: #7D9029">type=</span><span style="color: #BA2121">"mount"</span><span style="color: #008000; font-weight: bold">></span>
<span style="color: #008000; font-weight: bold"><source</span> <span style="color: #7D9029">dir=</span><span style="color: #BA2121">"/var/lib/lxc/test/rootfs"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><target</span> <span style="color: #7D9029">dir=</span><span style="color: #BA2121">"/"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"></filesystem></span>
<span style="color: #008000; font-weight: bold"><interface</span> <span style="color: #7D9029">type=</span><span style="color: #BA2121">"network"</span><span style="color: #008000; font-weight: bold">></span>
<span style="color: #008000; font-weight: bold"><source</span> <span style="color: #7D9029">network=</span><span style="color: #BA2121">"default"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><forward</span> <span style="color: #7D9029">mode=</span><span style="color: #BA2121">"nat"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><target</span> <span style="color: #7D9029">dev=</span><span style="color: #BA2121">"vnet7"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><mac</span> <span style="color: #7D9029">address=</span><span style="color: #BA2121">"00:44:01:61:78:22"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"></interface></span>
<span style="color: #008000; font-weight: bold"><console</span> <span style="color: #7D9029">type=</span><span style="color: #BA2121">"pty"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"></devices></span>
<span style="color: #008000; font-weight: bold"></domain></span>
</pre></div></span><span style="line-height:100%;display:none" id="6bf5f3b040c211e192b4bc7737dae05d"><pre><font face="courier"><?xml version="1.0" encoding="utf-8" ?>
<domain type="lxc">
<name>test</name>
<memory>1048576</memory>
<os>
<type>exe</type>
<init>/sbin/init</init>
</os>
<vcpu>1</vcpu>
<clock offset="utc"/>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>destroy</on_crash>
<devices>
<emulator>/usr/lib/libvirt/libvirt_lxc</emulator>
<filesystem type="mount">
<source dir="/var/lib/lxc/test/rootfs" />
<target dir="/" />
</filesystem>
<interface type="network">
<source network="default" />
<forward mode="nat" />
<target dev="vnet7" />
<mac address="00:44:01:61:78:22" />
</interface>
<console type="pty" />
</devices>
</domain></font></pre></span></span><p style="text-indent:20px">Записываем это в %CONTAINER_ROOT%/etc/init/mylxc.conf:</p><pre><font face="courier"> # %CONTAINER_ROOT%/etc/init/mylxc.conf
description "startup ifconfig"
start on filesystem or runlevel [2345]
pre-start script
# стартовый ip для адаптеров
now_ip=192.168.122.190
all_links=`ip link | grep '^[0-9][0-9]*:' | awk -F: '{print $2}' | grep -v lo`
for eth in $all_links ; do
ifconfig $eth $now_ip/24 up
now_ip=`echo $now_ip | awk -F. '{print $1 "." $2 "." $3 "." $4 + 1}'`
done
end script
exec ps</font></pre><p style="text-indent:20px">Запуск контейнера через virsh:</p><a hided_text="Показать код" visible_text="Скрыть код" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="hidder" objtohide="6bf6111a40c211e192b4bc7737dae05d">Показать код</a><br><span style="display:none" id="6bf6111a40c211e192b4bc7737dae05d"><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="6bf6506c40c211e192b4bc7737dae05d" objtohide2="6bf651d440c211e192b4bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="6bf6506c40c211e192b4bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">#</span> virsh -c lxc:/// create lxc.xml
<span style="color: #808080">Domain test created from lxc.xml</span>
<span style="color: #000080; font-weight: bold">#</span> virsh -c lxc:/// list
<span style="color: #808080"> Id Name State</span>
<span style="color: #808080">----------------------------------</span>
<span style="color: #808080">21834 test running</span>
<span style="color: #000080; font-weight: bold">#</span> ip addr show
<span style="color: #808080">.......</span>
<span style="color: #808080">10: virbr0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP </span>
<span style="color: #808080"> link/ether 8e:05:b2:bb:be:f2 brd ff:ff:ff:ff:ff:ff</span>
<span style="color: #808080"> inet 192.168.122.1/24 brd 192.168.122.255 scope global virbr0</span>
<span style="color: #808080">16: veth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master virbr0 state UP qlen 1000</span>
<span style="color: #808080"> link/ether 8e:05:b2:bb:be:f2 brd ff:ff:ff:ff:ff:ff</span>
<span style="color: #808080"> inet6 fe80::8c05:b2ff:febb:bef2/64 scope link </span>
<span style="color: #808080"> valid_lft forever preferred_lft forever</span>
<span style="color: #000080; font-weight: bold">#</span> virsh -c lxc:// list
<span style="color: #808080"> Id Name State</span>
<span style="color: #808080">----------------------------------</span>
<span style="color: #808080">13261 test11 running</span>
<span style="color: #000080; font-weight: bold">#</span> ssh root@192.168.122.190
<span style="color: #808080">root@192.168.122.190's password: </span>
<span style="color: #808080">root@192.168.122.190's password: </span>
<span style="color: #808080">Welcome to Ubuntu 11.10 (GNU/Linux 3.1.3-030103-generic x86_64)</span>
<span style="color: #808080"> * Documentation: https://help.ubuntu.com/</span>
<span style="color: #808080">Last login: Tue Jan 17 03:50:29 2012 from 192.168.122.1</span>
<span style="color: #000080; font-weight: bold">root@test11:~#</span> <span style="color: #408080; font-style: italic"># мы в контейнере</span>
</pre></div></span><span style="line-height:100%;display:none" id="6bf651d440c211e192b4bc7737dae05d"><pre><font face="courier"># virsh -c lxc:/// create lxc.xml
Domain test created from lxc.xml
# virsh -c lxc:/// list
Id Name State
----------------------------------
21834 test running
# ip addr show
.......
10: virbr0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
link/ether 8e:05:b2:bb:be:f2 brd ff:ff:ff:ff:ff:ff
inet 192.168.122.1/24 brd 192.168.122.255 scope global virbr0
16: veth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master virbr0 state UP qlen 1000
link/ether 8e:05:b2:bb:be:f2 brd ff:ff:ff:ff:ff:ff
inet6 fe80::8c05:b2ff:febb:bef2/64 scope link
valid_lft forever preferred_lft forever
# virsh -c lxc:// list
Id Name State
----------------------------------
13261 test11 running
# ssh root@192.168.122.190
root@192.168.122.190's password:
root@192.168.122.190's password:
Welcome to Ubuntu 11.10 (GNU/Linux 3.1.3-030103-generic x86_64)
* Documentation: https://help.ubuntu.com/
Last login: Tue Jan 17 03:50:29 2012 from 192.168.122.1
root@test11:~# # мы в контейнере</font></pre></span></span><p style="text-indent:20px">libvirt позволяет удобнее програмно контролировать контейнеры, чем lxc-xxx.</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="6c3aa37a40c211e192b4bc7737dae05d" objtohide2="6c3aa53c40c211e192b4bc7737dae05d" >Без подсветки синтаксиса</a><br><span id="6c3aa37a40c211e192b4bc7737dae05d"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #408080; font-style: italic">#!/bin/env python</span>
<span style="color: #408080; font-style: italic"># -*- coding:utf8 -*-</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">time</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">socket</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">libvirt</span>
c <span style="color: #666666">=</span> libvirt<span style="color: #666666">.</span>open(<span style="color: #BA2121">"lxc:///"</span>)
dom <span style="color: #666666">=</span> <span style="color: #008000">open</span>(<span style="color: #BA2121">"lxc.xml"</span>)<span style="color: #666666">.</span>read()
t <span style="color: #666666">=</span> time<span style="color: #666666">.</span>time()
c<span style="color: #666666">.</span>createXML(dom, <span style="color: #666666">0</span>)
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"Time 1"</span>, time<span style="color: #666666">.</span>time() <span style="color: #666666">-</span> t
<span style="color: #008000; font-weight: bold">try</span>:
<span style="color: #008000; font-weight: bold">while</span> <span style="color: #008000">True</span>:
<span style="color: #008000; font-weight: bold">try</span>:
socket<span style="color: #666666">.</span>socket()<span style="color: #666666">.</span>connect((<span style="color: #BA2121">"192.168.122.190"</span>, <span style="color: #666666">22</span>))
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"SSH available after"</span>, time<span style="color: #666666">.</span>time() <span style="color: #666666">-</span> t, <span style="color: #BA2121">"seconds"</span>
<span style="color: #008000; font-weight: bold">break</span>
<span style="color: #008000; font-weight: bold">except</span>:
<span style="color: #408080; font-style: italic"># на самм деле будет спать больше</span>
time<span style="color: #666666">.</span>sleep(<span style="color: #666666">0.001</span>)
<span style="color: #008000; font-weight: bold">finally</span>:
vm <span style="color: #666666">=</span> c<span style="color: #666666">.</span>lookupByName(<span style="color: #BA2121">'test11'</span>)
vm<span style="color: #666666">.</span>destroy()
</pre></div></span><span style="line-height:100%;display:none" id="6c3aa53c40c211e192b4bc7737dae05d"><pre><font face="courier">#!/bin/env python
# -*- coding:utf8 -*-
import time
import socket
import libvirt
c = libvirt.open("lxc:///")
dom = open("lxc.xml").read()
t = time.time()
c.createXML(dom, 0)
print "Time 1", time.time() - t
try:
while True:
try:
socket.socket().connect(("192.168.122.190", 22))
print "SSH available after", time.time() - t, "seconds"
break
except:
# на самм деле будет спать больше
time.sleep(0.001)
finally:
vm = c.lookupByName('test11')
vm.destroy()</font></pre></span><p style="text-indent:20px"> Этот quick-and-dirty скрипт дает время старта контейнера - 0.1±0.02сек, полной загрузки до готовности ssh сервера - 0.9±0.05сек на core i7-2630QM @ 2.00GHz (ноутбучный процессор). Примерно в 1.5 раза меньше времени нужно для старта на Core i5-650 @ 3.20GHz.</p><br><h3>footer</h3><p style="text-indent:20px"> И напоследок о другой стороне контейнеров - запуск отдельных приложений в изолированных окружениях ( a-la <a href="http://en.wikipedia.org/wiki/FreeBSD_jail">FreeBSD jail</a>). Практически бесплатность создания контейнера (порядка десятка системных вызовов) создает интересные возможности. Например можно вынести в контейнеры исполнение потенциально опасного кода, отдельных сервисов и проч. Уже есть реализация подобной идеи - <a href="https://launchpad.net/arkose">arkose</a>. Он позволяет запускать потенциально опасные приложения в отдельных контейнерах, например браузер.</p><p style="text-indent:20px"> Подведем итоги - по сумме характеристик LXC очень серьезный конкурент для классической виртуализации (KVM/XEN). Пока его промышленное применение сдерживается некоторым объемом недоработок (но перспективы весьма радужные), но для "домашнего" использования он уже вполне готов.</p>Ссылки:<br> <a name="LXC"><a href="http://lxc.sourceforge.net/">lxc.sourceforge.net</a></a><br> <a name="сотен"><a href="http://tr.opensuse.org/OpenVZ_virtualization#Density">tr.opensuse.org/OpenVZ_virtualization#Density</a></a><br> <a name="namespaces"><a href="http://eos.aristanetworks.com/2011/06/linux-namespaces-at-arista/">eos.aristanetworks.com/2011/06/linux-namespaces-at-arista</a></a><br> <a name="cgroups"><a href="http://en.wikipedia.org/wiki/Cgroups">en.wikipedia.org/wiki/Cgroups</a></a><br> <a name="capabilities"><a href="http://linux.die.net/man/7/capabilities">linux.die.net/man/7/capabilities</a></a><br> <a name="проблема"><a href="http://en.gentoo-wiki.com/wiki/LXC#MAJOR_Temporary_Problems_with_LXC_-_READ_THIS">en.gentoo-wiki.com/wiki/LXC#MAJOR_Temporary_Problems_with_LXC_-_READ_THIS</a></a><br> <a name="поддержка"><a href="https://code.launchpad.net/~zulcss/nova/nova-lxc">code.launchpad.net/~zulcss/nova/nova-lxc</a></a><br> <a name="примеры"><a href="http://wiki.debian.org/LXC">wiki.debian.org/LXC</a></a><br> <a name="созданию"><a href="http://en.gentoo-wiki.com/wiki/LXC">en.gentoo-wiki.com/wiki/LXC</a></a><br> <a name="эту"><a href="http://habrahabr.ru/blogs/virtualization/130522/">habrahabr.ru/blogs/virtualization/130522</a></a><br> <a name="1"><a href="http://lxc.sourceforge.net/man/lxc.html">lxc.sourceforge.net/man/lxc.html</a></a><br> <a name="2"><a href="http://lxc.teegra.net/">lxc.teegra.net</a></a><br> <a name="debian"><a href="http://nigel.mcnie.name/blog/a-five-minute-guide-to-linux-containers-for-debian">nigel.mcnie.name/blog/a-five-minute-guide-to-linux-containers-for-debian</a></a><br> <a name="ubuntu"><a href="https://help.ubuntu.com/community/LXC">help.ubuntu.com/community/LXC</a></a><br> <a name="3"><a href="http://www.markus-gattol.name/ws/linux_containers.html">www.markus-gattol.name/ws/linux_containers.html</a></a><br> <a name="запуск_X"><a href="http://blog.mraw.org/2011/04/05/Running_X_from_LXC/">blog.mraw.org/2011/04/05/Running_X_from_LXC</a></a><br> <a name="все_есть"><a href="http://libvirt.org/drvlxc.html">libvirt.org/drvlxc.html</a></a><br> <a name="thin_provisioning"><a href="http://kernelnewbies.org/Linux_3.2#head-782dd8358611718f0d8468ee7034c760ba5a20d3">kernelnewbies.org/Linux_3.2#head-782dd8358611718f0d8468ee7034c760ba5a20d3</a></a><br> <a name="использовать"><a href="http://koder-ua.blogspot.com/2012/01/libvirt-co-3.html">koder-ua.blogspot.com/2012/01/libvirt-co-3.html</a></a><br> <a name="lxc_network_performance"><a href="https://www.iip.net.pl/sites/default/files/41/Benchmarking%20the%20performance%20of%20virtualized%20routers.pdf">www.iip.net.pl/sites/default/files/41/Benchmarking%20the%20performance%20of%20virtualized%20routers.pdf</a></a><br> <a name="arkose"><a href="https://launchpad.net/arkose">launchpad.net/arkose</a></a><br> <a name="FreeBSD_jail"><a href="http://en.wikipedia.org/wiki/FreeBSD_jail">en.wikipedia.org/wiki/FreeBSD_jail</a></a><br> <a name="еще_год_назад"><a href="https://lists.ubuntu.com/archives/ubuntu-server-bugs/2011-February/051503.html">lists.ubuntu.com/archives/ubuntu-server-bugs/2011-February/051503.html</a></a><br> <a name="вопросов"><a href="http://www.redhat.com/archives/libvir-list/2010-January/msg00218.html">www.redhat.com/archives/libvir-list/2010-January/msg00218.html</a></a><br> <a name="libvirt_lxc_support"><a href="http://libvirt.org/drvlxc.html">libvirt.org/drvlxc.html</a></a><br> <a name="upstart"><a href="http://upstart.ubuntu.com/">upstart.ubuntu.com</a></a><br> <a name="tcp_offload"><a href="http://en.wikipedia.org/wiki/TCP_offload_engine">en.wikipedia.org/wiki/TCP_offload_engine</a></a><br> <a name="veth"><a href="http://wiki.openvz.org/Virtual_Ethernet_device">wiki.openvz.org/Virtual_Ethernet_device</a></a><br> <a name="тянут"><a href="http://wiki.ncl.cs.columbia.edu/wiki/KVMARM:MainPage">wiki.ncl.cs.columbia.edu/wiki/KVMARM:MainPage</a></a><br><p style="text-indent:20px">Исходники этого и других постов со скриптами лежат тут - <a href="https://github.com/koder-ua/python-lectures">github.com/koder-ua</a>. При использовании их, пожалуйста, ссылайтесь на <a href="http://koder-ua.blogspot.com/">koder-ua.blogspot.com</a>.</p>
<script type="text/javascript">
function on_hidabble_click()
{
var me = $(this);
var hide_id = me.attr("objtohide");
var controlled_object = $('#' + hide_id);
controlled_object.toggle();
if ( controlled_object.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".hidder").click(on_hidabble_click);
function on_double_hidabble_click()
{
var me = $(this);
var hide_id1 = me.attr("objtohide1");
var hide_id2 = me.attr("objtohide2");
var controlled_object1 = $('#' + hide_id1);
var controlled_object2 = $('#' + hide_id2);
controlled_object1.toggle();
controlled_object2.toggle();
if ( controlled_object1.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".dhidder").click(on_double_hidabble_click);
</script>Anonymoushttp://www.blogger.com/profile/06645078574760734586noreply@blogger.com4tag:blogger.com,1999:blog-1174489715777430743.post-52201160404321766252012-01-12T04:58:00.000+02:002012-01-15T09:25:15.171+02:00libvirt & Co. Облако "на коленке". Часть 3 - Дисковые образы<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<p style="text-indent:20px"> Следующий шаг - разобраться с дисковыми образами виртуальных машин. Основные вопросы - как хранить, где хранить и как модифицировать.</p><br><h2>Как хранить</h2><ul><li><a href="http://ru.wikipedia.org/wiki/RAW_%28%D1%84%D0%BE%D1%80%D0%BC%D0%B0%D1%82_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%29">raw</a> - самый простой формат, прямая копия данных с диска.<li><a href="http://en.wikipedia.org/wiki/Qcow#qcow2">qcow2</a> - основной формат qemu. Обладает большим спектром возможностей<li><a href="http://en.wikipedia.org/wiki/VDI_%28file_format%29#Virtual_Disk_Image">vdi</a> - формат, используемый <a href="https://www.virtualbox.org/">VirtualBox'ом</a><li><a href="http://en.wikipedia.org/wiki/VMDK">vmdk</a> - <a href="http://www.vmware.com/">VMware</a> формат<li>cow, qcow - каменный век</ul><p style="text-indent:20px"> Обсуждать <a href="http://ru.wikipedia.org/wiki/RAW_%28%D1%84%D0%BE%D1%80%D0%BC%D0%B0%D1%82_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%29">raw</a> смысла не имеет - просто диск байт по байту. qcow2 самый распространенный и функциональный формат виртуальных дисков для qemu/xen.</p><ul><li>содержит только те кластеры, в которые были записаны данные. Т.е. можно создать образ диска размером в 10G, но реальный размер файла будет расти только при фактической записи на диск.</ul><a name='more'></a><ul><li>поддерживает "наслоение" файлов. Qcow2 файл позволяет хранить только изменения относительно другого файла, представляющего базовый образ (backing store). При таком режиме базовый файл никогда не модифицируется - запись идет в "верхний" qcow2 файл, а чтение происходит из qcow2, если соответствующий кластер был модифицирован и записан в qcow2 или из базового в противном случае. Это позволяет, например, запустить несколько виртуальных машин на основе одного образа. После подготовки базового образа к нему подключается параллельно несколько qcow2 файлов и на их основе запускаются vm, каждая из которых "идет своей дорогой". Кроме экономии места на диске это также улучшает кеширование. Поддерживается перенесение(commit) изменений назад в базовый файл и некоторые другие возможности. Базовый файл может быть в любом формате - raw, qcow2, etc.<li>шифрование<li>компрессия (только для чтения, запись будет производиться в распакованный сектор)<li>можно делать qcow2 файлы с пред выделенным местом для метаданных, это повышает быстродействие в случае если ожидаются интенсивные дисковые операции.</ul><p style="text-indent:20px">Базовая утилита для работы с этими форматами - <a href="http://linux.die.net/man/1/qemu-img">qemu-img</a>.</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="22b9ea3c3cc911e186be14feb5b819a0" objtohide2="22ba23d03cc911e186be14feb5b819a0" >Без подсветки синтаксиса</a><br><span id="22b9ea3c3cc911e186be14feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">$</span> qemu-img create -f raw img.raw 10G
<span style="color: #808080">Formatting 'img.raw', fmt=raw size=10737418240 </span>
<span style="color: #000080; font-weight: bold">$</span> qemu-img create -f qcow2 img.qcow2 10G
<span style="color: #808080">Formatting 'img.qcow2', fmt=qcow2 size=10737418240 encryption=off </span>
<span style="color: #808080"> cluster_size=0</span>
<span style="color: #000080; font-weight: bold">$</span> qemu-img create -f qcow2 -o <span style="color: #19177C">preallocation</span><span style="color: #666666">=</span>metadata img_m.qcow2 10G
<span style="color: #808080">Formatting 'img.qcow2', fmt=qcow2 size=10737418240 encryption=off </span>
<span style="color: #808080"> cluster_size=0 preallocation='metadata'</span>
<span style="color: #000080; font-weight: bold">$</span> ls -lhsS img*
<span style="color: #808080">1.8M -rw-r--r-- 1 koder koder 11G 2011-12-24 21:37 img_m.qcow2</span>
<span style="color: #808080"> 0 -rw-r--r-- 1 koder koder 10G 2011-12-24 21:34 img.raw</span>
<span style="color: #808080">136K -rw-r--r-- 1 koder koder 193K 2011-12-24 21:37 img.qcow2</span>
</pre></div></span><span style="line-height:100%;display:none" id="22ba23d03cc911e186be14feb5b819a0"><pre><font face="courier">$ qemu-img create -f raw img.raw 10G
Formatting 'img.raw', fmt=raw size=10737418240
$ qemu-img create -f qcow2 img.qcow2 10G
Formatting 'img.qcow2', fmt=qcow2 size=10737418240 encryption=off
cluster_size=0
$ qemu-img create -f qcow2 -o preallocation=metadata img_m.qcow2 10G
Formatting 'img.qcow2', fmt=qcow2 size=10737418240 encryption=off
cluster_size=0 preallocation='metadata'
$ ls -lhsS img*
1.8M -rw-r--r-- 1 koder koder 11G 2011-12-24 21:37 img_m.qcow2
0 -rw-r--r-- 1 koder koder 10G 2011-12-24 21:34 img.raw
136K -rw-r--r-- 1 koder koder 193K 2011-12-24 21:37 img.qcow2</font></pre></span><p style="text-indent:20px"> Первая запись в ls это реальный размер файла на диске, а шестая - заявленный размер. В данном случае мы видим <a href="http://administratosphere.wordpress.com/2008/05/23/sparse-files-what-why-and-how/">sparse</a> файлы в действии. Все основные файловые системы в linux поддерживают выделение реального места на диске под файл при записи реальных данных и такое поведение установлено по умолчанию. В отличии от raw файлов qcow2 файлы со старта содержат управляющие структуры, так что их размен не нулевой. Впрочем при копировании/архивации и др. raw файлов все-же придется обрабатывать полный размер, в отличии от qcow2.</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="22bb0d723cc911e186be14feb5b819a0" objtohide2="22bb6df83cc911e186be14feb5b819a0" >Без подсветки синтаксиса</a><br><span id="22bb0d723cc911e186be14feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">$</span> qemu-img info img.raw <span style="color: #408080; font-style: italic"># информация о файле </span>
<span style="color: #808080">image: img.raw</span>
<span style="color: #808080">file format: raw</span>
<span style="color: #808080">virtual size: 10G (10737418240 bytes)</span>
<span style="color: #808080">disk size: 0</span>
<span style="color: #000080; font-weight: bold">$</span> qemu-img info img.qcow2
<span style="color: #808080">image: img.qcow2</span>
<span style="color: #808080">file format: qcow2</span>
<span style="color: #808080">virtual size: 10G (10737418240 bytes)</span>
<span style="color: #808080">disk size: 136K</span>
<span style="color: #808080">cluster_size: 65536</span>
</pre></div></span><span style="line-height:100%;display:none" id="22bb6df83cc911e186be14feb5b819a0"><pre><font face="courier">$ qemu-img info img.raw # информация о файле
image: img.raw
file format: raw
virtual size: 10G (10737418240 bytes)
disk size: 0
$ qemu-img info img.qcow2
image: img.qcow2
file format: qcow2
virtual size: 10G (10737418240 bytes)
disk size: 136K
cluster_size: 65536</font></pre></span><p style="text-indent:20px"> Сделаем "наслаивающиеся" qcow2 файлы. Размер для нового файла указывать не нужно - так как он должен быть такого-же размера, как и базовый образ:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="22bc665e3cc911e186be14feb5b819a0" objtohide2="22bc6f823cc911e186be14feb5b819a0" >Без подсветки синтаксиса</a><br><span id="22bc665e3cc911e186be14feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">$</span> qemu-img create -b img.qcow2 -f qcow2 -F qcow2 img_bs.qcow2
<span style="color: #808080">Formatting 'img_bs.qcow2', fmt=qcow2 size=10737418240 </span>
<span style="color: #808080">backing_file='img.qcow2' backing_fmt='qcow2' encryption=off cluster_size=0 </span>
<span style="color: #000080; font-weight: bold">$</span> qemu-img info img_bs.qcow2
<span style="color: #808080">image: img_bs.qcow2</span>
<span style="color: #808080">file format: qcow2</span>
<span style="color: #808080">virtual size: 10G (10737418240 bytes)</span>
<span style="color: #808080">disk size: 136K</span>
<span style="color: #808080">cluster_size: 65536</span>
<span style="color: #808080">backing file: img.qcow2 (actual path: img.qcow2)</span>
</pre></div></span><span style="line-height:100%;display:none" id="22bc6f823cc911e186be14feb5b819a0"><pre><font face="courier">$ qemu-img create -b img.qcow2 -f qcow2 -F qcow2 img_bs.qcow2
Formatting 'img_bs.qcow2', fmt=qcow2 size=10737418240
backing_file='img.qcow2' backing_fmt='qcow2' encryption=off cluster_size=0
$ qemu-img info img_bs.qcow2
image: img_bs.qcow2
file format: qcow2
virtual size: 10G (10737418240 bytes)
disk size: 136K
cluster_size: 65536
backing file: img.qcow2 (actual path: img.qcow2)</font></pre></span><p style="text-indent:20px"> Базовый файл может быть любого формата, но создаваемый с backing store файл может быть только qcow2. qemu-img также Поддерживает все основные операции - изменение размера образа, смена базового файла, проверка целостности и создание/работа со снимками.</p><p style="text-indent:20px"> Остальные форматы интересны только при использовании соответствующих систем виртуализации и рассматривать их в контексте qemu/kvm/xen смысле не имеет. qemu-img позволяет конвертировать файлы между всеми форматами, описанными сверху. Единственное исключение - разбитые на несколько файлов диски vmWare. Для работы сначала их нужно объединить в один файл с помошью <a href="http://www.vmware.com/support/ws45/doc/disks_vdiskmanager_eg_ws.html">vdiskmanager</a>, который входит в поставку vmWare Workstation for linux. Таким образом можно с kvm/xen использовать широко доступные в интернете виртуальные машины для vmWare.</p><p style="text-indent:20px"> Подключение этих файлов к виртуальным машинам <a href="http://libvirt.org/formatdomain.html#elementsDisks">libvirt-disk</a>, подключение CD делается примерно так-же, как и raw файлов.</p><br><h2>Где хранить</h2><p style="text-indent:20px"> Для локального хранения есть два основных варианта - файл или диск/раздел/<a href="http://en.wikipedia.org/wiki/Logical_Volume_Manager_%28Linux%29">LVM</a> устройством ( всем линуксоидам, кто еще не освоил LVM - очень советую это <a href="http://habrahabr.ru/blogs/linux/67283/">сделать</a>, это мощная система управления дисковым пространством; так-же полезное чтиво - <a href="http://sources.redhat.com/dm/">devmapper</a>, на нем и работают в linux LVM2, всякие software RAID & Co). Создание образа на разделе/LVM принципиально не отличается от файла на FS, нужно только указывать qemu-img format не raw а host_device:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="22bd0e243cc911e186be14feb5b819a0" objtohide2="22bd11a83cc911e186be14feb5b819a0" >Без подсветки синтаксиса</a><br><span id="22bd0e243cc911e186be14feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">#</span> qemu-img create -f host_device /dev/vm_images/img1 10G
</pre></div></span><span style="line-height:100%;display:none" id="22bd11a83cc911e186be14feb5b819a0"><pre><font face="courier"># qemu-img create -f host_device /dev/vm_images/img1 10G</font></pre></span><p style="text-indent:20px">/dev/vm_images/img1 - это логический раздел img1 в группе разделов vm_images (LVM). После этого можно передать его в libvirt:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="22bd83c23cc911e186be14feb5b819a0" objtohide2="22bd85523cc911e186be14feb5b819a0" >Без подсветки синтаксиса</a><br><span id="22bd83c23cc911e186be14feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold"><disk</span> <span style="color: #7D9029">type=</span><span style="color: #BA2121">'block'</span> <span style="color: #7D9029">device=</span><span style="color: #BA2121">'disk'</span><span style="color: #008000; font-weight: bold">></span>
<span style="color: #008000; font-weight: bold"><driver</span> <span style="color: #7D9029">name=</span><span style="color: #BA2121">'qemu'</span> <span style="color: #7D9029">type=</span><span style="color: #BA2121">'raw'</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><source</span> <span style="color: #7D9029">dev=</span><span style="color: #BA2121">'/dev/vm_images/img1'</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><target</span> <span style="color: #7D9029">dev=</span><span style="color: #BA2121">'vda'</span> <span style="color: #7D9029">bus=</span><span style="color: #BA2121">'virtio'</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"></disk></span>
</pre></div></span><span style="line-height:100%;display:none" id="22bd85523cc911e186be14feb5b819a0"><pre><font face="courier"><disk type='block' device='disk'>
<driver name='qemu' type='raw' />
<source dev='/dev/vm_images/img1' />
<target dev='vda' bus='virtio' />
</disk></font></pre></span><p style="text-indent:20px"> Обратите внимание на <b><target dev="vda" bus="virtio" /></b> вместо него можно использовать <b><target dev="hda" bus="ide" /></b>, но <a href="http://wiki.libvirt.org/page/Virtio">virtio</a> дает значительный прирост производительности. Virtio это система частичной паравиртуализации для kvm. Она позволяет установить в гостевой системе набор драйверов, которые будут обслуживать виртуальные устройства, передавая в гипервизор высокоуровневые запросы и избавляя гостевую систему от превращения их (запросов) в общение с портами и DMA, а гипервизор от имитации низкоуровневых протоколов. Особенно хорошо virtio ускоряет сеть и дисковые операции. В своем роде это аналог <a href="http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=340">vmware tools</a>. Все современные ядра linux поддерживают virtio без дополнительных телодвижений, но, например, ядро из debian lenny - нет. Так что образ debian lenny просто не загрузится с virtio устройства (есть virtio драйвера и для windows).</p><p style="text-indent:20px"> Это были локальные способы, кроме них есть еще вагон и маленькая тележка сетевых хранилищ (причем они могут быть использованы и как набор блочных устройств в режиме "по одному на VM", так и в режиме удаленного диска с файловой системой). Очевидными преимуществами сетевых хранилищ являются централизованное управление дисками для всего облака и облегчение миграции. Для небольших облаков они не так интересны, так что пробежимся по некоторым из ниx вскользь.</p><p style="text-indent:20px"> <a href="http://en.wikipedia.org/wiki/ATA_over_Ethernet">AOE</a> - (ATA Over Ethernet) реализация ATA протокола поверх ethernet. Фактически ATA команды напрямую передаются удаленному винчестеру. Из преимуществ - скорость. Дисковый массив и все рабочие станции должны быть в одном сегменте локальной сети (нет маршрутизации). Основной производитель устройств - <a href="http://www.coraid.com/">Coraid</a>. Впрочем набор утилит открыт и можно достаточно просто самостоятельно сделать AOE сервер <a href="http://habrahabr.ru/blogs/linux/64350/">примеров</a> <a href="http://www.howtoforge.com/how-to-build-a-low-cost-san">в</a> <a href="http://www.linux-mag.com/id/2028/">сети</a> <a href="http://www.linuxfordevices.com/c/a/News/ATAoverEthernet-enables-lowcost-Linuxoriented-SAN/">предостаточно</a>.</p><p style="text-indent:20px"> <a href="http://en.wikipedia.org/wiki/ISCSI">iSCSI</a> - SCSI поверх IP. Многие системы удаленного хранения работают именно на этом проколе. Маршрутизируемый (дисковый массив и рабочие станции могут располагаться в сети где угодно). Возможностей больше, чем у AOE, скорость - ниже.</p><p style="text-indent:20px"> <a href="http://nbd.sourceforge.net/">nbd</a> - Network Block Device. Протокол для раздачи блочных устройств по сети, подробнее обсуждается ниже на примере qemu-nbd.</p><p style="text-indent:20px"> <a href="http://ceph.newdream.net/wiki/Rbd">rbd</a> - Rados block device. Часть проекта <a href="http://ceph.newdream.net/">ceph</a> - распределенного сетевого хранилища и файловой системы (из интересного - есть прямая привязка к qemu/kvm).</p><p style="text-indent:20px"> <a href="http://www.drbd.org/home/what-is-drb">drbd</a> - Distributed Replicated Block Device. Название говорит само за себя.</p><p style="text-indent:20px"> Из действительно интересного - <a href="http://www.osrg.net/sheepdog/">sheepdog</a>. Это фактически первое(AFAIK) "правильное" распределенное сетевое хранилище (только для kvm/qemu). Построено в соответствии с идеями <a href="http://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf">amazon dynamo</a> (статья обязательно к прочтению всем, кто так или иначе сталкивается с распределенными системами хранения данных. Именно на этих идеях построены все основные NoSQL базы данных, ориентированные на сверхбольшие объемы данных, - Cassandra, REAK, Voldemort). К сожалению проект похож на заброшенный.</p><p style="text-indent:20px"> Больше информации по этим хранилищам можно получить еще и в блоге <a href="http://berrange.com/">Daniel P. Berrange</a>.</p><p style="text-indent:20px"> Я пока сознательно обхожу стороной производительность дисковых операций на разных форматах, поскольку это довольно большая тема для отдельного поста. Для локальных хранилищ с высоты птичьего полета результат примерно такой - qcow2 почти такой же по скорости, как raw. Двухслойные qcow2 (и qcow2 поверх raw) примерно в два раза медленнее, если не используется SSD диск. А размещение образа на lvm разделе вместо файла увеличивает скорость работы только на больших блоках (файловые системы хорошо оптимизированы под такие нагрузки, по крайней мере XFS).</p><br><h2>Как модифицировать</h2><p style="text-indent:20px"> Есть масса причин модифицировать диски виртуальных машин до запуска - изменение настроек для соответствия виртуальному окружению, передача параметров в vm, установка драйверов, etc. Нас в первую очередь будет интересовать изменение сетевых настроек и передача параметров в vm.</p><p style="text-indent:20px"> Первый и самый основанной способ модифицировать файл с образом - каким-то образом примонтировать его в локальную файловую систему и дальше работать с локальными файлами. Для этого сначала нужно подключить его к виртуальному блочному устройству - loop.</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="22be39a23cc911e186be14feb5b819a0" objtohide2="22be3b143cc911e186be14feb5b819a0" >Без подсветки синтаксиса</a><br><span id="22be39a23cc911e186be14feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">#</span> losetup -f --show img.raw
<span style="color: #808080">/dev/loop0</span>
<span style="color: #000080; font-weight: bold">#</span> losetup -a
<span style="color: #808080">/dev/loop0: [0807]:2754650 (/home/koder/vm_images/img.raw)</span>
<span style="color: #000080; font-weight: bold">#</span> mkdir -p /mnt/vm/img_0
<span style="color: #000080; font-weight: bold">#</span> mount /dev/loop0 /mnt/vm/img_0
</pre></div></span><span style="line-height:100%;display:none" id="22be3b143cc911e186be14feb5b819a0"><pre><font face="courier"># losetup -f --show img.raw
/dev/loop0
# losetup -a
/dev/loop0: [0807]:2754650 (/home/koder/vm_images/img.raw)
# mkdir -p /mnt/vm/img_0
# mount /dev/loop0 /mnt/vm/img_0</font></pre></span><p style="text-indent:20px"> После этих команд файловая система из img.raw подключена в /mnt/vm/img_0 т.е. ее корень совпадает с /mnt/vm/img_0, /etc - /mnt/vm/img_0/etc и т.д. По окончанию обработки отключаем файл</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="22be5c3e3cc911e186be14feb5b819a0" objtohide2="22be5db03cc911e186be14feb5b819a0" >Без подсветки синтаксиса</a><br><span id="22be5c3e3cc911e186be14feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">#</span> umount /dev/loop0
<span style="color: #000080; font-weight: bold">#</span> losetup -d /dev/loop0
</pre></div></span><span style="line-height:100%;display:none" id="22be5db03cc911e186be14feb5b819a0"><pre><font face="courier"># umount /dev/loop0
# losetup -d /dev/loop0</font></pre></span><p style="text-indent:20px"> Все внесенные изменения будут сохранены в файл образа. Если на образе несколько разделов, то все чуть сложнее - либо сначала с помощью fdisk смотрим таблицу разделов, определяем смещение необходимого раздела и передаем его в опции -o в losetup либо используем <a href="http://linux.die.net/man/8/kpartx">kpartx</a>:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="22be82f43cc911e186be14feb5b819a0" objtohide2="22be84663cc911e186be14feb5b819a0" >Без подсветки синтаксиса</a><br><span id="22be82f43cc911e186be14feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">#</span> kpartx -a img2.raw
<span style="color: #000080; font-weight: bold">#</span> mount /dev/mapper/loop0p1 /mnt/vm/img_0
</pre></div></span><span style="line-height:100%;display:none" id="22be84663cc911e186be14feb5b819a0"><pre><font face="courier"># kpartx -a img2.raw
# mount /dev/mapper/loop0p1 /mnt/vm/img_0</font></pre></span><p style="text-indent:20px"> kpartx делает дополнительные виртуальные устройства /dev/mapper/loop0pX для всех разделов на образе (на самом деле он использует devmapper для /dev/loop0).</p><p style="text-indent:20px"> Однако так можно подключить только raw образы, для всех остальных нужно использовать qemu-nbd. Это сервер, которые умеет раздавать блочное устройство по <a href="http://nbd.sourceforge.net/">nbd</a> протоколу. (Вообще nbd довольно интересный прокол - позволяет раздать по сети любое локальное блочное устройство, или, что еще интереснее, иммитировать его из пользовательской программы ,а не из драйвера. Вот, например ndb сервера <a href="http://code.activestate.com/recipes/577569-nbd-server-in-python/">1</a>, <a href="http://lists.canonical.org/pipermail/kragen-hacks/2004-May/000397.html">2</a> на python.)</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="22bebddc3cc911e186be14feb5b819a0" objtohide2="22bebf4e3cc911e186be14feb5b819a0" >Без подсветки синтаксиса</a><br><span id="22bebddc3cc911e186be14feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">#</span> modprobe nbd <span style="color: #19177C">max_part</span><span style="color: #666666">=</span>16 <span style="color: #408080; font-style: italic"># <- очень важно передать max_part, если nbd</span>
<span style="color: #000080; font-weight: bold">#</span> qemu-nbd -b 127.0.0.1 -p 5555 img.qcow2
<span style="color: #000080; font-weight: bold">#</span> nbd-client 127.0.0.1 5555 /dev/nbd0 <span style="color: #408080; font-style: italic"># <- подключить к /dev/nb0</span>
</pre></div></span><span style="line-height:100%;display:none" id="22bebf4e3cc911e186be14feb5b819a0"><pre><font face="courier"># modprobe nbd max_part=16 # <- очень важно передать max_part, если nbd
# qemu-nbd -b 127.0.0.1 -p 5555 img.qcow2
# nbd-client 127.0.0.1 5555 /dev/nbd0 # <- подключить к /dev/nb0</font></pre></span><p style="text-indent:20px"> Если привязывать сервер не к localhost, то можно подключить это устройство на другом компьютере удаленно. Можно объединить оба шага в один:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="22bedd943cc911e186be14feb5b819a0" objtohide2="22bedefc3cc911e186be14feb5b819a0" >Без подсветки синтаксиса</a><br><span id="22bedd943cc911e186be14feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">#</span> qemu-nbd --connect<span style="color: #666666">=</span>/dev/nbd0 img.qcow2
</pre></div></span><span style="line-height:100%;display:none" id="22bedefc3cc911e186be14feb5b819a0"><pre><font face="courier"># qemu-nbd --connect=/dev/nbd0 img.qcow2</font></pre></span><p style="text-indent:20px"> По итогу /dev/nbd0 будет представлять образ виртуальной машины, а /dev/nbd0pX - отдельные разделы. Дальнейшая работа не отличается от raw файла - монтирует полученные устройства в локальную файловую систему и елозим байты, вся поддержка формата qcow2 будет выполняться qemu-nbd сервером.</p><p style="text-indent:20px"> Совершенно другой способ модификации дисковых образов предлагает библиотека <a href="http://libguestfs.org/">libguestfs</a>. Это одна интересных библиотек, которые я видел. Она позволяет модифицировать образы виртуальных машин и многое другое. При это не требуются права root, поддерживаются все файловые системы, поддерживаемые в linux, LVM и все-все-все. Внутри она запускает простенькую виртуальную машину, монтирует в нее указанный файл образа и позволяет модифицировать его используя обширный <a href="http://libguestfs.org/guestfs.3.html">API</a>, который в итоге общается с VM по RPC. В принципе libguestfs позволяет и <a href="http://libguestfs.org/guestmount.1.html">смонтировать</a> файловую систему локально, использую fuse. Вообще возможности libguestfs очень обширны - <a href="http://libguestfs.org/virt-v2v/">p2v</a> и <a href="http://libguestfs.org/virt-v2v/">v2v</a> конвертация, модификация реестра windows, определение установленной операционной системы и ПО, автоматическое монтирование всего в соответствии с локальными fstab и др. Еще из интересного в нее входит <a href="http://libguestfs.org/guestfish.1.html">guestfish</a> - утилита командной строки, позволяющая выполнять все операции из командной строки, в комплекте с virsh они позволяют писать маленькие системы управления VM прямо на bash. Есть API для C, Python, Perl, Ruby, Ocaml и прочего. Меня, ессно, интересует в первую очередь python.</p><p style="text-indent:20px">Для приведения образа vm к удобному для запуска виду нам нужно примерно такая функция:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="22e7149e3cc911e186be14feb5b819a0" objtohide2="22e717463cc911e186be14feb5b819a0" >Без подсветки синтаксиса</a><br><span id="22e7149e3cc911e186be14feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">prepare_guest_debian</span>(disk_path, hostname, passwords, eth_devs, format<span style="color: #666666">=</span><span style="color: #008000">None</span>, apt_proxy_ip<span style="color: #666666">=</span><span style="color: #008000">None</span>):
<span style="color: #408080; font-style: italic"># создаем и запускаем vm </span>
gfs <span style="color: #666666">=</span> guestfs<span style="color: #666666">.</span>GuestFS()
gfs<span style="color: #666666">.</span>add_drive_opts(disk_path, format<span style="color: #666666">=</span>format)
gfs<span style="color: #666666">.</span>launch()
<span style="color: #408080; font-style: italic"># находим раздел с /etc. Не очень чистое решение, но для образов, когда все </span>
<span style="color: #408080; font-style: italic"># на одном разделе работает </span>
<span style="color: #008000; font-weight: bold">for</span> dev, fs_type <span style="color: #AA22FF; font-weight: bold">in</span> gfs<span style="color: #666666">.</span>list_filesystems():
<span style="color: #008000; font-weight: bold">if</span> fs_type <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #BA2121">'ext2 ext3 reiserfs3 reiserfs4 xfs jfs btrfs'</span>:
<span style="color: #408080; font-style: italic"># монтирует раздел в корень файловой системы vm</span>
gfs<span style="color: #666666">.</span>mount(dev, <span style="color: #BA2121">'/'</span>)
<span style="color: #408080; font-style: italic"># если есть etc - все ок</span>
<span style="color: #008000; font-weight: bold">if</span> gfs<span style="color: #666666">.</span>exists(<span style="color: #BA2121">'/etc'</span>):
<span style="color: #008000; font-weight: bold">break</span>
gfs<span style="color: #666666">.</span>umount(dev)
<span style="color: #408080; font-style: italic"># обновляем hostname, для правильной работы hostname нужно так-же модифицировать</span>
<span style="color: #408080; font-style: italic"># /etc/hosts</span>
gfs<span style="color: #666666">.</span>write(<span style="color: #BA2121">'/etc/hostname'</span>, hostname)
<span style="color: #408080; font-style: italic"># set eth device names for udev</span>
templ <span style="color: #666666">=</span> <span style="color: #BA2121">'SUBSYSTEM=="net", DRIVERS=="?*", ATTR{{address}}=="{hw}", NAME="{name}"'</span>
<span style="color: #408080; font-style: italic"># записываем настройки сетевых интерфейсов в /etc/network/interfaces</span>
rules_fc <span style="color: #666666">=</span> []
interfaces <span style="color: #666666">=</span> [<span style="color: #BA2121">"auto lo</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">iface lo inet loopback"</span>]
<span style="color: #008000; font-weight: bold">for</span> dev, (hw, ip, sz, gw) <span style="color: #AA22FF; font-weight: bold">in</span> eth_devs<span style="color: #666666">.</span>items():
rules_fc<span style="color: #666666">.</span>append(templ<span style="color: #666666">.</span>format(hw<span style="color: #666666">=</span>hw, name<span style="color: #666666">=</span>dev))
interfaces<span style="color: #666666">.</span>append(<span style="color: #BA2121">"auto "</span> <span style="color: #666666">+</span> dev)
<span style="color: #008000; font-weight: bold">if</span> ip <span style="color: #666666">==</span> <span style="color: #BA2121">'dhcp'</span>:
interfaces<span style="color: #666666">.</span>append(<span style="color: #BA2121">"iface {0} inet dhcp"</span><span style="color: #666666">.</span>format(dev))
<span style="color: #008000; font-weight: bold">else</span>:
interfaces<span style="color: #666666">.</span>append(<span style="color: #BA2121">"iface {0} inet static"</span><span style="color: #666666">.</span>format(dev))
interfaces<span style="color: #666666">.</span>append(<span style="color: #BA2121">" address "</span> <span style="color: #666666">+</span> ip)
network <span style="color: #666666">=</span> int2ip(ip2int(ip) <span style="color: #666666">&</span> ip2int(netsz2netmask(sz)))
interfaces<span style="color: #666666">.</span>append(<span style="color: #BA2121">" network "</span> <span style="color: #666666">+</span> network)
interfaces<span style="color: #666666">.</span>append(<span style="color: #BA2121">" netmask "</span> <span style="color: #666666">+</span> netsz2netmask(sz))
gfs<span style="color: #666666">.</span>write(<span style="color: #BA2121">'/etc/udev/rules.d/70-persistent-net.rules'</span>, <span style="color: #BA2121">"</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">"</span><span style="color: #666666">.</span>join(rules_fc))
gfs<span style="color: #666666">.</span>write(<span style="color: #BA2121">'/etc/network/interfaces'</span>, <span style="color: #BA2121">"</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">"</span><span style="color: #666666">.</span>join(interfaces))
<span style="color: #408080; font-style: italic"># обновляем пароли для пользователей</span>
chars <span style="color: #666666">=</span> <span style="color: #BA2121">""</span><span style="color: #666666">.</span>join(<span style="color: #008000">chr</span>(i) <span style="color: #008000; font-weight: bold">for</span> i <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">range</span>(<span style="color: #008000">ord</span>(<span style="color: #BA2121">'a'</span>), <span style="color: #008000">ord</span>(<span style="color: #BA2121">'z'</span>) <span style="color: #666666">+</span> <span style="color: #666666">1</span>))
chars <span style="color: #666666">+=</span> <span style="color: #BA2121">""</span><span style="color: #666666">.</span>join(<span style="color: #008000">chr</span>(i) <span style="color: #008000; font-weight: bold">for</span> i <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">range</span>(<span style="color: #008000">ord</span>(<span style="color: #BA2121">'A'</span>), <span style="color: #008000">ord</span>(<span style="color: #BA2121">'Z'</span>) <span style="color: #666666">+</span> <span style="color: #666666">1</span>))
chars <span style="color: #666666">+=</span> <span style="color: #BA2121">""</span><span style="color: #666666">.</span>join(<span style="color: #008000">chr</span>(i) <span style="color: #008000; font-weight: bold">for</span> i <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">range</span>(<span style="color: #008000">ord</span>(<span style="color: #BA2121">'0'</span>), <span style="color: #008000">ord</span>(<span style="color: #BA2121">'9'</span>) <span style="color: #666666">+</span> <span style="color: #666666">1</span>))
hashes <span style="color: #666666">=</span> {}
<span style="color: #008000; font-weight: bold">for</span> login, passwd <span style="color: #AA22FF; font-weight: bold">in</span> passwords<span style="color: #666666">.</span>items():
salt <span style="color: #666666">=</span> <span style="color: #BA2121">""</span><span style="color: #666666">.</span>join(random<span style="color: #666666">.</span>choice(chars) <span style="color: #008000; font-weight: bold">for</span> _ <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">range</span>(<span style="color: #666666">8</span>))
hashes[login] <span style="color: #666666">=</span> crypt<span style="color: #666666">.</span>crypt(passwd, <span style="color: #BA2121">"$6$"</span> <span style="color: #666666">+</span> salt)
new_shadow <span style="color: #666666">=</span> []
need_logins <span style="color: #666666">=</span> <span style="color: #008000">set</span>(hashes)
<span style="color: #008000; font-weight: bold">for</span> ln <span style="color: #AA22FF; font-weight: bold">in</span> gfs<span style="color: #666666">.</span>read_file(<span style="color: #BA2121">'/etc/shadow'</span>)<span style="color: #666666">.</span>split(<span style="color: #BA2121">'</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">'</span>):
ln <span style="color: #666666">=</span> ln<span style="color: #666666">.</span>strip()
<span style="color: #008000; font-weight: bold">if</span> ln <span style="color: #666666">!=</span> <span style="color: #BA2121">''</span> <span style="color: #AA22FF; font-weight: bold">and</span> ln[<span style="color: #666666">0</span>] <span style="color: #666666">!=</span> <span style="color: #BA2121">'#'</span>:
login <span style="color: #666666">=</span> ln<span style="color: #666666">.</span>split(<span style="color: #BA2121">':'</span>, <span style="color: #666666">1</span>)[<span style="color: #666666">0</span>]
<span style="color: #008000; font-weight: bold">if</span> login <span style="color: #AA22FF; font-weight: bold">in</span> hashes:
new_shadow<span style="color: #666666">.</span>append(<span style="color: #BA2121">"{login}:{hash}:{rest}"</span><span style="color: #666666">.</span>format(login<span style="color: #666666">=</span>login,
<span style="color: #008000">hash</span><span style="color: #666666">=</span>hashes[login],
rest<span style="color: #666666">=</span>ln<span style="color: #666666">.</span>split(<span style="color: #BA2121">':'</span>, <span style="color: #666666">2</span>)[<span style="color: #666666">2</span>]))
need_logins<span style="color: #666666">.</span>remove(login)
<span style="color: #008000; font-weight: bold">else</span>:
new_shadow<span style="color: #666666">.</span>append(ln)
<span style="color: #008000; font-weight: bold">for</span> login <span style="color: #AA22FF; font-weight: bold">in</span> need_logins:
new_shadow<span style="color: #666666">.</span>append(<span style="color: #BA2121">"{login}:{hash}:{rest}"</span><span style="color: #666666">.</span>format(login<span style="color: #666666">=</span>login,
<span style="color: #008000">hash</span><span style="color: #666666">=</span>hashes[login],
rest<span style="color: #666666">=</span><span style="color: #BA2121">"0:0:99999:7:::"</span>))
gfs<span style="color: #666666">.</span>write(<span style="color: #BA2121">'/etc/shadow'</span>, <span style="color: #BA2121">"</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">"</span><span style="color: #666666">.</span>join(new_shadow))
<span style="color: #408080; font-style: italic"># тут еще модификация /etc/passwd, /etc/hosts, создание домащних папок, etc</span>
<span style="color: #408080; font-style: italic"># полная версия на github</span>
</pre></div></span><span style="line-height:100%;display:none" id="22e717463cc911e186be14feb5b819a0"><pre><font face="courier">
def prepare_guest_debian(disk_path, hostname, passwords, eth_devs, format=None, apt_proxy_ip=None):
# создаем и запускаем vm
gfs = guestfs.GuestFS()
gfs.add_drive_opts(disk_path, format=format)
gfs.launch()
# находим раздел с /etc. Не очень чистое решение, но для образов, когда все
# на одном разделе работает
for dev, fs_type in gfs.list_filesystems():
if fs_type in 'ext2 ext3 reiserfs3 reiserfs4 xfs jfs btrfs':
# монтирует раздел в корень файловой системы vm
gfs.mount(dev, '/')
# если есть etc - все ок
if gfs.exists('/etc'):
break
gfs.umount(dev)
# обновляем hostname, для правильной работы hostname нужно так-же модифицировать
# /etc/hosts
gfs.write('/etc/hostname', hostname)
# set eth device names for udev
templ = 'SUBSYSTEM=="net", DRIVERS=="?*", ATTR{{address}}=="{hw}", NAME="{name}"'
# записываем настройки сетевых интерфейсов в /etc/network/interfaces
rules_fc = []
interfaces = ["auto lo\niface lo inet loopback"]
for dev, (hw, ip, sz, gw) in eth_devs.items():
rules_fc.append(templ.format(hw=hw, name=dev))
interfaces.append("auto " + dev)
if ip == 'dhcp':
interfaces.append("iface {0} inet dhcp".format(dev))
else:
interfaces.append("iface {0} inet static".format(dev))
interfaces.append(" address " + ip)
network = int2ip(ip2int(ip) & ip2int(netsz2netmask(sz)))
interfaces.append(" network " + network)
interfaces.append(" netmask " + netsz2netmask(sz))
gfs.write('/etc/udev/rules.d/70-persistent-net.rules', "\n".join(rules_fc))
gfs.write('/etc/network/interfaces', "\n".join(interfaces))
# обновляем пароли для пользователей
chars = "".join(chr(i) for i in range(ord('a'), ord('z') + 1))
chars += "".join(chr(i) for i in range(ord('A'), ord('Z') + 1))
chars += "".join(chr(i) for i in range(ord('0'), ord('9') + 1))
hashes = {}
for login, passwd in passwords.items():
salt = "".join(random.choice(chars) for _ in range(8))
hashes[login] = crypt.crypt(passwd, "$6$" + salt)
new_shadow = []
need_logins = set(hashes)
for ln in gfs.read_file('/etc/shadow').split('\n'):
ln = ln.strip()
if ln != '' and ln[0] != '#':
login = ln.split(':', 1)[0]
if login in hashes:
new_shadow.append("{login}:{hash}:{rest}".format(login=login,
hash=hashes[login],
rest=ln.split(':', 2)[2]))
need_logins.remove(login)
else:
new_shadow.append(ln)
for login in need_logins:
new_shadow.append("{login}:{hash}:{rest}".format(login=login,
hash=hashes[login],
rest="0:0:99999:7:::"))
gfs.write('/etc/shadow', "\n".join(new_shadow))
# тут еще модификация /etc/passwd, /etc/hosts, создание домащних папок, etc
# полная версия на github</font></pre></span><p style="text-indent:20px"> Тут использован прямой интерфейс libguestfs. Наверное, для указанных задач проще смонтировать образ через guestmount и модифицировать локальные файлы, иногда делая chroot (это, например, позволит использовать локальный passwd для обновления пароля). OpenStack делает это именно <a href="https://github.com/openstack/nova/tree/master/nova/virt/disk">так</a>.</p><br><h2>Итоги</h2><p style="text-indent:20px"> После добавки кода, управляющего образами дисков, рефакторинга и перенесения конфигов в yaml файл <a href="https://github.com/koder-ua/tiny_cloud">tiny_cloud</a> практически готов к использованию. Можно добавить интеграцию с fabric, мониторинг, кеш пакетов и многое другое, но и так уже вполне годно к использованию. Разве что python API сделать удобное.</p>Ссылки:<br> <a name="raw"><a href="http://ru.wikipedia.org/wiki/RAW_%28%D1%84%D0%BE%D1%80%D0%BC%D0%B0%D1%82_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%29">ru.wikipedia.org/wiki/RAW_%28%D1%84%D0%BE%D1%80%D0%BC%D0%B0%D1%82_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%29</a></a><br> <a name="sparse"><a href="http://administratosphere.wordpress.com/2008/05/23/sparse-files-what-why-and-how/">administratosphere.wordpress.com/2008/05/23/sparse-files-what-why-and-how</a></a><br> <a name="LVM"><a href="http://en.wikipedia.org/wiki/Logical_Volume_Manager_%28Linux%29">en.wikipedia.org/wiki/Logical_Volume_Manager_%28Linux%29</a></a><br> <a name="qcow2"><a href="http://en.wikipedia.org/wiki/Qcow#qcow2">en.wikipedia.org/wiki/Qcow#qcow2</a></a><br> <a name="vdi"><a href="http://en.wikipedia.org/wiki/VDI_%28file_format%29#Virtual_Disk_Image">en.wikipedia.org/wiki/VDI_%28file_format%29#Virtual_Disk_Image</a></a><br> <a name="vmdk"><a href="http://en.wikipedia.org/wiki/VMDK">en.wikipedia.org/wiki/VMDK</a></a><br> <a name="VMware"><a href="http://www.vmware.com/">www.vmware.com</a></a><br> <a name="VirtualBox"><a href="https://www.virtualbox.org/">www.virtualbox.org</a></a><br> <a name="vdiskmanager"><a href="http://www.vmware.com/support/ws45/doc/disks_vdiskmanager_eg_ws.html">www.vmware.com/support/ws45/doc/disks_vdiskmanager_eg_ws.html</a></a><br> <a name="qcow2-internals"><a href="http://people.gnome.org/~markmc/qcow-image-format.html">people.gnome.org/~markmc/qcow-image-format.html</a></a><br> <a name="libvirt-disk"><a href="http://libvirt.org/formatdomain.html#elementsDisks">libvirt.org/formatdomain.html#elementsDisks</a></a><br> <a name="qemu-img"><a href="http://linux.die.net/man/1/qemu-img">linux.die.net/man/1/qemu-img</a></a><br> <a name="nbd"><a href="http://nbd.sourceforge.net/">nbd.sourceforge.net</a></a><br> <a name="kpartx"><a href="http://linux.die.net/man/8/kpartx">linux.die.net/man/8/kpartx</a></a><br> <a name="1"><a href="http://code.activestate.com/recipes/577569-nbd-server-in-python/">code.activestate.com/recipes/577569-nbd-server-in-python</a></a><br> <a name="2"><a href="http://lists.canonical.org/pipermail/kragen-hacks/2004-May/000397.html">lists.canonical.org/pipermail/kragen-hacks/2004-May/000397.html</a></a><br> <a name="libguestfs"><a href="http://libguestfs.org/">libguestfs.org</a></a><br> <a name="p2v"><a href="http://libguestfs.org/virt-v2v/">libguestfs.org/virt-v2v</a></a><br> <a name="v2v"><a href="http://libguestfs.org/virt-v2v/">libguestfs.org/virt-v2v</a></a><br> <a name="API"><a href="http://libguestfs.org/guestfs.3.html">libguestfs.org/guestfs.3.html</a></a><br> <a name="guestfish"><a href="http://libguestfs.org/guestfish.1.html">libguestfs.org/guestfish.1.html</a></a><br> <a name="смонтировать"><a href="http://libguestfs.org/guestmount.1.html">libguestfs.org/guestmount.1.html</a></a><br> <a name="tiny_cloud"><a href="https://github.com/koder-ua/tiny_cloud">github.com/koder-ua/tiny_cloud</a></a><br> <a name="сделать"><a href="http://habrahabr.ru/blogs/linux/67283/">habrahabr.ru/blogs/linux/67283</a></a><br> <a name="devmapper"><a href="http://sources.redhat.com/dm/">sources.redhat.com/dm</a></a><br> <a name="virtio"><a href="http://wiki.libvirt.org/page/Virtio">wiki.libvirt.org/page/Virtio</a></a><br> <a name="vmware_tools"><a href="http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=340">kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=340</a></a><br> <a name="так"><a href="https://github.com/openstack/nova/tree/master/nova/virt/disk">github.com/openstack/nova/tree/master/nova/virt/disk</a></a><br> <a name="примеров"><a href="http://habrahabr.ru/blogs/linux/64350/">habrahabr.ru/blogs/linux/64350</a></a><br> <a name="предостаточно"><a href="http://www.linuxfordevices.com/c/a/News/ATAoverEthernet-enables-lowcost-Linuxoriented-SAN/">www.linuxfordevices.com/c/a/News/ATAoverEthernet-enables-lowcost-Linuxoriented-SAN</a></a><br> <a name="сети"><a href="http://www.linux-mag.com/id/2028/">www.linux-mag.com/id/2028</a></a><br> <a name="в"><a href="http://www.howtoforge.com/how-to-build-a-low-cost-san">www.howtoforge.com/how-to-build-a-low-cost-san</a></a><br> <a name="AOE"><a href="http://en.wikipedia.org/wiki/ATA_over_Ethernet">en.wikipedia.org/wiki/ATA_over_Ethernet</a></a><br> <a name="Coraid"><a href="http://www.coraid.com/">www.coraid.com</a></a><br> <a name="iSCSI"><a href="http://en.wikipedia.org/wiki/ISCSI">en.wikipedia.org/wiki/ISCSI</a></a><br> <a name="rbd"><a href="http://ceph.newdream.net/wiki/Rbd">ceph.newdream.net/wiki/Rbd</a></a><br> <a name="ceph"><a href="http://ceph.newdream.net/">ceph.newdream.net</a></a><br> <a name="drbd"><a href="http://www.drbd.org/home/what-is-drb">www.drbd.org/home/what-is-drb</a></a><br> <a name="sheepdog"><a href="http://www.osrg.net/sheepdog/">www.osrg.net/sheepdog</a></a><br> <a name="Daniel_P._Berrange"><a href="http://berrange.com/">berrange.com</a></a><br> <a name="amazon_dynamo"><a href="http://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf">www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf</a></a><br><p style="text-indent:20px">Исходники этого и других постов со скриптами лежат тут - <a href="https://github.com/koder-ua/python-lectures">github.com/koder-ua</a>. При использовании их, пожалуйста, ссылайтесь на <a href="http://koder-ua.blogspot.com/">koder-ua.blogspot.com</a>.</p>
<script type="text/javascript">
function on_hidabble_click()
{
var me = $(this);
var hide_id = me.attr("objtohide");
var controlled_object = $('#' + hide_id);
controlled_object.toggle();
if ( controlled_object.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".hidder").click(on_hidabble_click);
function on_double_hidabble_click()
{
var me = $(this);
var hide_id1 = me.attr("objtohide1");
var hide_id2 = me.attr("objtohide2");
var controlled_object1 = $('#' + hide_id1);
var controlled_object2 = $('#' + hide_id2);
controlled_object1.toggle();
controlled_object2.toggle();
if ( controlled_object1.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".dhidder").click(on_double_hidabble_click);
</script>Anonymoushttp://www.blogger.com/profile/06645078574760734586noreply@blogger.com2tag:blogger.com,1999:blog-1174489715777430743.post-63265075179721080142011-12-27T01:16:00.000+02:002012-01-12T14:06:07.872+02:00Сопоставление объектов с образцом (pattern matching)<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<p style="text-indent:20px"> В <a href="http://ru.wikipedia.org/wiki/haskell">функциональных</a> <a href="http://ru.wikipedia.org/wiki/Erlang">языках</a> есть интересная возможность, фактически являющаяся расширением идеи перегрузки функций - <a href="http://en.wikipedia.org/wiki/Pattern_matching">сопоставление с образцом</a>. Для этого поддерживается специальный синтаксис шаблонов структур данных, позволяющий проверить что объект имеет определенный тип и/или поля, а также извлечь из него некоторые данные. Пример из <a href="http://ru.wikipedia.org/wiki/haskell">haskell</a> (частично взято тут <a href="http://en.wikibooks.org/wiki/Haskell/Pattern_matching">wiki</a>):</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="be90bcc83d1511e1b4cc14feb5b819a0" objtohide2="be91ceba3d1511e1b4cc14feb5b819a0" >Без подсветки синтаксиса</a><br><span id="be90bcc83d1511e1b4cc14feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #408080; font-style: italic">-- f - функция от одного целого параметра</span>
<span style="color: #408080; font-style: italic">-- возвращающая целое</span>
<span style="color: #0000FF">f</span> <span style="color: #AA22FF; font-weight: bold">::</span> <span style="color: #B00040">Int</span> <span style="color: #AA22FF; font-weight: bold">-></span> <span style="color: #B00040">Int</span>
<span style="color: #0000FF">f</span> <span style="color: #666666">1</span> <span style="color: #AA22FF; font-weight: bold">=</span> <span style="color: #666666">0</span>
<span style="color: #408080; font-style: italic">-- если ей передать 1, то она вернет 0</span>
<span style="color: #0000FF">f</span> <span style="color: #008000; font-weight: bold">_</span> <span style="color: #AA22FF; font-weight: bold">-></span> <span style="color: #666666">1</span>
<span style="color: #408080; font-style: italic">-- если что-либо другое - 1</span>
<span style="color: #408080; font-style: italic">-- map от чего угодно и пустого списка возвращает пустой список</span>
<span style="color: #0000FF">map</span> <span style="color: #008000; font-weight: bold">_</span> <span style="color: #B00040">[]</span> <span style="color: #AA22FF; font-weight: bold">=</span> <span style="color: #B00040">[]</span>
<span style="color: #408080; font-style: italic">-- рекурсия - map от функции и списка это конкатенация </span>
<span style="color: #408080; font-style: italic">-- f от первого параметра и map от f и остатка списка</span>
<span style="color: #0000FF">map</span> f (x<span style="color: #B00040">:</span>xs) <span style="color: #AA22FF; font-weight: bold">=</span> f x <span style="color: #B00040">:</span> map f xs
<span style="color: #408080; font-style: italic">-- разбор структуры</span>
<span style="color: #408080; font-style: italic">-- Foo это или Bar или Baz </span>
<span style="color: #008000; font-weight: bold">data</span> <span style="color: #B00040">Foo</span> <span style="color: #AA22FF; font-weight: bold">=</span> <span style="color: #B00040">Bar</span> <span style="color: #666666">|</span> <span style="color: #B00040">Baz</span> {bazNumber<span style="color: #AA22FF; font-weight: bold">::</span><span style="color: #B00040">Int</span>, bazName<span style="color: #AA22FF; font-weight: bold">::</span><span style="color: #B00040">String</span>}
<span style="color: #0000FF">h</span> <span style="color: #AA22FF; font-weight: bold">::</span> <span style="color: #B00040">Foo</span> <span style="color: #AA22FF; font-weight: bold">-></span> <span style="color: #B00040">Int</span>
<span style="color: #408080; font-style: italic">-- Baz - это тип структуры, bazName - это имя поля</span>
<span style="color: #0000FF">h</span> <span style="color: #B00040">Baz</span> {bazName<span style="color: #AA22FF; font-weight: bold">=</span>name} <span style="color: #AA22FF; font-weight: bold">=</span> length name
<span style="color: #0000FF">h</span> <span style="color: #B00040">Bar</span> {} <span style="color: #AA22FF; font-weight: bold">=</span> <span style="color: #666666">0</span>
</pre></div></span><span style="line-height:100%;display:none" id="be91ceba3d1511e1b4cc14feb5b819a0"><pre><font face="courier">-- f - функция от одного целого параметра
-- возвращающая целое
f :: Int -> Int
f 1 = 0
-- если ей передать 1, то она вернет 0
f _ -> 1
-- если что-либо другое - 1
-- map от чего угодно и пустого списка возвращает пустой список
map _ [] = []
-- рекурсия - map от функции и списка это конкатенация
-- f от первого параметра и map от f и остатка списка
map f (x:xs) = f x : map f xs
-- разбор структуры
-- Foo это или Bar или Baz
data Foo = Bar | Baz {bazNumber::Int, bazName::String}
h :: Foo -> Int
-- Baz - это тип структуры, bazName - это имя поля
h Baz {bazName=name} = length name
h Bar {} = 0</font></pre></span><p style="text-indent:20px"> Примерно тоже можно сделать во многих функциональных языках, но я никогда не видел подобных возможностей в императивных языках. Самое близкое что есть по интеллектуальности - перегрузка функций в C++. Такое положение связанно и с особенностями задач, обычно решаемыми в функциональных языках, и с их ориентированностью на рекурсивные структуры данных и с попытками уйти от <b>if</b> и других императивных особенностей.</p><a name='more'></a><p style="text-indent:20px"> Но тем не менее желание сделать что-то подобное для python возникало после каждого ковыряния в функциональщине и после каждой конструкции вида:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="bed2500c3d1511e1b4cc14feb5b819a0" objtohide2="bed2ba603d1511e1b4cc14feb5b819a0" >Без подсветки синтаксиса</a><br><span id="bed2500c3d1511e1b4cc14feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">if</span> <span style="color: #008000">isinstance</span>(x, Message):
<span style="color: #008000; font-weight: bold">if</span> x<span style="color: #666666">.</span>mtype <span style="color: #666666">==</span> DATA_READY <span style="color: #AA22FF; font-weight: bold">and</span> x<span style="color: #666666">.</span>data <span style="color: #AA22FF; font-weight: bold">is</span> <span style="color: #AA22FF; font-weight: bold">not</span> <span style="color: #008000">None</span>:
<span style="color: #408080; font-style: italic">#some code</span>
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #008000; font-weight: bold">elif</span> x<span style="color: #666666">.</span>mtype <span style="color: #666666">==</span> PROCESS_FINISHED:
<span style="color: #408080; font-style: italic">#some code</span>
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #408080; font-style: italic"># ....</span>
<span style="color: #408080; font-style: italic"># .....</span>
</pre></div></span><span style="line-height:100%;display:none" id="bed2ba603d1511e1b4cc14feb5b819a0"><pre><font face="courier">if isinstance(x, Message):
if x.mtype == DATA_READY and x.data is not None:
#some code
pass
elif x.mtype == PROCESS_FINISHED:
#some code
pass
# ....
# .....</font></pre></span><p style="text-indent:20px"> А тут что-то захотелось посмотреть внимательно на модуль <a href="http://docs.python.org/library/ast.html">ast</a> (<a href="http://en.wikipedia.org/wiki/Abstract_syntax_tree">abstract syntax tree</a>) - давно не использовал его, последний раз еще во времена 2.4 - тогда очень жалел, что он не позволяет компилировать измененный <b>ast</b> (кстати делал интересный проект по портированию большого куска кода с PyQt3 на PyQt4 и ast позволил значительно автоматизировать этот перенос).</p><p style="text-indent:20px"> <b>ast</b> позволяет получить из python кода его результат после синтаксического разбора, но еще до компиляции в байтокод, исследовать его и/или изменять и компилировать новый вариант. Пример ast:</p><pre><font face="courier"> a = f.b(1)
=>
Assign(
targets=[Name(id='a', ctx=Store())],
value=Call(
func=Attribute(
value=Name(id='f', ctx=Load()),
attr='b',
ctx=Load()),
args=[Num(n=1)],
keywords=[],
starargs=None,
kwargs=None
)
)</font></pre><p style="text-indent:20px"> Фактически мы получаем исходный текст в удобном для ковыряния виде (правда несколько громоздком). Именно с абстрактными синтаксически деревьями работаю всяческие анализаторы кода, оптимизаторы и прочее. <b>ast</b> предоставляет некоторое количество вспомогательных функций и два класса - <b>NodeVisitor</b> для просмотра ast и <b>NodeTransformer</b> для модификации.</p><p style="text-indent:20px">На этом все про <b>ast</b>. Что хотелось от сопоставления с образцом:</p><ul><li>Чистый python синтаксис, что-бы никаких новых зарезервированных слов и IDE что-бы не ругались<li>Как-можно меньше кода при использовании<li>Обеспечить сопоставление с константой, типом, проверку атрибутов и вложенную проверку</ul><p style="text-indent:20px">После некоторого времени размышлений остановился на таком варианте:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="bf11851a3d1511e1b4cc14feb5b819a0" objtohide2="bf118f243d1511e1b4cc14feb5b819a0" >Без подсветки синтаксиса</a><br><span id="bf11851a3d1511e1b4cc14feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">with</span> match(x) <span style="color: #008000; font-weight: bold">as</span> res:
<span style="color: #666666">1</span> <span style="color: #666666">>></span> <span style="color: #666666">2</span>
<span style="color: #008000">int</span> <span style="color: #666666">>></span> x <span style="color: #666666">*</span> <span style="color: #666666">3</span>
<span style="color: #008000">str</span> <span style="color: #666666">>></span> func_str(x)
SomeType(c<span style="color: #666666">=</span>V_c, d<span style="color: #666666">=</span>V_c) <span style="color: #666666">>></span> on_val(V_c)
SomeType(c<span style="color: #666666">=</span>V_c, d<span style="color: #666666">=</span>V_d) <span style="color: #666666">>></span> on_val2(x, V_c)
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"res ="</span>, res
</pre></div></span><span style="line-height:100%;display:none" id="bf118f243d1511e1b4cc14feb5b819a0"><pre><font face="courier">with match(x) as res:
1 >> 2
int >> x * 3
str >> func_str(x)
SomeType(c=V_c, d=V_c) >> on_val(V_c)
SomeType(c=V_c, d=V_d) >> on_val2(x, V_c)
print "res =", res</font></pre></span><p style="text-indent:20px">Как это должно было-бы работать:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="bf44fd463d1511e1b4cc14feb5b819a0" objtohide2="bf4500483d1511e1b4cc14feb5b819a0" >Без подсветки синтаксиса</a><br><span id="bf44fd463d1511e1b4cc14feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">if</span> x <span style="color: #666666">==</span> <span style="color: #666666">1</span>:
res <span style="color: #666666">=</span> <span style="color: #666666">2</span>
<span style="color: #008000; font-weight: bold">elif</span> <span style="color: #008000">isinstance</span>(x, <span style="color: #008000">int</span>):
res <span style="color: #666666">=</span> x <span style="color: #666666">*</span> <span style="color: #666666">3</span>
<span style="color: #008000; font-weight: bold">elif</span> <span style="color: #008000">isinstance</span>(x, <span style="color: #008000">str</span>):
res <span style="color: #666666">=</span> func_str(x)
<span style="color: #008000; font-weight: bold">elif</span> <span style="color: #008000">isinstance</span>(x, SomeType) <span style="color: #AA22FF; font-weight: bold">and</span> \
<span style="color: #008000">hasattr</span>(x, <span style="color: #BA2121">'c'</span>) <span style="color: #AA22FF; font-weight: bold">and</span> \
<span style="color: #008000">hasattr</span>(x, <span style="color: #BA2121">'d'</span>) <span style="color: #AA22FF; font-weight: bold">and</span> \
x<span style="color: #666666">.</span>c <span style="color: #666666">==</span> x<span style="color: #666666">.</span>d:
res <span style="color: #666666">=</span> on_val(x<span style="color: #666666">.</span>c)
<span style="color: #008000; font-weight: bold">elif</span> <span style="color: #008000">isinstance</span>(x, SomeType) <span style="color: #AA22FF; font-weight: bold">and</span> \
<span style="color: #008000">hasattr</span>(x, <span style="color: #BA2121">'c'</span>) <span style="color: #AA22FF; font-weight: bold">and</span> \
<span style="color: #008000">hasattr</span>(x, <span style="color: #BA2121">'d'</span>):
res <span style="color: #666666">=</span> on_val2(x, x<span style="color: #666666">.</span>c)
<span style="color: #008000; font-weight: bold">else</span>:
<span style="color: #008000; font-weight: bold">raise</span> <span style="color: #D2413A; font-weight: bold">ValueError</span>(<span style="color: #BA2121">"{0!r} don't match any pattern!"</span><span style="color: #666666">.</span>format(x))
</pre></div></span><span style="line-height:100%;display:none" id="bf4500483d1511e1b4cc14feb5b819a0"><pre><font face="courier">if x == 1:
res = 2
elif isinstance(x, int):
res = x * 3
elif isinstance(x, str):
res = func_str(x)
elif isinstance(x, SomeType) and \
hasattr(x, 'c') and \
hasattr(x, 'd') and \
x.c == x.d:
res = on_val(x.c)
elif isinstance(x, SomeType) and \
hasattr(x, 'c') and \
hasattr(x, 'd'):
res = on_val2(x, x.c)
else:
raise ValueError("{0!r} don't match any pattern!".format(x))</font></pre></span><p style="text-indent:20px">Совсем так, как хотелось, сразу не вышло. Вышло так:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="bfa3f5803d1511e1b4cc14feb5b819a0" objtohide2="bfa3f88c3d1511e1b4cc14feb5b819a0" >Без подсветки синтаксиса</a><br><span id="bfa3f5803d1511e1b4cc14feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">python_match</span>
<span style="color: #AA22FF">@python_match.mathing</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">come_func</span>():
<span style="color: #408080; font-style: italic"># some code</span>
<span style="color: #008000; font-weight: bold">with</span> python_match<span style="color: #666666">.</span>match(x) <span style="color: #008000; font-weight: bold">as</span> res:
<span style="color: #666666">1</span> <span style="color: #666666">>></span> <span style="color: #666666">2</span>
<span style="color: #008000">int</span> <span style="color: #666666">>></span> x <span style="color: #666666">*</span> <span style="color: #666666">3</span>
<span style="color: #008000">str</span> <span style="color: #666666">>></span> func_str(x)
SomeType(c<span style="color: #666666">=</span>V_c, d<span style="color: #666666">=</span>V_c) <span style="color: #666666">>></span> on_val(V_c)
SomeType(c<span style="color: #666666">=</span>V_c, d<span style="color: #666666">=</span>V_d) <span style="color: #666666">>></span> on_val2(x, V_c)
<span style="color: #008000; font-weight: bold">print</span> res<span style="color: #666666">.</span>val
</pre></div></span><span style="line-height:100%;display:none" id="bfa3f88c3d1511e1b4cc14feb5b819a0"><pre><font face="courier">import python_match
@python_match.mathing
def come_func():
# some code
with python_match.match(x) as res:
1 >> 2
int >> x * 3
str >> func_str(x)
SomeType(c=V_c, d=V_c) >> on_val(V_c)
SomeType(c=V_c, d=V_d) >> on_val2(x, V_c)
print res.val</font></pre></span><p style="text-indent:20px"> Из необязательных ограничений - нужно импортировать модуль <b>python_match</b> без переименования. Обернуть все функции, где используется сопоставление с образцом, декоратором '<b>python_match.mathing</b>'.</p><p style="text-indent:20px">Как это работает:</p><ul><li>декоратор с помощью модуля <b>inspect</b> получает исходный код функции, разбирает его в ast и прогоняет через класс <b>MatchReplacer</b><li><b>MatchReplacer</b> наследует <b>ast.NodeTransformer</b> и перегружает метод <b>visit_With</b>, в котором подменяет ноду <b>with</b> на измененную конструкцию со сравнениями. Строка до <b>>></b> изменяется на сравнение, а в строка после - подменяются переменные.<li>класс <b>Match</b> делает сопоставление объектов с образцом, если использовалось сравнение атрибутов.</ul><p style="text-indent:20px"> Осталось некоторое количество ограничений, которые однако не принципиальные, так что поскольку задача скорее стояла из разряда - "как бы это сделать" я не стал заниматься дальнейшими оптимизациями/улучшениями.</p><p style="text-indent:20px">Полный код тут - <a href="https://github.com/koder-ua/python-lectures/blob/master/python_match.py">python_match.py</a>, <a href="https://github.com/koder-ua/python-lectures/blob/master/test_pm.py">test_pm.py</a>.</p>Ссылки:<br> <a name="haskell"><a href="http://ru.wikipedia.org/wiki/haskell">ru.wikipedia.org/wiki/haskell</a></a><br> <a name="erlang"><a href="http://ru.wikipedia.org/wiki/Erlang">ru.wikipedia.org/wiki/Erlang</a></a><br> <a name="сопоставление_с_образцом"><a href="http://en.wikipedia.org/wiki/Pattern_matching">en.wikipedia.org/wiki/Pattern_matching</a></a><br> <a name="wiki"><a href="http://en.wikibooks.org/wiki/Haskell/Pattern_matching">en.wikibooks.org/wiki/Haskell/Pattern_matching</a></a><br> <a name="ast"><a href="http://docs.python.org/library/ast.html">docs.python.org/library/ast.html</a></a><br> <a name="python_match.py"><a href="https://github.com/koder-ua/python-lectures/blob/master/python_match.py">github.com/koder-ua/python-lectures/blob/master/python_match.py</a></a><br> <a name="test_pm.py"><a href="https://github.com/koder-ua/python-lectures/blob/master/test_pm.py">github.com/koder-ua/python-lectures/blob/master/test_pm.py</a></a><br> <a name="abstract_syntax_tree"><a href="http://en.wikipedia.org/wiki/Abstract_syntax_tree">en.wikipedia.org/wiki/Abstract_syntax_tree</a></a><br><p style="text-indent:20px">Исходники этого и других постов со скриптами лежат тут - <a href="https://github.com/koder-ua/python-lectures">github.com/koder-ua</a>. При использовании их, пожалуйста, ссылайтесь на <a href="http://koder-ua.blogspot.com/">koder-ua.blogspot.com</a>.</p>
<script type="text/javascript">
function on_hidabble_click()
{
var me = $(this);
var hide_id = me.attr("objtohide");
var controlled_object = $('#' + hide_id);
controlled_object.toggle();
if ( controlled_object.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".hidder").click(on_hidabble_click);
function on_double_hidabble_click()
{
var me = $(this);
var hide_id1 = me.attr("objtohide1");
var hide_id2 = me.attr("objtohide2");
var controlled_object1 = $('#' + hide_id1);
var controlled_object2 = $('#' + hide_id2);
controlled_object1.toggle();
controlled_object2.toggle();
if ( controlled_object1.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".dhidder").click(on_double_hidabble_click);
</script>Anonymoushttp://www.blogger.com/profile/06645078574760734586noreply@blogger.com2tag:blogger.com,1999:blog-1174489715777430743.post-40915006148382683552011-12-24T14:41:00.000+02:002012-01-18T08:06:22.870+02:00libvirt & Co. Облако "на коленке". Часть 2 - Сети<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<br><h2>Компоненты</h2><p style="text-indent:20px"> Функционирование виртуальных сетей обеспечивается различными технологиями, которые я бегло опишу:</p><p style="text-indent:20px"> <a href="http://www.linuxfoundation.org/collaborate/workgroups/networking/bridge">bridges</a> - сетевые мосты - программные аналоги свичей, позволяют соединить вместе несколько сетевых интерфейсов и передавать между ними пакеты, как если бы они были включены в один свич. Бриджи управляются с помощью команды <b>brctl</b>:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="395207402f2011e1ad4914feb5b819a0" objtohide2="39527f402f2011e1ad4914feb5b819a0" >Без подсветки синтаксиса</a><br><span id="395207402f2011e1ad4914feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">$</span> brctl show <span style="color: #408080; font-style: italic"># напечатать все бриджи с подключенными интерфейсами</span>
<span style="color: #808080">bridge name bridge id STP enabled interfaces </span>
<span style="color: #808080">virbr0 8000.000000000000 yes </span>
<span style="color: #000080; font-weight: bold">#</span> brctl add <name> - добавить бридж
<span style="color: #000080; font-weight: bold">#</span> brctl addif <brname> <ifname> - включить eth в бридж
<span style="color: #000080; font-weight: bold">#</span> brctl delif <brname> <ifname> - отключить интерфейс
<span style="color: #000080; font-weight: bold">#</span> brctl delbr <brname> - удалить бридж
</pre></div></span><span style="line-height:100%;display:none" id="39527f402f2011e1ad4914feb5b819a0"><pre><font face="courier">$ brctl show # напечатать все бриджи с подключенными интерфейсами
bridge name bridge id STP enabled interfaces
virbr0 8000.000000000000 yes
# brctl add <name> - добавить бридж
# brctl addif <brname> <ifname> - включить eth в бридж
# brctl delif <brname> <ifname> - отключить интерфейс
# brctl delbr <brname> - удалить бридж</font></pre></span><p style="text-indent:20px"> Перед работой с бриджами лучше ознакомиться с документацией, они содержат некоторое количество нетривиальных моментов.</p><a name='more'></a><p style="text-indent:20px"> <a href="http://backreference.org/2010/03/26/tuntap-interface-tutorial">tun</a> (<a href="http://en.wikipedia.org/wiki/TUN/TAP">tap</a>) - виртуальные сетевые интерфейсы. В отличии от аппаратных привязаны к определенному процессу пользовательского режима, а не к сетевой карте. Родительский процесс может писать/читать данные из виртуального интерфейса имитируя работу сети. В остальном они не отличаются от обычных интерфейсов. С помощью tun/tap работают многие VNP программы, например openvpn, которая создает tun/tap, вычитывает из него данные, шифрует и переправляет по обычной сети на другой компьютер, где второй процесс openvpn принимает данные, расшифровывает и записывает в свой tun/tap, имитируя прямое сетевое соединение между удаленными компьютерами. Как и 95% всех сетевых возможностей linux tun/tap можно управлять с помошью утилиты ip. Пример использования tun из python можно найти тут <a href="http://kharkovpromenade.com.ua/?id=9">kharkovpromenade</a>. Tun используются для создания сетевых интерфейсов виртуальынх машин.</p><p style="text-indent:20px"> <a href="http://en.wikipedia.org/wiki/Iptables">iptables</a> - система управления сетевым трафиком в linux. Обеспечивает фильтрация и модификацию трафика, управление сетевыми соединениями, etc. Возможности iptables чрезвычайно обширные и описывать даже примерно я их не буду, приведу только команды, позволяющие увидеть все правила на компьютере:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="3953bacc2f2011e1ad4914feb5b819a0" objtohide2="3953fbfe2f2011e1ad4914feb5b819a0" >Без подсветки синтаксиса</a><br><span id="3953bacc2f2011e1ad4914feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">#</span> iptables -t nat -S
<span style="color: #000080; font-weight: bold">#</span> iptables -t filter -S
<span style="color: #000080; font-weight: bold">#</span> iptables -t raw -S
<span style="color: #000080; font-weight: bold">#</span> iptables -t mangle -S
</pre></div></span><span style="line-height:100%;display:none" id="3953fbfe2f2011e1ad4914feb5b819a0"><pre><font face="courier"># iptables -t nat -S
# iptables -t filter -S
# iptables -t raw -S
# iptables -t mangle -S</font></pre></span><p style="text-indent:20px">Все правила легко читаются даже без знания iptables.</p><p style="text-indent:20px"> Ок, с этим багажом уже можно разбираться с виртуальными сетями. Для большинства случаев нам не придется делать сети самостоятельно - libvirt берет эту работу на себя, предоставляя нам готовый <a href="http://wiki.libvirt.org/page/Networking">результат</a>. Начнем с устройства простейшей сети, которую со старта создает libvirt - defaults.</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="395462ce2f2011e1ad4914feb5b819a0" objtohide2="395467422f2011e1ad4914feb5b819a0" >Без подсветки синтаксиса</a><br><span id="395462ce2f2011e1ad4914feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">#</span> virsh net-list
<span style="color: #808080">Name State Autostart</span>
<span style="color: #808080">-----------------------------------------</span>
<span style="color: #808080">default active yes</span>
</pre></div></span><span style="line-height:100%;display:none" id="395467422f2011e1ad4914feb5b819a0"><pre><font face="courier"># virsh net-list
Name State Autostart
-----------------------------------------
default active yes</font></pre></span><p style="text-indent:20px">Описание этой сети можно получить с помощью следующих команд:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="395497622f2011e1ad4914feb5b819a0" objtohide2="395498de2f2011e1ad4914feb5b819a0" >Без подсветки синтаксиса</a><br><span id="395497622f2011e1ad4914feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">#</span> virsh net-info default
<span style="color: #808080">Name default</span>
<span style="color: #808080">UUID c598e36f-31fd-672e-09e3-2cbe061cd606</span>
<span style="color: #808080">Active: yes</span>
<span style="color: #808080">Persistent: yes</span>
<span style="color: #808080">Autostart: yes</span>
<span style="color: #808080">Bridge: virbr0</span>
<span style="color: #000080; font-weight: bold">#</span> virsh net-dumpxml default
</pre></div></span><span style="line-height:100%;display:none" id="395498de2f2011e1ad4914feb5b819a0"><pre><font face="courier"># virsh net-info default
Name default
UUID c598e36f-31fd-672e-09e3-2cbe061cd606
Active: yes
Persistent: yes
Autostart: yes
Bridge: virbr0
# virsh net-dumpxml default</font></pre></span><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="3954fb082f2011e1ad4914feb5b819a0" objtohide2="3954fc8e2f2011e1ad4914feb5b819a0" >Без подсветки синтаксиса</a><br><span id="3954fb082f2011e1ad4914feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold"><network></span>
<span style="color: #008000; font-weight: bold"><name></span>default<span style="color: #008000; font-weight: bold"></name></span>
<span style="color: #008000; font-weight: bold"><uuid></span>c598e36f-31fd-672e-09e3-2cbe061cd606<span style="color: #008000; font-weight: bold"></uuid></span>
<span style="color: #008000; font-weight: bold"><forward</span> <span style="color: #7D9029">mode=</span><span style="color: #BA2121">'nat'</span><span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><bridge</span> <span style="color: #7D9029">name=</span><span style="color: #BA2121">'virbr0'</span> <span style="color: #7D9029">stp=</span><span style="color: #BA2121">'on'</span> <span style="color: #7D9029">delay=</span><span style="color: #BA2121">'0'</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><ip</span> <span style="color: #7D9029">address=</span><span style="color: #BA2121">'192.168.122.1'</span> <span style="color: #7D9029">netmask=</span><span style="color: #BA2121">'255.255.255.0'</span><span style="color: #008000; font-weight: bold">></span>
<span style="color: #008000; font-weight: bold"><dhcp></span>
<span style="color: #008000; font-weight: bold"><range</span> <span style="color: #7D9029">start=</span><span style="color: #BA2121">'192.168.122.40'</span> <span style="color: #7D9029">end=</span><span style="color: #BA2121">'192.168.122.254'</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"></dhcp></span>
<span style="color: #008000; font-weight: bold"></ip></span>
<span style="color: #008000; font-weight: bold"></network></span>
</pre></div></span><span style="line-height:100%;display:none" id="3954fc8e2f2011e1ad4914feb5b819a0"><pre><font face="courier"><network>
<name>default</name>
<uuid>c598e36f-31fd-672e-09e3-2cbe061cd606</uuid>
<forward mode='nat'/>
<bridge name='virbr0' stp='on' delay='0' />
<ip address='192.168.122.1' netmask='255.255.255.0'>
<dhcp>
<range start='192.168.122.40' end='192.168.122.254' />
</dhcp>
</ip>
</network></font></pre></span><p style="text-indent:20px">Тот же самый результат можно получить и из python:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="39e9afa02f2011e1ad4914feb5b819a0" objtohide2="39e9b1802f2011e1ad4914feb5b819a0" >Без подсветки синтаксиса</a><br><span id="39e9afa02f2011e1ad4914feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">libvirt</span>
<span style="color: #008000; font-weight: bold">from</span> <span style="color: #0000FF; font-weight: bold">xml.etree.ElementTree</span> <span style="color: #008000; font-weight: bold">import</span> fromstring
conn <span style="color: #666666">=</span> libvirt<span style="color: #666666">.</span>open(<span style="color: #BA2121">"qemu:///system"</span>)
net <span style="color: #666666">=</span> conn<span style="color: #666666">.</span>networkLookupByName(<span style="color: #BA2121">'default'</span>)
xml <span style="color: #666666">=</span> fromstring(net<span style="color: #666666">.</span>XMLDesc(<span style="color: #666666">0</span>))
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"default net addr ="</span>, xml<span style="color: #666666">.</span>find(<span style="color: #BA2121">'ip'</span>)<span style="color: #666666">.</span>attrib[<span style="color: #BA2121">'address'</span>]
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"default net mask ="</span>, xml<span style="color: #666666">.</span>find(<span style="color: #BA2121">'ip'</span>)<span style="color: #666666">.</span>attrib[<span style="color: #BA2121">'netmask'</span>]
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"default net bridge ="</span>, xml<span style="color: #666666">.</span>find(<span style="color: #BA2121">'bridge'</span>)<span style="color: #666666">.</span>attrib[<span style="color: #BA2121">'name'</span>]
</pre></div></span><span style="line-height:100%;display:none" id="39e9b1802f2011e1ad4914feb5b819a0"><pre><font face="courier">import libvirt
from xml.etree.ElementTree import fromstring
conn = libvirt.open("qemu:///system")
net = conn.networkLookupByName('default')
xml = fromstring(net.XMLDesc(0))
print "default net addr =", xml.find('ip').attrib['address']
print "default net mask =", xml.find('ip').attrib['netmask']
print "default net bridge =", xml.find('bridge').attrib['name']</font></pre></span><p style="text-indent:20px">Еще один важный компонент сети - dnsmasq:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="39ebb9942f2011e1ad4914feb5b819a0" objtohide2="39ebbc0a2f2011e1ad4914feb5b819a0" >Без подсветки синтаксиса</a><br><span id="39ebb9942f2011e1ad4914feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">$</span> ps aux | grep dnsmasq | grep -v grep
<span style="color: #808080"> nobody 4503 0.0 0.0 25836 976 ? S 02:08 0:00 </span>
<span style="color: #808080"> dnsmasq --strict-order --bind-interfaces </span>
<span style="color: #808080"> --pid-file=/var/run/libvirt/network/default.pid </span>
<span style="color: #808080"> --conf-file= --except-interface lo </span>
<span style="color: #808080"> --listen-address 192.168.122.1 </span>
<span style="color: #808080"> --dhcp-range 192.168.122.40,192.168.122.254 </span>
<span style="color: #808080"> --dhcp-leasefile=/var/lib/libvirt/dnsmasq/default.leases </span>
<span style="color: #808080"> --dhcp-lease-max=215 --dhcp-no-override</span>
</pre></div></span><span style="line-height:100%;display:none" id="39ebbc0a2f2011e1ad4914feb5b819a0"><pre><font face="courier">$ ps aux | grep dnsmasq | grep -v grep
nobody 4503 0.0 0.0 25836 976 ? S 02:08 0:00
dnsmasq --strict-order --bind-interfaces
--pid-file=/var/run/libvirt/network/default.pid
--conf-file= --except-interface lo
--listen-address 192.168.122.1
--dhcp-range 192.168.122.40,192.168.122.254
--dhcp-leasefile=/var/lib/libvirt/dnsmasq/default.leases
--dhcp-lease-max=215 --dhcp-no-override</font></pre></span><p style="text-indent:20px">Конфигурационные файлы сетей хранятся в /var/lib/libvirt/network:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="39ebe37e2f2011e1ad4914feb5b819a0" objtohide2="39ebe5222f2011e1ad4914feb5b819a0" >Без подсветки синтаксиса</a><br><span id="39ebe37e2f2011e1ad4914feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">$</span> ls -l /var/lib/libvirt/network/
<span style="color: #808080">total 4</span>
<span style="color: #808080">-rw-r--r-- 1 root root 543 2011-12-24 02:08 default.xml</span>
</pre></div></span><span style="line-height:100%;display:none" id="39ebe5222f2011e1ad4914feb5b819a0"><pre><font face="courier">$ ls -l /var/lib/libvirt/network/
total 4
-rw-r--r-- 1 root root 543 2011-12-24 02:08 default.xml</font></pre></span><p style="text-indent:20px">Итак - что получилось в итоге. Вот эта строка конфигурационного файла:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="39ec08b82f2011e1ad4914feb5b819a0" objtohide2="39ec0a342f2011e1ad4914feb5b819a0" >Без подсветки синтаксиса</a><br><span id="39ec08b82f2011e1ad4914feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold"><interface</span> <span style="color: #7D9029">type=</span><span style="color: #BA2121">"network"</span><span style="color: #008000; font-weight: bold">></span>
<span style="color: #008000; font-weight: bold"><source</span> <span style="color: #7D9029">network=</span><span style="color: #BA2121">"default"</span> <span style="color: #008000; font-weight: bold">/></span> <span style="color: #408080; font-style: italic"><!-- эта девушка! --></span>
<span style="color: #008000; font-weight: bold"><forward</span> <span style="color: #7D9029">mode=</span><span style="color: #BA2121">"nat"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><target</span> <span style="color: #7D9029">dev=</span><span style="color: #BA2121">"vnet7"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><mac</span> <span style="color: #7D9029">address=</span><span style="color: #BA2121">"{mac}"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"></interface></span>
</pre></div></span><span style="line-height:100%;display:none" id="39ec0a342f2011e1ad4914feb5b819a0"><pre><font face="courier"><interface type="network">
<source network="default" /> <!-- эта девушка! -->
<forward mode="nat" />
<target dev="vnet7" />
<mac address="{mac}" />
</interface></font></pre></span><p style="text-indent:20px"> Подключила eth0 нашей виртуальной машины к бриджу <b>virbr0</b> сети <b>default</b>. Эта сеть имеет маску 192.168.122.0/24, подключена через <a href="http://en.wikipedia.org/wiki/Network_address_translation">NAT</a> к внешнему миру и обслуживается dhcp сервером. Причем сам virbr0 имеет ip 192.168.122.1 и служит гейтом для этой сети. Адреса из диапазона 192.168.122.2-192.168.122.40 я ранее зарезервировал для ручного распределения, отредактировав и перезапустив сеть.</p><p style="text-indent:20px"> Теперь вернемся к начальному вопросу - как программно узнать ip адрес, выданный нашей виртуалке? Есть три основных способа:</p><ul><li>Если с виртуальной машиной уже был обмен данными, то можно посмотреть в кеше маршрутизации '<b>ip route show cache | grep virbr0</b>' или в кеше аппаратных адресов - '<b>arp -na</b>'. Способ наименее надежный, так как если обмена не было кеши будут пустые.<li>Достать информацию из базы dhcp сервера - leases. Для dnsmasq это по умолчанию файл /var/lib/libvirt/dnsmasq/default.leases:</ul><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="39ec46842f2011e1ad4914feb5b819a0" objtohide2="39ec480a2f2011e1ad4914feb5b819a0" >Без подсветки синтаксиса</a><br><span id="39ec46842f2011e1ad4914feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">$</span> cat /var/lib/libvirt/dnsmasq/default.leases
<span style="color: #808080">1324718340 00:44:01:61:78:01 192.168.122.99 * *</span>
</pre></div></span><span style="line-height:100%;display:none" id="39ec480a2f2011e1ad4914feb5b819a0"><pre><font face="courier">$ cat /var/lib/libvirt/dnsmasq/default.leases
1324718340 00:44:01:61:78:01 192.168.122.99 * *</font></pre></span><p style="text-indent:20px"> В принципе это уже что-то, но такой способ зависимый от многих факторов - в других дистрибутивах или при других настройках путь к файлу может поменяться, виртуальная машина может использовать другой dhcp сервер или вообще иметь статический ip адрес.</p><ul><li>Получить адрес с помощью arp сканирования. Фактически в сеть посылается набор запросов вида "у кого есть такой ip addres". Этот запрос используется что-бы определить mac адрес машины с заданным ip, перед формирование ethernet фрейма. Способ наиболее универсальный, поскольку именно так работает tcp/ip стек - независимо от того как система получила этот ip адрес она должна ответить на arp запрос или будет вообще недоступна для ip протокола.</ul><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="39ec827a2f2011e1ad4914feb5b819a0" objtohide2="39ec83f62f2011e1ad4914feb5b819a0" >Без подсветки синтаксиса</a><br><span id="39ec827a2f2011e1ad4914feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">#</span> arp-scan -I virbr0 -l <span style="color: #408080; font-style: italic"># использовать маску сети из описания интерфейса</span>
<span style="color: #000080; font-weight: bold">#</span> arp-scan -I virbr0 192.168.122.0/24 <span style="color: #408080; font-style: italic"># то же самое, только руками</span>
<span style="color: #808080">Interface: virbr0, datalink type: EN10MB (Ethernet)</span>
<span style="color: #808080">Starting arp-scan 1.8.1 with 256 hosts (http://www.nta-monitor.com/tools/arp-scan/)</span>
<span style="color: #808080">192.168.122.99 00:44:01:61:78:01 (Unknown)</span>
<span style="color: #808080">1 packets received by filter, 0 packets dropped by kernel</span>
<span style="color: #808080">Ending arp-scan 1.8.1: 256 hosts scanned in 1.374 seconds (186.32 hosts/sec). 1 responded</span>
</pre></div></span><span style="line-height:100%;display:none" id="39ec83f62f2011e1ad4914feb5b819a0"><pre><font face="courier"># arp-scan -I virbr0 -l # использовать маску сети из описания интерфейса
# arp-scan -I virbr0 192.168.122.0/24 # то же самое, только руками
Interface: virbr0, datalink type: EN10MB (Ethernet)
Starting arp-scan 1.8.1 with 256 hosts (http://www.nta-monitor.com/tools/arp-scan/)
192.168.122.99 00:44:01:61:78:01 (Unknown)
1 packets received by filter, 0 packets dropped by kernel
Ending arp-scan 1.8.1: 256 hosts scanned in 1.374 seconds (186.32 hosts/sec). 1 responded</font></pre></span><p style="text-indent:20px">Собственно 192.168.122.99 это и есть наш ip адрес (у вас он, естественно, может быть другим).</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="39eca7c82f2011e1ad4914feb5b819a0" objtohide2="39eca9442f2011e1ad4914feb5b819a0" >Без подсветки синтаксиса</a><br><span id="39eca7c82f2011e1ad4914feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">$</span> ip addr show virbr0
<span style="color: #808080">5: virbr0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN </span>
<span style="color: #808080"> link/ether ca:8e:0a:8b:36:14 brd ff:ff:ff:ff:ff:ff</span>
<span style="color: #808080"> inet 192.168.122.1/24 brd 192.168.122.255 scope global virbr0</span>
</pre></div></span><span style="line-height:100%;display:none" id="39eca9442f2011e1ad4914feb5b819a0"><pre><font face="courier">$ ip addr show virbr0
5: virbr0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN
link/ether ca:8e:0a:8b:36:14 brd ff:ff:ff:ff:ff:ff
inet 192.168.122.1/24 brd 192.168.122.255 scope global virbr0</font></pre></span><p style="text-indent:20px"> Сделаем то же самое на python с помощью <a href="http://www.secdev.org/projects/scapy/doc/">scapy</a>. Scapy вообще очень мощная библиотека для сетевых манипуляций, см. например <a href="http://www.secdev.org/projects/scapy/doc/usage.html#tcp-traceroute-2">scapy-traceroute</a>.</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="3b1971082f2011e1ad4914feb5b819a0" objtohide2="3b1972de2f2011e1ad4914feb5b819a0" >Без подсветки синтаксиса</a><br><span id="3b1971082f2011e1ad4914feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #408080; font-style: italic"># -*- coding:utf8 -*-</span>
<span style="color: #408080; font-style: italic"># для разбора/преобразования ip адресов</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">ipaddr</span>
<span style="color: #008000; font-weight: bold">from</span> <span style="color: #0000FF; font-weight: bold">scapy.all</span> <span style="color: #008000; font-weight: bold">import</span> srp, Ether, ARP, conf
<span style="color: #408080; font-style: italic"># scapy в основном ориентированна на интерактивное использование </span>
<span style="color: #408080; font-style: italic"># и по умолчанию выводит много информации в sys.stdout</span>
<span style="color: #408080; font-style: italic"># запретим это</span>
conf<span style="color: #666666">.</span>verb <span style="color: #666666">=</span> <span style="color: #666666">0</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">arp_scan</span>(ip_addr, netmask, iface):
netsize <span style="color: #666666">=</span> ipaddr<span style="color: #666666">.</span>IPNetwork(<span style="color: #BA2121">"{0}/{1}"</span><span style="color: #666666">.</span>format(ip_addr, netmask))<span style="color: #666666">.</span>prefixlen
ans, unans <span style="color: #666666">=</span> srp(
Ether(dst<span style="color: #666666">=</span><span style="color: #BA2121">"ff:ff:ff:ff:ff:ff"</span>) <span style="color: #666666">/</span> \
ARP(pdst<span style="color: #666666">=</span><span style="color: #BA2121">"{0}/{1}"</span><span style="color: #666666">.</span>format(ip_addr, netsize)),
timeout<span style="color: #666666">=0.1</span>, iface<span style="color: #666666">=</span>iface)
<span style="color: #008000; font-weight: bold">for</span> request, responce <span style="color: #AA22FF; font-weight: bold">in</span> ans:
<span style="color: #008000; font-weight: bold">yield</span> responce<span style="color: #666666">.</span>payload<span style="color: #666666">.</span>fields[<span style="color: #BA2121">'hwsrc'</span>], responce<span style="color: #666666">.</span>payload<span style="color: #666666">.</span>fields[<span style="color: #BA2121">'psrc'</span>]
<span style="color: #008000; font-weight: bold">for</span> hw_addr, ipaddr <span style="color: #AA22FF; font-weight: bold">in</span> arp_scan(<span style="color: #BA2121">'192.168.122.2'</span>, <span style="color: #BA2121">'255.255.255.0'</span>, <span style="color: #BA2121">'virbr0'</span>):
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"{0} => {1}"</span><span style="color: #666666">.</span>format(hw_addr, ipaddr)
</pre></div></span><span style="line-height:100%;display:none" id="3b1972de2f2011e1ad4914feb5b819a0"><pre><font face="courier"># -*- coding:utf8 -*-
# для разбора/преобразования ip адресов
import ipaddr
from scapy.all import srp, Ether, ARP, conf
# scapy в основном ориентированна на интерактивное использование
# и по умолчанию выводит много информации в sys.stdout
# запретим это
conf.verb = 0
def arp_scan(ip_addr, netmask, iface):
netsize = ipaddr.IPNetwork("{0}/{1}".format(ip_addr, netmask)).prefixlen
ans, unans = srp(
Ether(dst="ff:ff:ff:ff:ff:ff") / \
ARP(pdst="{0}/{1}".format(ip_addr, netsize)),
timeout=0.1, iface=iface)
for request, responce in ans:
yield responce.payload.fields['hwsrc'], responce.payload.fields['psrc']
for hw_addr, ipaddr in arp_scan('192.168.122.2', '255.255.255.0', 'virbr0'):
print "{0} => {1}".format(hw_addr, ipaddr)</font></pre></span><p style="text-indent:20px"> Теперь осталось объединить все это вместе - нам нужна функция, которая по имени виртуальной машины выдаст все ее ip адреса:</p><a hided_text="Показать код" visible_text="Скрыть код" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="hidder" objtohide="3b198d282f2011e1ad4914feb5b819a0">Показать код</a><br><span style="display:none" id="3b198d282f2011e1ad4914feb5b819a0"><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="3c79d7222f2011e1ad4914feb5b819a0" objtohide2="3c79d9702f2011e1ad4914feb5b819a0" >Без подсветки синтаксиса</a><br><span id="3c79d7222f2011e1ad4914feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #408080; font-style: italic"># -*- coding:utf8 -*-</span>
<span style="color: #408080; font-style: italic"># tiny_network.py</span>
<span style="color: #008000; font-weight: bold">from</span> <span style="color: #0000FF; font-weight: bold">xml.etree.ElementTree</span> <span style="color: #008000; font-weight: bold">import</span> fromstring
<span style="color: #408080; font-style: italic"># для разбора/преобразования ip адресов</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">ipaddr</span>
<span style="color: #008000; font-weight: bold">from</span> <span style="color: #0000FF; font-weight: bold">scapy.all</span> <span style="color: #008000; font-weight: bold">import</span> srp, Ether, ARP, conf
<span style="color: #408080; font-style: italic"># scapy в основном ориентированна на интерактивное использование </span>
<span style="color: #408080; font-style: italic"># и по умолчанию выводит много информации в sys.stdout</span>
<span style="color: #408080; font-style: italic"># запретим это</span>
conf<span style="color: #666666">.</span>verb <span style="color: #666666">=</span> <span style="color: #666666">0</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">arp_scan</span>(ip_addr, netmask, iface):
netsize <span style="color: #666666">=</span> ipaddr<span style="color: #666666">.</span>IPNetwork(<span style="color: #BA2121">"{0}/{1}"</span><span style="color: #666666">.</span>format(ip_addr, netmask))<span style="color: #666666">.</span>prefixlen
ans, unans <span style="color: #666666">=</span> srp(
Ether(dst<span style="color: #666666">=</span><span style="color: #BA2121">"ff:ff:ff:ff:ff:ff"</span>) <span style="color: #666666">/</span> \
ARP(pdst<span style="color: #666666">=</span><span style="color: #BA2121">"{0}/{1}"</span><span style="color: #666666">.</span>format(ip_addr, netsize)),
timeout<span style="color: #666666">=0.1</span>, iface<span style="color: #666666">=</span>iface)
<span style="color: #008000; font-weight: bold">for</span> request, responce <span style="color: #AA22FF; font-weight: bold">in</span> ans:
<span style="color: #008000; font-weight: bold">yield</span> responce<span style="color: #666666">.</span>payload<span style="color: #666666">.</span>fields[<span style="color: #BA2121">'hwsrc'</span>], responce<span style="color: #666666">.</span>payload<span style="color: #666666">.</span>fields[<span style="color: #BA2121">'psrc'</span>]
<span style="color: #408080; font-style: italic"># получаем все ip адреса домена vm</span>
<span style="color: #408080; font-style: italic"># conn - соединение с libvirt</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">get_all_ips</span>(conn, vm):
<span style="color: #408080; font-style: italic"># xml описание vm</span>
xml <span style="color: #666666">=</span> vm<span style="color: #666666">.</span>XMLDesc(<span style="color: #666666">0</span>)
x <span style="color: #666666">=</span> fromstring(xml)
<span style="color: #408080; font-style: italic"># находим все сетевые интерфейсы</span>
<span style="color: #008000; font-weight: bold">for</span> xml_iface <span style="color: #AA22FF; font-weight: bold">in</span> x<span style="color: #666666">.</span>findall(<span style="color: #BA2121">"devices/interface"</span>):
<span style="color: #408080; font-style: italic"># получаем их параметры</span>
netname <span style="color: #666666">=</span> xml_iface<span style="color: #666666">.</span>find(<span style="color: #BA2121">'source'</span>)<span style="color: #666666">.</span>attrib[<span style="color: #BA2121">'network'</span>]
lookup_hwaddr <span style="color: #666666">=</span> xml_iface<span style="color: #666666">.</span>find(<span style="color: #BA2121">'mac'</span>)<span style="color: #666666">.</span>attrib[<span style="color: #BA2121">'address'</span>]
<span style="color: #408080; font-style: italic"># получаем параметры сети</span>
net <span style="color: #666666">=</span> conn<span style="color: #666666">.</span>networkLookupByName(netname)
xml <span style="color: #666666">=</span> fromstring(net<span style="color: #666666">.</span>XMLDesc(<span style="color: #666666">0</span>))
attrs <span style="color: #666666">=</span> xml<span style="color: #666666">.</span>find(<span style="color: #BA2121">'ip'</span>)<span style="color: #666666">.</span>attrib
addr <span style="color: #666666">=</span> attrs[<span style="color: #BA2121">'address'</span>]
netmask <span style="color: #666666">=</span> attrs[<span style="color: #BA2121">'netmask'</span>]
br_name <span style="color: #666666">=</span> xml<span style="color: #666666">.</span>find(<span style="color: #BA2121">'bridge'</span>)<span style="color: #666666">.</span>attrib[<span style="color: #BA2121">'name'</span>]
<span style="color: #408080; font-style: italic"># сканируем сеть в поисках нашего аппаратного адреса</span>
<span style="color: #008000; font-weight: bold">for</span> hw_addr, ip_addr <span style="color: #AA22FF; font-weight: bold">in</span> arp_scan(addr, netmask, br_name):
<span style="color: #008000; font-weight: bold">if</span> hw_addr <span style="color: #666666">==</span> lookup_hwaddr:
<span style="color: #008000; font-weight: bold">yield</span> netname, br_name, hw_addr, ip_addr
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">main</span>():
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">sys</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">libvirt</span>
conn <span style="color: #666666">=</span> libvirt<span style="color: #666666">.</span>open(sys<span style="color: #666666">.</span>argv[<span style="color: #666666">1</span>])
vm <span style="color: #666666">=</span> conn<span style="color: #666666">.</span>lookupByName(sys<span style="color: #666666">.</span>argv[<span style="color: #666666">2</span>])
<span style="color: #008000; font-weight: bold">for</span> netname, br_name, hwaddr, ipaddr <span style="color: #AA22FF; font-weight: bold">in</span> get_all_ips(conn, vm):
<span style="color: #008000; font-weight: bold">print</span> netname, br_name, hwaddr, ipaddr
<span style="color: #008000; font-weight: bold">if</span> __name__ <span style="color: #666666">==</span> <span style="color: #BA2121">"__main__"</span>:
main()
</pre></div></span><span style="line-height:100%;display:none" id="3c79d9702f2011e1ad4914feb5b819a0"><pre><font face="courier"># -*- coding:utf8 -*-
# tiny_network.py
from xml.etree.ElementTree import fromstring
# для разбора/преобразования ip адресов
import ipaddr
from scapy.all import srp, Ether, ARP, conf
# scapy в основном ориентированна на интерактивное использование
# и по умолчанию выводит много информации в sys.stdout
# запретим это
conf.verb = 0
def arp_scan(ip_addr, netmask, iface):
netsize = ipaddr.IPNetwork("{0}/{1}".format(ip_addr, netmask)).prefixlen
ans, unans = srp(
Ether(dst="ff:ff:ff:ff:ff:ff") / \
ARP(pdst="{0}/{1}".format(ip_addr, netsize)),
timeout=0.1, iface=iface)
for request, responce in ans:
yield responce.payload.fields['hwsrc'], responce.payload.fields['psrc']
# получаем все ip адреса домена vm
# conn - соединение с libvirt
def get_all_ips(conn, vm):
# xml описание vm
xml = vm.XMLDesc(0)
x = fromstring(xml)
# находим все сетевые интерфейсы
for xml_iface in x.findall("devices/interface"):
# получаем их параметры
netname = xml_iface.find('source').attrib['network']
lookup_hwaddr = xml_iface.find('mac').attrib['address']
# получаем параметры сети
net = conn.networkLookupByName(netname)
xml = fromstring(net.XMLDesc(0))
attrs = xml.find('ip').attrib
addr = attrs['address']
netmask = attrs['netmask']
br_name = xml.find('bridge').attrib['name']
# сканируем сеть в поисках нашего аппаратного адреса
for hw_addr, ip_addr in arp_scan(addr, netmask, br_name):
if hw_addr == lookup_hwaddr:
yield netname, br_name, hw_addr, ip_addr
def main():
import sys
import libvirt
conn = libvirt.open(sys.argv[1])
vm = conn.lookupByName(sys.argv[2])
for netname, br_name, hwaddr, ipaddr in get_all_ips(conn, vm):
print netname, br_name, hwaddr, ipaddr
if __name__ == "__main__":
main()</font></pre></span></span><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="3c7a1e3a2f2011e1ad4914feb5b819a0" objtohide2="3c7a20102f2011e1ad4914feb5b819a0" >Без подсветки синтаксиса</a><br><span id="3c7a1e3a2f2011e1ad4914feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">#</span> python test.py qemu:///system debian
<span style="color: #808080">WARNING: No route found for IPv6 destination :: (no default route?)</span>
<span style="color: #808080">default virbr0 00:44:01:61:78:01 192.168.122.99</span>
<span style="color: #000080; font-weight: bold">#</span> ssh root@192.168.122.99
<span style="color: #808080">blah-blah-blah</span>
<span style="color: #000080; font-weight: bold">root@debian-amd64:~#</span>
</pre></div></span><span style="line-height:100%;display:none" id="3c7a20102f2011e1ad4914feb5b819a0"><pre><font face="courier"># python test.py qemu:///system debian
WARNING: No route found for IPv6 destination :: (no default route?)
default virbr0 00:44:01:61:78:01 192.168.122.99
# ssh root@192.168.122.99
blah-blah-blah
root@debian-amd64:~#</font></pre></span><p style="text-indent:20px"> Отлично. Имея доступ по ssh мы уже может делать с vm что нам нужно. Добавим в tiny_cloud.py автоматический логин в vm по имени. Для этого воспользуемся утилитой <a href="http://en.wikipedia.org/wiki/Expect">expect</a></p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="3c7a42482f2011e1ad4914feb5b819a0" objtohide2="3c7a43c42f2011e1ad4914feb5b819a0" >Без подсветки синтаксиса</a><br><span id="3c7a42482f2011e1ad4914feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">#</span> apt-get install expect
</pre></div></span><span style="line-height:100%;display:none" id="3c7a43c42f2011e1ad4914feb5b819a0"><pre><font face="courier"># apt-get install expect</font></pre></span><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="3cc457202f2011e1ad4914feb5b819a0" objtohide2="3cc459502f2011e1ad4914feb5b819a0" >Без подсветки синтаксиса</a><br><span id="3cc457202f2011e1ad4914feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">expect_login <span style="color: #666666">=</span> <span style="color: #BA2121">"""expect -c'</span>
<span style="color: #BA2121">spawn ssh {0}@{1};</span>
<span style="color: #BA2121">while {{1}} {{</span>
<span style="color: #BA2121"> expect {{</span>
<span style="color: #BA2121"> eof {{ break }};</span>
<span style="color: #BA2121"> "The authenticity of host" {{ send "yes</span><span style="color: #BB6622; font-weight: bold">\\</span><span style="color: #BA2121">n" }};</span>
<span style="color: #BA2121"> "password:" {{ send "{2}</span><span style="color: #BB6622; font-weight: bold">\\</span><span style="color: #BA2121">n"; interact; break;}};</span>
<span style="color: #BA2121"> }};</span>
<span style="color: #BA2121">}};</span>
<span style="color: #BA2121">wait'</span>
<span style="color: #BA2121">"""</span>
found <span style="color: #666666">=</span> <span style="color: #008000">False</span>
<span style="color: #008000; font-weight: bold">for</span> ipaddr <span style="color: #AA22FF; font-weight: bold">in</span> get_all_ips(conn, name):
<span style="color: #408080; font-style: italic"># проверяем, что ssh порт открыт на этом ip</span>
s <span style="color: #666666">=</span> socket<span style="color: #666666">.</span>socket()
s<span style="color: #666666">.</span>settimeout(<span style="color: #666666">1</span>)
<span style="color: #008000; font-weight: bold">try</span>:
s<span style="color: #666666">.</span>connect((ipaddr, <span style="color: #666666">22</span>))
<span style="color: #008000; font-weight: bold">except</span> socket<span style="color: #666666">.</span>error:
<span style="color: #008000; font-weight: bold">raise</span>
<span style="color: #408080; font-style: italic"># запускаем ssh и ждем пока пользователь завершит сеанс</span>
os<span style="color: #666666">.</span>system(expect_login<span style="color: #666666">.</span>format(user, ipaddr, passwd))
found <span style="color: #666666">=</span> <span style="color: #008000">True</span>
<span style="color: #008000; font-weight: bold">break</span>
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #AA22FF; font-weight: bold">not</span> found:
<span style="color: #008000; font-weight: bold">raise</span> <span style="color: #D2413A; font-weight: bold">RuntimeError</span>(<span style="color: #BA2121">"No one interface of {0} accepts ssh connection"</span><span style="color: #666666">.</span>format(name))
</pre></div></span><span style="line-height:100%;display:none" id="3cc459502f2011e1ad4914feb5b819a0"><pre><font face="courier">expect_login = """expect -c'
spawn ssh {0}@{1};
while {{1}} {{
expect {{
eof {{ break }};
"The authenticity of host" {{ send "yes\\n" }};
"password:" {{ send "{2}\\n"; interact; break;}};
}};
}};
wait'
"""
found = False
for ipaddr in get_all_ips(conn, name):
# проверяем, что ssh порт открыт на этом ip
s = socket.socket()
s.settimeout(1)
try:
s.connect((ipaddr, 22))
except socket.error:
raise
# запускаем ssh и ждем пока пользователь завершит сеанс
os.system(expect_login.format(user, ipaddr, passwd))
found = True
break
if not found:
raise RuntimeError("No one interface of {0} accepts ssh connection".format(name))</font></pre></span><p style="text-indent:20px"> Для серьезного использования написанный сетевой код необходимо дорабатывать, что-бы он мог обрабатывать другие варианты сетей, но для домашнего использования сойдет.</p><p style="text-indent:20px"> Возможности libvirt по управлению сетями очень обширны и хорошо описаны в ее документации. В частности libvirt позволяет создавать сети с прямым подключением к lan, частные сети и "спрятанные" за NAT, а так-же умеет проброс портов. Для основных задач этого вполне достаточно.</p><p style="text-indent:20px"> В качестве примера сделаем сеть из 3х виртуалок, две из которых будут в частной lan сети <b>local_net</b>, а третья одним интерфейсом в <b>default</b>, а вторым в <b>local_net</b>.</p><p style="text-indent:20px"> Для этого нужно немного подправить образ. Логинимся в vm и удаляем файлы /etc/udev/rules.d/70-persistent-net.rules и /etc/udev/rules.d/010_netinterfaces.rules. В противном случае debian будет переименовывать сетевые интерфейсы при смене аппаратного адреса. Выполняем <b>poweroff</b>, отлогиниваемся и ждем пока vm остановится - python tiny_cloud.py list перестанет ее показывать и сделаем три копии образа vm - deb1.qcow2, deb2.qcow2, deb3.qcow2.</p><p style="text-indent:20px"> Сделаем новую сеть, для этого определим ее в <a href="http://libvirt.org/formatnetwork.html">xml формате для сети</a></p><a hided_text="Показать код" visible_text="Скрыть код" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="hidder" objtohide="3cc48fd82f2011e1ad4914feb5b819a0">Показать код</a><br><span style="display:none" id="3cc48fd82f2011e1ad4914feb5b819a0"><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="3cc4dbf02f2011e1ad4914feb5b819a0" objtohide2="3cc4dd6c2f2011e1ad4914feb5b819a0" >Без подсветки синтаксиса</a><br><span id="3cc4dbf02f2011e1ad4914feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #BC7A00"><?xml version="1.0" encoding="utf-8" ?></span>
<span style="color: #008000; font-weight: bold"><network></span>
<span style="color: #008000; font-weight: bold"><name></span>local_net<span style="color: #008000; font-weight: bold"></name></span>
<span style="color: #008000; font-weight: bold"><bridge</span> <span style="color: #7D9029">name=</span><span style="color: #BA2121">"virbr2"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><ip</span> <span style="color: #7D9029">address=</span><span style="color: #BA2121">"192.168.152.1"</span> <span style="color: #7D9029">netmask=</span><span style="color: #BA2121">"255.255.255.0"</span><span style="color: #008000; font-weight: bold">></span>
<span style="color: #008000; font-weight: bold"><dhcp></span>
<span style="color: #008000; font-weight: bold"><range</span> <span style="color: #7D9029">start=</span><span style="color: #BA2121">"192.168.152.2"</span> <span style="color: #7D9029">end=</span><span style="color: #BA2121">"192.168.152.254"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"></dhcp></span>
<span style="color: #008000; font-weight: bold"></ip></span>
<span style="color: #008000; font-weight: bold"></network></span>
</pre></div></span><span style="line-height:100%;display:none" id="3cc4dd6c2f2011e1ad4914feb5b819a0"><pre><font face="courier"><?xml version="1.0" encoding="utf-8" ?>
<network>
<name>local_net</name>
<bridge name="virbr2" />
<ip address="192.168.152.1" netmask="255.255.255.0">
<dhcp>
<range start="192.168.152.2" end="192.168.152.254" />
</dhcp>
</ip>
</network></font></pre></span></span><p style="text-indent:20px">Зарегистрируем ее в libvirt и активируем:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="3cc724d22f2011e1ad4914feb5b819a0" objtohide2="3cc72bda2f2011e1ad4914feb5b819a0" >Без подсветки синтаксиса</a><br><span id="3cc724d22f2011e1ad4914feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">#</span> virsh net-define local_net.xml
<span style="color: #000080; font-weight: bold">#</span> virsh net-list --all
<span style="color: #808080">Name State Autostart</span>
<span style="color: #808080">-----------------------------------------</span>
<span style="color: #808080">default active yes </span>
<span style="color: #808080">local_net inactive no</span>
<span style="color: #000080; font-weight: bold">#</span> virsh net-start local_net
<span style="color: #000080; font-weight: bold">#</span> virsh net-list
<span style="color: #808080">Name State Autostart</span>
<span style="color: #808080">-----------------------------------------</span>
<span style="color: #808080">default active yes </span>
<span style="color: #808080">local_net active no</span>
</pre></div></span><span style="line-height:100%;display:none" id="3cc72bda2f2011e1ad4914feb5b819a0"><pre><font face="courier"># virsh net-define local_net.xml
# virsh net-list --all
Name State Autostart
-----------------------------------------
default active yes
local_net inactive no
# virsh net-start local_net
# virsh net-list
Name State Autostart
-----------------------------------------
default active yes
local_net active no</font></pre></span><p style="text-indent:20px"> Проверяем, что все запустилось - в системе должно быть два dnsmasq сервера и должен добавить virbr2 мост, но правила для NAT не должны появиться в таблице filter. Обратите внимание - local_net не будет запускаться автоматически при старте компьютера, ее нужно активировать после каждого перезапуска перед стартом соответствующих vm или выполнить</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="3cc827c42f2011e1ad4914feb5b819a0" objtohide2="3cc82a122f2011e1ad4914feb5b819a0" >Без подсветки синтаксиса</a><br><span id="3cc827c42f2011e1ad4914feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">#</span> virsh net-autostart local_net
</pre></div></span><span style="line-height:100%;display:none" id="3cc82a122f2011e1ad4914feb5b819a0"><pre><font face="courier"># virsh net-autostart local_net</font></pre></span><p style="text-indent:20px"> Модифицируем шаблоны для vm - делаем две копии vm_templ.xml. В первой копии меняем сеть назначения для интерфейса на <b>local_net</b>, во второй добавляем еще один интерфейс, направленный на <b>local_net</b>. Так-же уменьшим объем оперативной памяти до 256М.</p><a hided_text="Показать код" visible_text="Скрыть код" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="hidder" objtohide="3cc83db82f2011e1ad4914feb5b819a0">Показать код</a><br><span style="display:none" id="3cc83db82f2011e1ad4914feb5b819a0"><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="3cc8682e2f2011e1ad4914feb5b819a0" objtohide2="3cc869c82f2011e1ad4914feb5b819a0" >Без подсветки синтаксиса</a><br><span id="3cc8682e2f2011e1ad4914feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #408080; font-style: italic"><!-- в vm_2if_templ.xml --></span>
<span style="color: #008000; font-weight: bold"><interface</span> <span style="color: #7D9029">type=</span><span style="color: #BA2121">"network"</span><span style="color: #008000; font-weight: bold">></span>
<span style="color: #008000; font-weight: bold"><source</span> <span style="color: #7D9029">network=</span><span style="color: #BA2121">"default"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><forward</span> <span style="color: #7D9029">mode=</span><span style="color: #BA2121">"nat"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><mac</span> <span style="color: #7D9029">address=</span><span style="color: #BA2121">"{mac1}"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"></interface></span>
<span style="color: #008000; font-weight: bold"><interface</span> <span style="color: #7D9029">type=</span><span style="color: #BA2121">"network"</span><span style="color: #008000; font-weight: bold">></span>
<span style="color: #008000; font-weight: bold"><source</span> <span style="color: #7D9029">network=</span><span style="color: #BA2121">"local_net"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><mac</span> <span style="color: #7D9029">address=</span><span style="color: #BA2121">"{mac2}"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"></interface></span>
<span style="color: #408080; font-style: italic"><!-- в vm_local_net_templ.xml --></span>
<span style="color: #008000; font-weight: bold"><interface</span> <span style="color: #7D9029">type=</span><span style="color: #BA2121">"network"</span><span style="color: #008000; font-weight: bold">></span>
<span style="color: #008000; font-weight: bold"><source</span> <span style="color: #7D9029">network=</span><span style="color: #BA2121">"local_net"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><mac</span> <span style="color: #7D9029">address=</span><span style="color: #BA2121">"{mac}"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"></interface></span>
</pre></div></span><span style="line-height:100%;display:none" id="3cc869c82f2011e1ad4914feb5b819a0"><pre><font face="courier"><!-- в vm_2if_templ.xml -->
<interface type="network">
<source network="default" />
<forward mode="nat" />
<mac address="{mac1}" />
</interface>
<interface type="network">
<source network="local_net" />
<mac address="{mac2}" />
</interface>
<!-- в vm_local_net_templ.xml -->
<interface type="network">
<source network="local_net" />
<mac address="{mac}" />
</interface></font></pre></span></span><p style="text-indent:20px">Запускаем систему:</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="3cc8a2302f2011e1ad4914feb5b819a0" objtohide2="3cc8a3c02f2011e1ad4914feb5b819a0" >Без подсветки синтаксиса</a><br><span id="3cc8a2302f2011e1ad4914feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">#</span> python tiny_cloud.py start --name deb1 --template vm_2if_templ.xml
<span style="color: #000080; font-weight: bold">#</span> python tiny_cloud.py start --name deb2 --template vm_local_net_templ.xml
<span style="color: #000080; font-weight: bold">#</span> python tiny_cloud.py start --name deb3 --template vm_local_net_templ.xml
<span style="color: #000080; font-weight: bold">#</span> python tiny_cloud.py list
<span style="color: #808080">5 deb3</span>
<span style="color: #808080">4 deb2</span>
<span style="color: #808080">3 deb1</span>
</pre></div></span><span style="line-height:100%;display:none" id="3cc8a3c02f2011e1ad4914feb5b819a0"><pre><font face="courier"># python tiny_cloud.py start --name deb1 --template vm_2if_templ.xml
# python tiny_cloud.py start --name deb2 --template vm_local_net_templ.xml
# python tiny_cloud.py start --name deb3 --template vm_local_net_templ.xml
# python tiny_cloud.py list
5 deb3
4 deb2
3 deb1</font></pre></span><p style="text-indent:20px">Немного модифицируем tiny_cloud.py, что-бы он показывал ip адреса виртуалок.</p><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="3cc8c3822f2011e1ad4914feb5b819a0" objtohide2="3cc8c4fe2f2011e1ad4914feb5b819a0" >Без подсветки синтаксиса</a><br><span id="3cc8c3822f2011e1ad4914feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">#</span> python tiny_cloud.py list
<span style="color: #808080">5 deb3 192.168.152.63</span>
<span style="color: #808080">4 deb2 192.168.152.62</span>
<span style="color: #808080">3 deb1 192.168.122.99,192.168.152.64</span>
</pre></div></span><span style="line-height:100%;display:none" id="3cc8c4fe2f2011e1ad4914feb5b819a0"><pre><font face="courier"># python tiny_cloud.py list
5 deb3 192.168.152.63
4 deb2 192.168.152.62
3 deb1 192.168.122.99,192.168.152.64</font></pre></span><p style="text-indent:20px"> Логинимся в deb1, смотрим на интерфейсы, проверяем, что 8.8.8.8(google DNS), deb2 и deb3 пингуются. Логинимся в deb2, проверяем что deb1 и deb3 пингуются, а 8.8.8.8 - нет.</p><p style="text-indent:20px"> Код tiny_cloud.py со всеми сегодняшними добавками:</p><a hided_text="Показать код" visible_text="Скрыть код" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="hidder" objtohide="3cc8e79a2f2011e1ad4914feb5b819a0">Показать код</a><br><span style="display:none" id="3cc8e79a2f2011e1ad4914feb5b819a0"><a hided_text="С подсветкой синтаксиса" visible_text="Без подсветки синтаксиса" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="3e6177162f2011e1ad4914feb5b819a0" objtohide2="3e6179282f2011e1ad4914feb5b819a0" >Без подсветки синтаксиса</a><br><span id="3e6177162f2011e1ad4914feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">os</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">sys</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">socket</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">argparse</span>
<span style="color: #008000; font-weight: bold">from</span> <span style="color: #0000FF; font-weight: bold">xml.etree.ElementTree</span> <span style="color: #008000; font-weight: bold">import</span> fromstring
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">libvirt</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">ipaddr</span>
<span style="color: #008000; font-weight: bold">from</span> <span style="color: #0000FF; font-weight: bold">scapy.all</span> <span style="color: #008000; font-weight: bold">import</span> srp, Ether, ARP, conf
conf<span style="color: #666666">.</span>verb <span style="color: #666666">=</span> <span style="color: #666666">0</span>
vm_sets <span style="color: #666666">=</span> \
{
<span style="color: #BA2121">'ubuntu'</span> :
{
<span style="color: #BA2121">'vcpu'</span> : <span style="color: #666666">1</span>,
<span style="color: #BA2121">'mem'</span> : <span style="color: #666666">1024</span> <span style="color: #666666">*</span> <span style="color: #666666">1024</span>, <span style="color: #408080; font-style: italic"># 1Gb RAM</span>
<span style="color: #BA2121">'mac'</span> : <span style="color: #BA2121">"00:44:01:61:78:01"</span>,
<span style="color: #BA2121">'image_file'</span> : <span style="color: #BA2121">'/home/koder/vm_images/ubuntu-server-nova-1.qcow2'</span>
},
<span style="color: #BA2121">'debian'</span> :
{
<span style="color: #BA2121">'vcpu'</span> : <span style="color: #666666">1</span>,
<span style="color: #BA2121">'mem'</span> : <span style="color: #666666">1024</span> <span style="color: #666666">*</span> <span style="color: #666666">1024</span>, <span style="color: #408080; font-style: italic"># 1Gb RAM</span>
<span style="color: #BA2121">'mac'</span> : <span style="color: #BA2121">"00:44:01:61:78:01"</span>,
<span style="color: #BA2121">'image_file'</span> : <span style="color: #BA2121">'/home/koder/vm_images/debian_squeeze_amd64_standard.qcow2'</span>
},
<span style="color: #BA2121">'deb1'</span> :
{
<span style="color: #BA2121">'vcpu'</span> : <span style="color: #666666">1</span>,
<span style="color: #BA2121">'mem'</span> : <span style="color: #666666">256</span> <span style="color: #666666">*</span> <span style="color: #666666">1024</span>, <span style="color: #408080; font-style: italic"># 256M RAM</span>
<span style="color: #BA2121">'mac1'</span> : <span style="color: #BA2121">"00:44:01:61:78:02"</span>,
<span style="color: #BA2121">'mac2'</span> : <span style="color: #BA2121">"00:44:01:61:78:05"</span>,
<span style="color: #BA2121">'image_file'</span> : <span style="color: #BA2121">'/home/koder/vm_images/deb1.qcow2'</span>,
},
<span style="color: #BA2121">'deb2'</span> :
{
<span style="color: #BA2121">'vcpu'</span> : <span style="color: #666666">1</span>,
<span style="color: #BA2121">'mem'</span> : <span style="color: #666666">256</span> <span style="color: #666666">*</span> <span style="color: #666666">1024</span>, <span style="color: #408080; font-style: italic"># 256M RAM</span>
<span style="color: #BA2121">'mac'</span> : <span style="color: #BA2121">"00:44:01:61:78:03"</span>,
<span style="color: #BA2121">'image_file'</span> : <span style="color: #BA2121">'/home/koder/vm_images/deb2.qcow2'</span>,
},
<span style="color: #BA2121">'deb3'</span> :
{
<span style="color: #BA2121">'vcpu'</span> : <span style="color: #666666">1</span>,
<span style="color: #BA2121">'mem'</span> : <span style="color: #666666">256</span> <span style="color: #666666">*</span> <span style="color: #666666">1024</span>, <span style="color: #408080; font-style: italic"># 256M RAM</span>
<span style="color: #BA2121">'mac'</span> : <span style="color: #BA2121">"00:44:01:61:78:04"</span>,
<span style="color: #BA2121">'image_file'</span> : <span style="color: #BA2121">'/home/koder/vm_images/deb3.qcow2'</span>,
}
}
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">arp_scan</span>(ip_addr, netmask, iface):
netsize <span style="color: #666666">=</span> ipaddr<span style="color: #666666">.</span>IPNetwork(<span style="color: #BA2121">"{0}/{1}"</span><span style="color: #666666">.</span>format(ip_addr, netmask))<span style="color: #666666">.</span>prefixlen
ans, unans <span style="color: #666666">=</span> srp(
Ether(dst<span style="color: #666666">=</span><span style="color: #BA2121">"ff:ff:ff:ff:ff:ff"</span>) <span style="color: #666666">/</span> \
ARP(pdst<span style="color: #666666">=</span><span style="color: #BA2121">"{0}/{1}"</span><span style="color: #666666">.</span>format(ip_addr, netsize)),
timeout<span style="color: #666666">=0.1</span>, iface<span style="color: #666666">=</span>iface)
<span style="color: #008000; font-weight: bold">for</span> request, responce <span style="color: #AA22FF; font-weight: bold">in</span> ans:
<span style="color: #008000; font-weight: bold">yield</span> responce<span style="color: #666666">.</span>payload<span style="color: #666666">.</span>fields[<span style="color: #BA2121">'hwsrc'</span>], responce<span style="color: #666666">.</span>payload<span style="color: #666666">.</span>fields[<span style="color: #BA2121">'psrc'</span>]
expect_login <span style="color: #666666">=</span> <span style="color: #BA2121">"""expect -c'</span>
<span style="color: #BA2121">spawn ssh {0}@{1};</span>
<span style="color: #BA2121">while {{1}} {{</span>
<span style="color: #BA2121"> expect {{</span>
<span style="color: #BA2121"> eof {{ break }};</span>
<span style="color: #BA2121"> "The authenticity of host" {{ send "yes</span><span style="color: #BB6622; font-weight: bold">\\</span><span style="color: #BA2121">n" }};</span>
<span style="color: #BA2121"> "password:" {{ send "{2}</span><span style="color: #BB6622; font-weight: bold">\\</span><span style="color: #BA2121">n"; interact; break;}};</span>
<span style="color: #BA2121"> }};</span>
<span style="color: #BA2121">}};</span>
<span style="color: #BA2121">wait'</span>
<span style="color: #BA2121">"""</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">TinyCloud</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>, conn):
<span style="color: #008000">self</span><span style="color: #666666">.</span>conn <span style="color: #666666">=</span> conn
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">start_vm</span>(<span style="color: #008000">self</span>, template, vmname):
vm_xml_templ <span style="color: #666666">=</span> <span style="color: #008000">open</span>(template)<span style="color: #666666">.</span>read()
vm_xml <span style="color: #666666">=</span> vm_xml_templ<span style="color: #666666">.</span>format(vmname<span style="color: #666666">=</span>vmname, <span style="color: #666666">**</span>vm_sets[vmname])
<span style="color: #008000">self</span><span style="color: #666666">.</span>conn<span style="color: #666666">.</span>createXML(vm_xml, <span style="color: #666666">0</span>)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">stop_vm</span>(<span style="color: #008000">self</span>, vmname):
vm <span style="color: #666666">=</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>conn<span style="color: #666666">.</span>lookupByName(vmname)
vm<span style="color: #666666">.</span>shutdown()
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">list_vms</span>(<span style="color: #008000">self</span>):
<span style="color: #008000; font-weight: bold">for</span> domain_id <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>conn<span style="color: #666666">.</span>listDomainsID():
<span style="color: #008000; font-weight: bold">yield</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>conn<span style="color: #666666">.</span>lookupByID(domain_id)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">get_network_data</span>(<span style="color: #008000">self</span>, name):
net <span style="color: #666666">=</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>conn<span style="color: #666666">.</span>networkLookupByName(name)
xml <span style="color: #666666">=</span> fromstring(net<span style="color: #666666">.</span>XMLDesc(<span style="color: #666666">0</span>))
attrs <span style="color: #666666">=</span> xml<span style="color: #666666">.</span>find(<span style="color: #BA2121">'ip'</span>)<span style="color: #666666">.</span>attrib
<span style="color: #008000; font-weight: bold">return</span> xml<span style="color: #666666">.</span>find(<span style="color: #BA2121">'bridge'</span>)<span style="color: #666666">.</span>attrib[<span style="color: #BA2121">'name'</span>], attrs[<span style="color: #BA2121">'address'</span>], attrs[<span style="color: #BA2121">'netmask'</span>]
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">get_all_ips</span>(<span style="color: #008000">self</span>, vmname):
vm <span style="color: #666666">=</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>conn<span style="color: #666666">.</span>lookupByName(vmname)
xml <span style="color: #666666">=</span> vm<span style="color: #666666">.</span>XMLDesc(<span style="color: #666666">0</span>)
xml_desc <span style="color: #666666">=</span> fromstring(xml)
<span style="color: #008000; font-weight: bold">for</span> xml_iface <span style="color: #AA22FF; font-weight: bold">in</span> xml_desc<span style="color: #666666">.</span>findall(<span style="color: #BA2121">"devices/interface"</span>):
netname <span style="color: #666666">=</span> xml_iface<span style="color: #666666">.</span>find(<span style="color: #BA2121">'source'</span>)<span style="color: #666666">.</span>attrib[<span style="color: #BA2121">'network'</span>]
lookup_hwaddr <span style="color: #666666">=</span> xml_iface<span style="color: #666666">.</span>find(<span style="color: #BA2121">'mac'</span>)<span style="color: #666666">.</span>attrib[<span style="color: #BA2121">'address'</span>]
br_name , addr, netmask <span style="color: #666666">=</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>get_network_data(netname)
<span style="color: #008000; font-weight: bold">for</span> hwaddr, ipaddr <span style="color: #AA22FF; font-weight: bold">in</span> arp_scan(addr, netmask, br_name):
<span style="color: #008000; font-weight: bold">if</span> hwaddr <span style="color: #666666">==</span> lookup_hwaddr:
<span style="color: #008000; font-weight: bold">yield</span> ipaddr
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">login_to_vm</span>(<span style="color: #008000">self</span>, name, user<span style="color: #666666">=</span><span style="color: #BA2121">'root'</span>, passwd<span style="color: #666666">=</span><span style="color: #BA2121">'root'</span>):
find <span style="color: #666666">=</span> <span style="color: #008000">False</span>
<span style="color: #008000; font-weight: bold">for</span> ipaddr <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>get_all_ips(name):
s <span style="color: #666666">=</span> socket<span style="color: #666666">.</span>socket()
s<span style="color: #666666">.</span>settimeout(<span style="color: #666666">1</span>)
<span style="color: #008000; font-weight: bold">try</span>:
s<span style="color: #666666">.</span>connect((ipaddr, <span style="color: #666666">22</span>))
<span style="color: #008000; font-weight: bold">except</span> socket<span style="color: #666666">.</span>error:
<span style="color: #008000; font-weight: bold">raise</span>
os<span style="color: #666666">.</span>system(expect_login<span style="color: #666666">.</span>format(user, ipaddr, passwd))
find <span style="color: #666666">=</span> <span style="color: #008000">True</span>
<span style="color: #008000; font-weight: bold">break</span>
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #AA22FF; font-weight: bold">not</span> find:
<span style="color: #008000; font-weight: bold">raise</span> <span style="color: #D2413A; font-weight: bold">RuntimeError</span>(<span style="color: #BA2121">"No one interface of {0} accepts ssh connection"</span><span style="color: #666666">.</span>format(name))
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">main</span>(argv<span style="color: #666666">=</span><span style="color: #008000">None</span>):
argv <span style="color: #666666">=</span> argv <span style="color: #008000; font-weight: bold">if</span> argv <span style="color: #AA22FF; font-weight: bold">is</span> <span style="color: #AA22FF; font-weight: bold">not</span> <span style="color: #008000">None</span> <span style="color: #008000; font-weight: bold">else</span> sys<span style="color: #666666">.</span>argv
parser <span style="color: #666666">=</span> argparse<span style="color: #666666">.</span>ArgumentParser()
parser<span style="color: #666666">.</span>add_argument(<span style="color: #BA2121">'cmd'</span>, choices<span style="color: #666666">=</span>(<span style="color: #BA2121">'start'</span>, <span style="color: #BA2121">'stop'</span>, <span style="color: #BA2121">'list'</span>, <span style="color: #BA2121">'login'</span>))
parser<span style="color: #666666">.</span>add_argument(<span style="color: #BA2121">'--name'</span>, choices<span style="color: #666666">=</span>vm_sets<span style="color: #666666">.</span>keys())
parser<span style="color: #666666">.</span>add_argument(<span style="color: #BA2121">'--uri'</span>, default<span style="color: #666666">=</span><span style="color: #BA2121">"qemu:///system"</span>)
parser<span style="color: #666666">.</span>add_argument(<span style="color: #BA2121">'--template'</span>, default<span style="color: #666666">=</span><span style="color: #BA2121">"vm_templ.xml"</span>)
opts <span style="color: #666666">=</span> parser<span style="color: #666666">.</span>parse_args(argv[<span style="color: #666666">1</span>:])
cloud <span style="color: #666666">=</span> TinyCloud(libvirt<span style="color: #666666">.</span>open(opts<span style="color: #666666">.</span>uri))
<span style="color: #008000; font-weight: bold">if</span> opts<span style="color: #666666">.</span>cmd <span style="color: #666666">==</span> <span style="color: #BA2121">'start'</span>:
cloud<span style="color: #666666">.</span>start_vm(opts<span style="color: #666666">.</span>template, opts<span style="color: #666666">.</span>name)
<span style="color: #008000; font-weight: bold">elif</span> opts<span style="color: #666666">.</span>cmd <span style="color: #666666">==</span> <span style="color: #BA2121">'stop'</span>:
cloud<span style="color: #666666">.</span>stop_vm(opts<span style="color: #666666">.</span>name)
<span style="color: #008000; font-weight: bold">elif</span> opts<span style="color: #666666">.</span>cmd <span style="color: #666666">==</span> <span style="color: #BA2121">'login'</span>:
cloud<span style="color: #666666">.</span>login_to_vm(opts<span style="color: #666666">.</span>name)
<span style="color: #008000; font-weight: bold">elif</span> opts<span style="color: #666666">.</span>cmd <span style="color: #666666">==</span> <span style="color: #BA2121">'list'</span>:
<span style="color: #008000; font-weight: bold">for</span> domain <span style="color: #AA22FF; font-weight: bold">in</span> cloud<span style="color: #666666">.</span>list_vms():
all_ips <span style="color: #666666">=</span> <span style="color: #BA2121">","</span><span style="color: #666666">.</span>join(cloud<span style="color: #666666">.</span>get_all_ips(domain<span style="color: #666666">.</span>name()))
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"{0:>5} {1} {2}"</span><span style="color: #666666">.</span>format(domain<span style="color: #666666">.</span>ID(), domain<span style="color: #666666">.</span>name(), all_ips)
<span style="color: #008000; font-weight: bold">else</span>:
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #666666">>></span>sys<span style="color: #666666">.</span>stderr, <span style="color: #BA2121">"Unknown cmd {0}"</span><span style="color: #666666">.</span>format(opts<span style="color: #666666">.</span>cmd)
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #666666">0</span>
<span style="color: #008000; font-weight: bold">if</span> __name__ <span style="color: #666666">==</span> <span style="color: #BA2121">"__main__"</span>:
sys<span style="color: #666666">.</span>exit(main(sys<span style="color: #666666">.</span>argv))
</pre></div></span><span style="line-height:100%;display:none" id="3e6179282f2011e1ad4914feb5b819a0"><pre><font face="courier">import os
import sys
import socket
import argparse
from xml.etree.ElementTree import fromstring
import libvirt
import ipaddr
from scapy.all import srp, Ether, ARP, conf
conf.verb = 0
vm_sets = \
{
'ubuntu' :
{
'vcpu' : 1,
'mem' : 1024 * 1024, # 1Gb RAM
'mac' : "00:44:01:61:78:01",
'image_file' : '/home/koder/vm_images/ubuntu-server-nova-1.qcow2'
},
'debian' :
{
'vcpu' : 1,
'mem' : 1024 * 1024, # 1Gb RAM
'mac' : "00:44:01:61:78:01",
'image_file' : '/home/koder/vm_images/debian_squeeze_amd64_standard.qcow2'
},
'deb1' :
{
'vcpu' : 1,
'mem' : 256 * 1024, # 256M RAM
'mac1' : "00:44:01:61:78:02",
'mac2' : "00:44:01:61:78:05",
'image_file' : '/home/koder/vm_images/deb1.qcow2',
},
'deb2' :
{
'vcpu' : 1,
'mem' : 256 * 1024, # 256M RAM
'mac' : "00:44:01:61:78:03",
'image_file' : '/home/koder/vm_images/deb2.qcow2',
},
'deb3' :
{
'vcpu' : 1,
'mem' : 256 * 1024, # 256M RAM
'mac' : "00:44:01:61:78:04",
'image_file' : '/home/koder/vm_images/deb3.qcow2',
}
}
def arp_scan(ip_addr, netmask, iface):
netsize = ipaddr.IPNetwork("{0}/{1}".format(ip_addr, netmask)).prefixlen
ans, unans = srp(
Ether(dst="ff:ff:ff:ff:ff:ff") / \
ARP(pdst="{0}/{1}".format(ip_addr, netsize)),
timeout=0.1, iface=iface)
for request, responce in ans:
yield responce.payload.fields['hwsrc'], responce.payload.fields['psrc']
expect_login = """expect -c'
spawn ssh {0}@{1};
while {{1}} {{
expect {{
eof {{ break }};
"The authenticity of host" {{ send "yes\\n" }};
"password:" {{ send "{2}\\n"; interact; break;}};
}};
}};
wait'
"""
class TinyCloud(object):
def __init__(self, conn):
self.conn = conn
def start_vm(self, template, vmname):
vm_xml_templ = open(template).read()
vm_xml = vm_xml_templ.format(vmname=vmname, **vm_sets[vmname])
self.conn.createXML(vm_xml, 0)
def stop_vm(self, vmname):
vm = self.conn.lookupByName(vmname)
vm.shutdown()
def list_vms(self):
for domain_id in self.conn.listDomainsID():
yield self.conn.lookupByID(domain_id)
def get_network_data(self, name):
net = self.conn.networkLookupByName(name)
xml = fromstring(net.XMLDesc(0))
attrs = xml.find('ip').attrib
return xml.find('bridge').attrib['name'], attrs['address'], attrs['netmask']
def get_all_ips(self, vmname):
vm = self.conn.lookupByName(vmname)
xml = vm.XMLDesc(0)
xml_desc = fromstring(xml)
for xml_iface in xml_desc.findall("devices/interface"):
netname = xml_iface.find('source').attrib['network']
lookup_hwaddr = xml_iface.find('mac').attrib['address']
br_name , addr, netmask = self.get_network_data(netname)
for hwaddr, ipaddr in arp_scan(addr, netmask, br_name):
if hwaddr == lookup_hwaddr:
yield ipaddr
def login_to_vm(self, name, user='root', passwd='root'):
find = False
for ipaddr in self.get_all_ips(name):
s = socket.socket()
s.settimeout(1)
try:
s.connect((ipaddr, 22))
except socket.error:
raise
os.system(expect_login.format(user, ipaddr, passwd))
find = True
break
if not find:
raise RuntimeError("No one interface of {0} accepts ssh connection".format(name))
def main(argv=None):
argv = argv if argv is not None else sys.argv
parser = argparse.ArgumentParser()
parser.add_argument('cmd', choices=('start', 'stop', 'list', 'login'))
parser.add_argument('--name', choices=vm_sets.keys())
parser.add_argument('--uri', default="qemu:///system")
parser.add_argument('--template', default="vm_templ.xml")
opts = parser.parse_args(argv[1:])
cloud = TinyCloud(libvirt.open(opts.uri))
if opts.cmd == 'start':
cloud.start_vm(opts.template, opts.name)
elif opts.cmd == 'stop':
cloud.stop_vm(opts.name)
elif opts.cmd == 'login':
cloud.login_to_vm(opts.name)
elif opts.cmd == 'list':
for domain in cloud.list_vms():
all_ips = ",".join(cloud.get_all_ips(domain.name()))
print "{0:>5} {1} {2}".format(domain.ID(), domain.name(), all_ips)
else:
print >>sys.stderr, "Unknown cmd {0}".format(opts.cmd)
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv))</font></pre></span></span><p style="text-indent:20px"> Итого текущая версия кода позволяет поднимать виртуальные сети разнообразных конфигураций. Пока это включает избыточное количество ручной работы - в следующий раз расмотрим работу с дисковыми образами и автоматизацию их модификации, что уменьшит количество нажатий кнопок для поднятия сетей и отдельных vm, а также позволит начать использовать уникальные для виртуализации возможности, недоступные для реальных серверов (или доступные с большим трудом).</p>Ссылки:<br> <a name="bridges"><a href="http://www.linuxfoundation.org/collaborate/workgroups/networking/bridge">www.linuxfoundation.org/collaborate/workgroups/networking/bridge</a></a><br> <a name="tun"><a href="http://backreference.org/2010/03/26/tuntap-interface-tutorial">backreference.org/2010/03/26/tuntap-interface-tutorial</a></a><br> <a name="tap"><a href="http://en.wikipedia.org/wiki/TUN/TAP">en.wikipedia.org/wiki/TUN/TAP</a></a><br> <a name="kharkovpromenade"><a href="http://kharkovpromenade.com.ua/?id=9">kharkovpromenade.com.ua/?id=9</a></a><br> <a name="результат"><a href="http://wiki.libvirt.org/page/Networking">wiki.libvirt.org/page/Networking</a></a><br> <a name="xml_формате_для_сети"><a href="http://libvirt.org/formatnetwork.html">libvirt.org/formatnetwork.html</a></a><br> <a name="expect"><a href="http://en.wikipedia.org/wiki/Expect">en.wikipedia.org/wiki/Expect</a></a><br> <a name="iptables"><a href="http://en.wikipedia.org/wiki/Iptables">en.wikipedia.org/wiki/Iptables</a></a><br> <a name="scapy"><a href="http://www.secdev.org/projects/scapy/doc/">www.secdev.org/projects/scapy/doc</a></a><br> <a name="scapy-traceroute"><a href="http://www.secdev.org/projects/scapy/doc/usage.html#tcp-traceroute-2">www.secdev.org/projects/scapy/doc/usage.html#tcp-traceroute-2</a></a><br> <a name="NAT"><a href="http://en.wikipedia.org/wiki/Network_address_translation">en.wikipedia.org/wiki/Network_address_translation</a></a><br><p style="text-indent:20px">Исходники этого и других постов со скриптами лежат тут - <a href="https://github.com/koder-ua/python-lectures">github.com/koder-ua</a>. При использовании их, пожалуйста, ссылайтесь на <a href="http://koder-ua.blogspot.com/">koder-ua.blogspot.com</a>.</p>
<script type="text/javascript">
function on_hidabble_click()
{
var me = $(this);
var hide_id = me.attr("objtohide");
var controlled_object = $('#' + hide_id);
controlled_object.toggle();
if ( controlled_object.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".hidder").click(on_hidabble_click);
function on_double_hidabble_click()
{
var me = $(this);
var hide_id1 = me.attr("objtohide1");
var hide_id2 = me.attr("objtohide2");
var controlled_object1 = $('#' + hide_id1);
var controlled_object2 = $('#' + hide_id2);
controlled_object1.toggle();
controlled_object2.toggle();
if ( controlled_object1.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".dhidder").click(on_double_hidabble_click);
</script>Anonymoushttp://www.blogger.com/profile/06645078574760734586noreply@blogger.com0tag:blogger.com,1999:blog-1174489715777430743.post-61933954154971323102011-12-22T03:12:00.001+02:002011-12-23T13:07:17.139+02:00libvirt & Co. Облако "на коленке". Часть 1<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<br><h2>Buzzword</h2><p style="text-indent:20px"> <a href="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">Облако(cloud)</a> это инфраструктура для управления виртуальными машинами. Агенты облака устанавливаются на железных серверах, превращая их единый мегасервер, которые используется для виртуализации. Облако должно уметь:</p><ul><li>запускать группы виртуальных машин на базе загруженных в него образов<li>изменять образы виртуальных машин<li>управлять сетевой инфраструктурой - объединять виртуальные машины в ( возможно виртуальные ) локальные сети, настраивать правила доступа к этим сетям извне и доступ наружу из сетей<li>поддерживать остановку, приостановку и миграцию виртуалок<li>балансировать нагрузку на железные сервера<li>управлять местом на дисках<li>..............</ul><br><h2>Предисловие</h2><p style="text-indent:20px"> На сегодняшний день есть четыре основных облачных системы - перспективный и активно развиваемый <a href="http://openstack.org/">openstack</a>, рабочий но мало интересный из-за лицензии <a href="http://www.eucalyptus.com/">eucalyptus</a>, совсем-совсем проприетарный <a href="http://www.vmware.com/solutions/cloud-computing/index.html">VMware vCloud</a> и очень-очень microsoft <a href="http://en.wikipedia.org/wiki/Azure_Services_Platform">azure</a>. Но это все "серьезные" облака, а как это часто бывает большие системы не удобно использовать на малых задачах. Я расскажу как управлять небольшими группами виртуальных машин "малой кровью". Впрочем openstack использует эти же утилиты, а все остальные узнают на чем основываются linux клауды.</p><a name='more'></a><p style="text-indent:20px"> Для описанных методик вам необходим Linux 2.6.26+ и процессор с поддержкой виртуализации. Проверить это можно следующими командами:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="09a7ffb02d0711e1b09414feb5b819a0" objtohide2="09a82bb62d0711e1b09414feb5b819a0" >Hightlited/Raw</a><br><span id="09a7ffb02d0711e1b09414feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">$</span> cat /proc/cpuinfo | egrep <span style="color: #BA2121">'vmx|svm'</span>
<span style="color: #000080; font-weight: bold">$</span> cat /proc/cpuinfo | egrep <span style="color: #BA2121">'rvi|ept'</span>
</pre></div></span><span style="font-size:110%;display:none" id="09a82bb62d0711e1b09414feb5b819a0"><pre><font face="courier">$ cat /proc/cpuinfo | egrep 'vmx|svm'
$ cat /proc/cpuinfo | egrep 'rvi|ept'</font></pre></span><p style="text-indent:20px"> Если первая команда ничего не вывела - вам не повезло, аппаратной поддержки виртуализации у вас нет. Если обе команды выдали не пустой ответ - вам повезло вдвойне - в вашем процессоре есть поддержка виртуализации таблицы страниц - это значительно ускоряет работу с памятью, фактически выводя ее на уровень сырого железа.</p><p style="text-indent:20px"> Вложенная аппаратная виртуализация не поддерживается, т.е. если linux установлен в виртуальной машине, то описанные примеры работать не будут. Впрочем и те, кто запускает линукс в виртуалке и те, у кого нет поддержки виртуализации могут адаптировать эти примеры для использования <a href="http://xen.org/">xen</a> c паравиртуализацией или <a href="http://lxc.sourceforge.net/">lxc</a> - эти техники не требуют аппаратной поддержки. В принципе ипользуемая <a href="http://libvirt.org/">libvirt</a> имеет зачаточную поддержку windows, желающие могут попробовать и так.</p><p style="text-indent:20px"> Из других аппаратных требований желательно по-больше оперативной памяти (3Gb+) и быстрый диск (SSD). На магнитном жестком диске все будет работать, но некоторые наиболее интересные варианты организации виртульных образов заметно тормозят на дисковых операциях из-за большого количества разрозненных обращений.</p><p style="text-indent:20px"> Все примеры для Ubuntu 11.10, для других дистрибутивов нужно подправить обращения к пакетному менеджеру и пути к конфигам.</p><br><h2>libvirt</h2><p style="text-indent:20px"> Хотя формально <a href="http://libvirt.org/">libvirt</a> называется библиотекой, но это целая инфраструктура для управления виртуальными машинами. Она включает:</p><ul><li>libvirt-bin демон с внешним API, управляющий виртуальными машинами<li>libvirt - библиотека для доступа к демону<li>masqdns - dns/dhcp сервер, используемый совместно с iptables, vlan и бриджами для управлением виртуальными сетями<li>virsh - клиент командной строки</ul><p style="text-indent:20px"> libvirt предоставляет почти унифицированный интерфейс для работы с различными гипервизорами - поддерживаются <a href="http://en.wikipedia.org/wiki/Kernel-based_Virtual_Machine">kvm</a>, <a href="http://lxc.sourceforge.net/">lxc</a>, <a href="http://xen.org/">xen</a>, vmware, hyper-v, <a href="http://wiki.openvz.org/Main_Page">openvz</a>, и другие - в общем почти все, что еще шевелится. При этом libvirt не пытается подобрать общий знаменатель ко всем системам виртуализации, а предоставляет полный набор возможностей каждого гипервизора - просто не все конфигурации будут работать на всех системах виртуализаций.</p><p style="text-indent:20px"> Для описания виртуальных машин, сетей и хранилищ libvirt использует xml, строки с которым выступают параметрами во всех основных функциях. Модель кажется сначала странной, но после ближайшего рассмотрения понимаешь, что это очень яркий пример удачного использования <a href="http://en.wikipedia.org/wiki/Dependency_injection">dependency injection</a>. В 99% случаев программам которые используют libvirt все равно какая структура каждой конкретной виртуальной машины. А при использовании внешних xml файлов их можно править не трогая код, при этом исходная программа будет(почти) однообразно работать на всех поддерживаемых гипервизорах и с самыми разными виртуальными машинами.</p><p style="text-indent:20px"> Итак начнем с самого простого - с запуска vm. Примеры кода будут на python, но все эти действия можно выполнить из командной строки с помощью <b>virsh</b>. В качестве гипервизора будем использовать kvm. Он по умолчанию доступен в современных Linux системах.</p><p style="text-indent:20px"> Ставим необходимые пакеты:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="09a8feba2d0711e1b09414feb5b819a0" objtohide2="09a92a702d0711e1b09414feb5b819a0" >Hightlited/Raw</a><br><span id="09a8feba2d0711e1b09414feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">#</span> apt-get install kvm qemu qemu-kvm qemu-common libvirt-bin libvirt0 python-libvirt
<span style="color: #000080; font-weight: bold">#</span> modprobe kvm
<span style="color: #000080; font-weight: bold">#</span> mkdprobe kvm-intel <span style="color: #408080; font-style: italic"># kvm-amd</span>
</pre></div></span><span style="font-size:110%;display:none" id="09a92a702d0711e1b09414feb5b819a0"><pre><font face="courier"># apt-get install kvm qemu qemu-kvm qemu-common libvirt-bin libvirt0 python-libvirt
# modprobe kvm
# mkdprobe kvm-intel # kvm-amd</font></pre></span><p style="text-indent:20px"> Выключаем apparmor/selinux. Желающие могут его настроить, но по умолчанию они настроены криво:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="09aa97982d0711e1b09414feb5b819a0" objtohide2="09aa9df62d0711e1b09414feb5b819a0" >Hightlited/Raw</a><br><span id="09aa97982d0711e1b09414feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">#</span> service apparmor teardown
<span style="color: #000080; font-weight: bold">#</span> service libvirt-bin stop
<span style="color: #000080; font-weight: bold">#</span> service libvirt-bin start
</pre></div></span><span style="font-size:110%;display:none" id="09aa9df62d0711e1b09414feb5b819a0"><pre><font face="courier"># service apparmor teardown
# service libvirt-bin stop
# service libvirt-bin start</font></pre></span><p style="text-indent:20px">Выкачиваем образ <a href="http://people.debian.org/~aurel32/qemu/amd64/debian_lenny_amd64_standard.qcow2">debian_vm</a> и делаем конфигурационный файл для нее:</p><a hided_text="Show" visible_text="Hide" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 80%" class="hidder" objtohide="09aab5b62d0711e1b09414feb5b819a0">Show</a><br><span style="display:none" id="09aab5b62d0711e1b09414feb5b819a0"><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="09ab53402d0711e1b09414feb5b819a0" objtohide2="09ab54bc2d0711e1b09414feb5b819a0" >Hightlited/Raw</a><br><span id="09ab53402d0711e1b09414feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #BC7A00"><?xml version="1.0" encoding="utf-8" ?></span>
<span style="color: #408080; font-style: italic"><!-- vm_templ.xml --></span>
<span style="color: #008000; font-weight: bold"><domain</span> <span style="color: #7D9029">type=</span><span style="color: #BA2121">"kvm"</span><span style="color: #008000; font-weight: bold">></span>
<span style="color: #008000; font-weight: bold"><name></span>{vm_name}<span style="color: #008000; font-weight: bold"></name></span>
<span style="color: #008000; font-weight: bold"><memory></span>{mem}<span style="color: #008000; font-weight: bold"></memory></span>
<span style="color: #008000; font-weight: bold"><uuid</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><vcpu></span>{vcpu}<span style="color: #008000; font-weight: bold"></vcpu></span>
<span style="color: #008000; font-weight: bold"><os></span>
<span style="color: #008000; font-weight: bold"><type></span>hvm<span style="color: #008000; font-weight: bold"></type></span>
<span style="color: #008000; font-weight: bold"><loader></span>/usr/lib/xen-default/boot/hvmloader<span style="color: #008000; font-weight: bold"></loader></span>
<span style="color: #008000; font-weight: bold"><boot</span> <span style="color: #7D9029">dev=</span><span style="color: #BA2121">"hd"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><boot</span> <span style="color: #7D9029">dev=</span><span style="color: #BA2121">"cdrom"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><bootmenu</span> <span style="color: #7D9029">enable=</span><span style="color: #BA2121">"yes"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><bios</span> <span style="color: #7D9029">useserial=</span><span style="color: #BA2121">"yes"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"></os></span>
<span style="color: #008000; font-weight: bold"><clock</span> <span style="color: #7D9029">sync=</span><span style="color: #BA2121">"localtime"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><on_poweroff></span>destroy<span style="color: #008000; font-weight: bold"></on_poweroff></span>
<span style="color: #008000; font-weight: bold"><on_reboot></span>restart<span style="color: #008000; font-weight: bold"></on_reboot></span>
<span style="color: #008000; font-weight: bold"><on_crash></span>destroy<span style="color: #008000; font-weight: bold"></on_crash></span>
<span style="color: #008000; font-weight: bold"><features></span>
<span style="color: #008000; font-weight: bold"><acpi</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><hap</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><apic</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"></features></span>
<span style="color: #008000; font-weight: bold"><devices></span>
<span style="color: #008000; font-weight: bold"><emulator></span>/usr/bin/kvm<span style="color: #008000; font-weight: bold"></emulator></span>
<span style="color: #008000; font-weight: bold"><disk</span> <span style="color: #7D9029">device=</span><span style="color: #BA2121">"disk"</span> <span style="color: #7D9029">type=</span><span style="color: #BA2121">"file"</span><span style="color: #008000; font-weight: bold">></span>
<span style="color: #008000; font-weight: bold"><driver</span> <span style="color: #7D9029">name=</span><span style="color: #BA2121">"qemu"</span> <span style="color: #7D9029">type=</span><span style="color: #BA2121">"qcow2"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #408080; font-style: italic"><!-- подправить на нужный формат файла --></span>
<span style="color: #008000; font-weight: bold"><source</span> <span style="color: #7D9029">file=</span><span style="color: #BA2121">"{image_file}"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><target</span> <span style="color: #7D9029">bus=</span><span style="color: #BA2121">"ide"</span> <span style="color: #7D9029">dev=</span><span style="color: #BA2121">"hda"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"></disk></span>
<span style="color: #008000; font-weight: bold"><interface</span> <span style="color: #7D9029">type=</span><span style="color: #BA2121">"network"</span><span style="color: #008000; font-weight: bold">></span>
<span style="color: #008000; font-weight: bold"><source</span> <span style="color: #7D9029">network=</span><span style="color: #BA2121">"default"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><forward</span> <span style="color: #7D9029">mode=</span><span style="color: #BA2121">"nat"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><target</span> <span style="color: #7D9029">dev=</span><span style="color: #BA2121">"vnet7"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><mac</span> <span style="color: #7D9029">address=</span><span style="color: #BA2121">"{mac}"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"></interface></span>
<span style="color: #008000; font-weight: bold"><serial</span> <span style="color: #7D9029">type=</span><span style="color: #BA2121">"pty"</span><span style="color: #008000; font-weight: bold">></span>
<span style="color: #008000; font-weight: bold"><target</span> <span style="color: #7D9029">port=</span><span style="color: #BA2121">"0"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"></serial></span>
<span style="color: #008000; font-weight: bold"><console</span> <span style="color: #7D9029">type=</span><span style="color: #BA2121">"pty"</span><span style="color: #008000; font-weight: bold">></span>
<span style="color: #008000; font-weight: bold"><target</span> <span style="color: #7D9029">port=</span><span style="color: #BA2121">"0"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"></console></span>
<span style="color: #008000; font-weight: bold"><input</span> <span style="color: #7D9029">bus=</span><span style="color: #BA2121">"ps2"</span> <span style="color: #7D9029">type=</span><span style="color: #BA2121">"mouse"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><graphics</span> <span style="color: #7D9029">autoport=</span><span style="color: #BA2121">"yes"</span> <span style="color: #7D9029">keymap=</span><span style="color: #BA2121">"en-us"</span> <span style="color: #7D9029">port=</span><span style="color: #BA2121">"-1"</span> <span style="color: #7D9029">type=</span><span style="color: #BA2121">"vnc"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"></devices></span>
<span style="color: #008000; font-weight: bold"></domain></span>
</pre></div></span><span style="font-size:110%;display:none" id="09ab54bc2d0711e1b09414feb5b819a0"><pre><font face="courier"><?xml version="1.0" encoding="utf-8" ?>
<!-- vm_templ.xml -->
<domain type="kvm">
<name>{vm_name}</name>
<memory>{mem}</memory>
<uuid />
<vcpu>{vcpu}</vcpu>
<os>
<type>hvm</type>
<loader>/usr/lib/xen-default/boot/hvmloader</loader>
<boot dev="hd" />
<boot dev="cdrom" />
<bootmenu enable="yes" />
<bios useserial="yes" />
</os>
<clock sync="localtime" />
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>destroy</on_crash>
<features>
<acpi />
<hap />
<apic />
</features>
<devices>
<emulator>/usr/bin/kvm</emulator>
<disk device="disk" type="file">
<driver name="qemu" type="qcow2" />
<!-- подправить на нужный формат файла -->
<source file="{image_file}" />
<target bus="ide" dev="hda" />
</disk>
<interface type="network">
<source network="default" />
<forward mode="nat" />
<target dev="vnet7" />
<mac address="{mac}" />
</interface>
<serial type="pty">
<target port="0" />
</serial>
<console type="pty">
<target port="0" />
</console>
<input bus="ps2" type="mouse" />
<graphics autoport="yes" keymap="en-us" port="-1" type="vnc" />
</devices>
</domain></font></pre></span></span><p style="text-indent:20px">Файл для запуска vm:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="09e27b542d0711e1b09414feb5b819a0" objtohide2="09e27d2a2d0711e1b09414feb5b819a0" >Hightlited/Raw</a><br><span id="09e27b542d0711e1b09414feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #408080; font-style: italic"># tiny_cloud.py</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">sys</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">libvirt</span>
<span style="color: #408080; font-style: italic"># соединяемся с libvirtd</span>
uri <span style="color: #666666">=</span> <span style="color: #BA2121">'qemu://system'</span>
conn <span style="color: #666666">=</span> libvirt<span style="color: #666666">.</span>open(uri)
vm_xml_templ <span style="color: #666666">=</span> <span style="color: #008000">open</span>(sys<span style="color: #666666">.</span>argv[<span style="color: #666666">1</span>])<span style="color: #666666">.</span>read()
vm_xml <span style="color: #666666">=</span> vm_xml_templ<span style="color: #666666">.</span>format(vcpu<span style="color: #666666">=1</span>,
mem<span style="color: #666666">=1024</span> <span style="color: #666666">*</span> <span style="color: #666666">1024</span>, <span style="color: #408080; font-style: italic"># 1Gb</span>
name<span style="color: #666666">=</span>sys<span style="color: #666666">.</span>argv[<span style="color: #666666">2</span>],
mac<span style="color: #666666">=</span><span style="color: #BA2121">"00:44:01:61:78:01"</span>,
image_file<span style="color: #666666">=</span>sys<span style="color: #666666">.</span>argv[<span style="color: #666666">3</span>]
)
<span style="color: #408080; font-style: italic"># запускаем vm</span>
conn<span style="color: #666666">.</span>createXML(vm_xml, <span style="color: #666666">0</span>)
</pre></div></span><span style="font-size:110%;display:none" id="09e27d2a2d0711e1b09414feb5b819a0"><pre><font face="courier"># tiny_cloud.py
import sys
import libvirt
# соединяемся с libvirtd
uri = 'qemu://system'
conn = libvirt.open(uri)
vm_xml_templ = open(sys.argv[1]).read()
vm_xml = vm_xml_templ.format(vcpu=1,
mem=1024 * 1024, # 1Gb
name=sys.argv[2],
mac="00:44:01:61:78:01",
image_file=sys.argv[3]
)
# запускаем vm
conn.createXML(vm_xml, 0)</font></pre></span><p style="text-indent:20px">Запускаем:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="09e29ec22d0711e1b09414feb5b819a0" objtohide2="09e2a0342d0711e1b09414feb5b819a0" >Hightlited/Raw</a><br><span id="09e29ec22d0711e1b09414feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">#</span> python tiny_cloud.py vm_templ.xml debian path_to_image
</pre></div></span><span style="font-size:110%;display:none" id="09e2a0342d0711e1b09414feb5b819a0"><pre><font face="courier"># python tiny_cloud.py vm_templ.xml debian path_to_image</font></pre></span><p style="text-indent:20px">Проверяем, что виртуалка запущенна:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="09e2bccc2d0711e1b09414feb5b819a0" objtohide2="09e2be342d0711e1b09414feb5b819a0" >Hightlited/Raw</a><br><span id="09e2bccc2d0711e1b09414feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #000080; font-weight: bold">#</span> virsh list
<span style="color: #808080"> Id Name State</span>
<span style="color: #808080">----------------------------------</span>
<span style="color: #808080"> 1 debian running</span>
</pre></div></span><span style="font-size:110%;display:none" id="09e2be342d0711e1b09414feb5b819a0"><pre><font face="courier"># virsh list
Id Name State
----------------------------------
1 debian running</font></pre></span><p style="text-indent:20px"> В принципе libvirt с помощью <b>virsh</b> позволяет регистрировать/запускать/останавливать виртуальные машины и без использования python, но для более сложных задач bash не самый лучший язык программирования.</p><p style="text-indent:20px"> Виртуальные машины запущенные под управлением kvm являются обычными linux процессами, так что ими можно управлять в том числе с помощью стандартных средств - ps, kill, nice, ionice, etc. Это так-же означает что работают все стандартные системы мониторинга (htop/atop/iotop/sar) и другое, а</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="09e2f0f22d0711e1b09414feb5b819a0" objtohide2="09e2f2782d0711e1b09414feb5b819a0" >Hightlited/Raw</a><br><span id="09e2f0f22d0711e1b09414feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #808080"> # ps aux | grep kvm</span>
<span style="color: #808080"> покажет командную строку, с помощью которой можно запустить vm не используя</span>
<span style="color: #808080">libvirt.</span>
</pre></div></span><span style="font-size:110%;display:none" id="09e2f2782d0711e1b09414feb5b819a0"><pre><font face="courier"> # ps aux | grep kvm
покажет командную строку, с помощью которой можно запустить vm не используя
libvirt.</font></pre></span><p style="text-indent:20px"> Продолжим с tiny_cloud.py - добавим останов виртуалки, разбор командной строки, etc.</p><a hided_text="Show" visible_text="Hide" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 80%" class="hidder" objtohide="09e304342d0711e1b09414feb5b819a0">Show</a><br><span style="display:none" id="09e304342d0711e1b09414feb5b819a0"><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="0a34006e2d0711e1b09414feb5b819a0" objtohide2="0a3402442d0711e1b09414feb5b819a0" >Hightlited/Raw</a><br><span id="0a34006e2d0711e1b09414feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #408080; font-style: italic"># tiny_cloud.py</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">sys</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">argparse</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">libvirt</span>
vm_sets <span style="color: #666666">=</span> \
{
<span style="color: #BA2121">'ubuntu'</span> :
{
<span style="color: #BA2121">'vcpu'</span> : <span style="color: #666666">1</span>,
<span style="color: #BA2121">'mem'</span> : <span style="color: #666666">1024</span> <span style="color: #666666">*</span> <span style="color: #666666">1024</span>, <span style="color: #408080; font-style: italic"># 1Gb RAM</span>
<span style="color: #BA2121">'mac'</span> : <span style="color: #BA2121">"00:44:01:61:78:01"</span>,
<span style="color: #BA2121">'image_file'</span> : <span style="color: #BA2121">'/home/koder/vm_images/ubuntu-server-nova-1.qcow2'</span>
},
<span style="color: #BA2121">'debian'</span> :
{
<span style="color: #BA2121">'vcpu'</span> : <span style="color: #666666">1</span>,
<span style="color: #BA2121">'mem'</span> : <span style="color: #666666">1024</span> <span style="color: #666666">*</span> <span style="color: #666666">1024</span>, <span style="color: #408080; font-style: italic"># 1Gb RAM</span>
<span style="color: #BA2121">'mac'</span> : <span style="color: #BA2121">"00:44:01:61:78:01"</span>,
<span style="color: #BA2121">'image_file'</span> :
<span style="color: #BA2121">'/home/koder/vm_images/debian_squeeze_amd64_standard.qcow2'</span>
}
}
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">TinyCloud</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>, conn):
<span style="color: #008000">self</span><span style="color: #666666">.</span>conn <span style="color: #666666">=</span> conn
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">start_vm</span>(<span style="color: #008000">self</span>, template, vmname):
vm_xml_templ <span style="color: #666666">=</span> <span style="color: #008000">open</span>(template)<span style="color: #666666">.</span>read()
vm_xml <span style="color: #666666">=</span> vm_xml_templ<span style="color: #666666">.</span>format(vmname<span style="color: #666666">=</span>vmname, <span style="color: #666666">**</span>vm_sets[vmname])
<span style="color: #008000">self</span><span style="color: #666666">.</span>conn<span style="color: #666666">.</span>createXML(vm_xml, <span style="color: #666666">0</span>)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">stop_vm</span>(<span style="color: #008000">self</span>, vmname):
vm <span style="color: #666666">=</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>conn<span style="color: #666666">.</span>lookupByName(vmname)
vm<span style="color: #666666">.</span>destroy()
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">list_vms</span>(<span style="color: #008000">self</span>):
<span style="color: #008000; font-weight: bold">for</span> domain_id <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>conn<span style="color: #666666">.</span>listDomainsID():
<span style="color: #008000; font-weight: bold">yield</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>conn<span style="color: #666666">.</span>lookupByID(domain_id)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">main</span>(argv<span style="color: #666666">=</span><span style="color: #008000">None</span>):
argv <span style="color: #666666">=</span> argv <span style="color: #008000; font-weight: bold">if</span> argv <span style="color: #AA22FF; font-weight: bold">is</span> <span style="color: #AA22FF; font-weight: bold">not</span> <span style="color: #008000">None</span> <span style="color: #008000; font-weight: bold">else</span> sys<span style="color: #666666">.</span>argv
parser <span style="color: #666666">=</span> argparse<span style="color: #666666">.</span>ArgumentParser()
parser<span style="color: #666666">.</span>add_argument(<span style="color: #BA2121">'cmd'</span>, choices<span style="color: #666666">=</span>(<span style="color: #BA2121">'start'</span>, <span style="color: #BA2121">'stop'</span>, <span style="color: #BA2121">'list'</span>))
parser<span style="color: #666666">.</span>add_argument(<span style="color: #BA2121">'--name'</span>, choices<span style="color: #666666">=</span>vm_sets<span style="color: #666666">.</span>keys())
parser<span style="color: #666666">.</span>add_argument(<span style="color: #BA2121">'--uri'</span>, default<span style="color: #666666">=</span><span style="color: #BA2121">"qemu:///system"</span>)
parser<span style="color: #666666">.</span>add_argument(<span style="color: #BA2121">'--template'</span>, default<span style="color: #666666">=</span><span style="color: #BA2121">"vm_templ.xml"</span>)
opts <span style="color: #666666">=</span> parser<span style="color: #666666">.</span>parse_args(argv[<span style="color: #666666">1</span>:])
cloud <span style="color: #666666">=</span> TinyCloud(libvirt<span style="color: #666666">.</span>open(opts<span style="color: #666666">.</span>uri))
<span style="color: #008000; font-weight: bold">if</span> opts<span style="color: #666666">.</span>cmd <span style="color: #666666">==</span> <span style="color: #BA2121">'start'</span>:
cloud<span style="color: #666666">.</span>start_vm(opts<span style="color: #666666">.</span>template, opts<span style="color: #666666">.</span>name)
<span style="color: #008000; font-weight: bold">elif</span> opts<span style="color: #666666">.</span>cmd <span style="color: #666666">==</span> <span style="color: #BA2121">'stop'</span>:
cloud<span style="color: #666666">.</span>stop_vm(opts<span style="color: #666666">.</span>name)
<span style="color: #008000; font-weight: bold">elif</span> opts<span style="color: #666666">.</span>cmd <span style="color: #666666">==</span> <span style="color: #BA2121">'list'</span>:
<span style="color: #008000; font-weight: bold">for</span> domain <span style="color: #AA22FF; font-weight: bold">in</span> cloud<span style="color: #666666">.</span>list_vms():
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"{0:>5} {1}"</span><span style="color: #666666">.</span>format(domain<span style="color: #666666">.</span>ID(), domain<span style="color: #666666">.</span>name())
<span style="color: #008000; font-weight: bold">else</span>:
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #666666">>></span> sys<span style="color: #666666">.</span>stderr, <span style="color: #BA2121">"Unknown cmd {0}"</span><span style="color: #666666">.</span>format(opts<span style="color: #666666">.</span>cmd)
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #666666">0</span>
<span style="color: #008000; font-weight: bold">if</span> __name__ <span style="color: #666666">==</span> <span style="color: #BA2121">"__main__"</span>:
sys<span style="color: #666666">.</span>exit(main(sys<span style="color: #666666">.</span>argv))
</pre></div></span><span style="font-size:110%;display:none" id="0a3402442d0711e1b09414feb5b819a0"><pre><font face="courier"># tiny_cloud.py
import sys
import argparse
import libvirt
vm_sets = \
{
'ubuntu' :
{
'vcpu' : 1,
'mem' : 1024 * 1024, # 1Gb RAM
'mac' : "00:44:01:61:78:01",
'image_file' : '/home/koder/vm_images/ubuntu-server-nova-1.qcow2'
},
'debian' :
{
'vcpu' : 1,
'mem' : 1024 * 1024, # 1Gb RAM
'mac' : "00:44:01:61:78:01",
'image_file' :
'/home/koder/vm_images/debian_squeeze_amd64_standard.qcow2'
}
}
class TinyCloud(object):
def __init__(self, conn):
self.conn = conn
def start_vm(self, template, vmname):
vm_xml_templ = open(template).read()
vm_xml = vm_xml_templ.format(vmname=vmname, **vm_sets[vmname])
self.conn.createXML(vm_xml, 0)
def stop_vm(self, vmname):
vm = self.conn.lookupByName(vmname)
vm.destroy()
def list_vms(self):
for domain_id in self.conn.listDomainsID():
yield self.conn.lookupByID(domain_id)
def main(argv=None):
argv = argv if argv is not None else sys.argv
parser = argparse.ArgumentParser()
parser.add_argument('cmd', choices=('start', 'stop', 'list'))
parser.add_argument('--name', choices=vm_sets.keys())
parser.add_argument('--uri', default="qemu:///system")
parser.add_argument('--template', default="vm_templ.xml")
opts = parser.parse_args(argv[1:])
cloud = TinyCloud(libvirt.open(opts.uri))
if opts.cmd == 'start':
cloud.start_vm(opts.template, opts.name)
elif opts.cmd == 'stop':
cloud.stop_vm(opts.name)
elif opts.cmd == 'list':
for domain in cloud.list_vms():
print "{0:>5} {1}".format(domain.ID(), domain.name())
else:
print >> sys.stderr, "Unknown cmd {0}".format(opts.cmd)
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv))</font></pre></span></span><p style="text-indent:20px"> Для подключения к полученным виртуалкам можно использовать ssh или vnc viewer. Для виртуалок, поднятых с помощью libvirt есть удобный <a href="http://virt-manager.org/">virt-manager</a>, который показывает все запущенные домены и позволяет подключится по vnc, что необходимо если сеть не загрузилась или на образе не было ssh сервера.</p><center><br><img src="http://3.bp.blogspot.com/-9ORCk64v3Cc/TvPIAnLPeaI/AAAAAAAAAs8/eTEyeK5JGJE/s1600/debian_vm.png" width="400" /><br></center><center><br><img src="http://4.bp.blogspot.com/-hK7mqAUBaHA/TvPIA4_XV-I/AAAAAAAAAtI/8RjPCk4jztc/s1600/debian_vm_vnc.png" width="700" /><br></center><p style="text-indent:20px"> Первая проблема после запуска виртуалки - програмно определять ip, который она получила. Для этого желательно разобраться с сетевой моделью libvirt и вообще с основными сетевыми средствами linux, чему и будет посвящена следующая статья.</p>Ссылки:<br> <a name="debian-vm"><a href="http://people.debian.org/~aurel32/qemu/amd64/debian_squeeze_amd64_standard.qcow2">people.debian.org/~aurel32/qemu/amd64/debian_squeeze_amd64_standard.qcow2</a></a><br> <a name="cloud"><a href="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">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</a></a><br> <a name="openstack"><a href="http://openstack.org/">openstack.org</a></a><br> <a name="eucalyptus"><a href="http://www.eucalyptus.com/">www.eucalyptus.com</a></a><br> <a name="VMware_vCloud"><a href="http://www.vmware.com/solutions/cloud-computing/index.html">www.vmware.com/solutions/cloud-computing/index.html</a></a><br> <a name="azure"><a href="http://en.wikipedia.org/wiki/Azure_Services_Platform">en.wikipedia.org/wiki/Azure_Services_Platform</a></a><br> <a name="kvm"><a href="http://en.wikipedia.org/wiki/Kernel-based_Virtual_Machine">en.wikipedia.org/wiki/Kernel-based_Virtual_Machine</a></a><br> <a name="xen"><a href="http://xen.org/">xen.org</a></a><br> <a name="lxc"><a href="http://lxc.sourceforge.net/">lxc.sourceforge.net</a></a><br> <a name="openvz"><a href="http://wiki.openvz.org/Main_Page">wiki.openvz.org/Main_Page</a></a><br> <a name="libvirt"><a href="http://libvirt.org/">libvirt.org</a></a><br> <a name="dependency_injection"><a href="http://en.wikipedia.org/wiki/Dependency_injection">en.wikipedia.org/wiki/Dependency_injection</a></a><br> <a name="virt-manager"><a href="http://virt-manager.org/">virt-manager.org</a></a><br><p style="text-indent:20px">Исходники этого и других постов со скриптами лежат тут - <a href="https://github.com/koder-ua/python-lectures">github.com/koder-ua</a>. При использовании их, пожалуйста, ссылайтесь на <a href="http://koder-ua.blogspot.com/">koder-ua.blogspot.com</a>.</p>
<script type="text/javascript">
function on_hidabble_click()
{
var me = $(this);
var hide_id = me.attr("objtohide");
var controlled_object = $('#' + hide_id);
controlled_object.toggle();
if ( controlled_object.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".hidder").click(on_hidabble_click);
function on_double_hidabble_click()
{
var me = $(this);
var hide_id1 = me.attr("objtohide1");
var hide_id2 = me.attr("objtohide2");
var controlled_object1 = $('#' + hide_id1);
var controlled_object2 = $('#' + hide_id2);
controlled_object1.toggle();
controlled_object2.toggle();
if ( controlled_object1.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".dhidder").click(on_double_hidabble_click);
</script>Anonymoushttp://www.blogger.com/profile/06645078574760734586noreply@blogger.com11tag:blogger.com,1999:blog-1174489715777430743.post-37663176613436730162011-12-22T00:01:00.000+02:002011-12-22T12:55:55.239+02:00Решение предыдущего поста<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<p style="text-indent:20px"> Если вы не читали предыдущий пост - начните с него.</p><a name='more'></a><p style="text-indent:20px"> <b>overloadable</b> включает трассировку и следит за исполнением тела класса. Если обнаруживает, что значение исполняемой переменной было изменено - подменяет ее на объект, управляющий вызовом соответствующей функции в зависимости от параметров.</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="5d6efa7e2c2911e188e214feb5b819a0" objtohide2="5d6f465a2c2911e188e214feb5b819a0" >Hightlited/Raw</a><br><span id="5d6efa7e2c2911e188e214feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #408080; font-style: italic"># -*- coding:utf8 -*-</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">sys</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">overloadable</span>():
sys<span style="color: #666666">.</span>settrace(OverloadTracer()<span style="color: #666666">.</span>on_event)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">closure</span>(cls):
<span style="color: #408080; font-style: italic"># удаляем трассировку по выходу из класса</span>
<span style="color: #408080; font-style: italic"># вообще говоря это можно сделать по 'return'</span>
<span style="color: #408080; font-style: italic"># но семантика декоратора заставит использовать</span>
<span style="color: #408080; font-style: italic"># код по назначению</span>
sys<span style="color: #666666">.</span>settrace(<span style="color: #008000">None</span>)
<span style="color: #008000; font-weight: bold">return</span> cls
<span style="color: #008000; font-weight: bold">return</span> closure
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">OverloadTracer</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>):
<span style="color: #008000">self</span><span style="color: #666666">.</span>in_class <span style="color: #666666">=</span> <span style="color: #008000">False</span>
<span style="color: #008000">self</span><span style="color: #666666">.</span>prev_locals <span style="color: #666666">=</span> {}
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">on_event</span>(<span style="color: #008000">self</span>, frame, event, _):
<span style="color: #408080; font-style: italic">#вызывается при каждом событии трассировки</span>
<span style="color: #008000; font-weight: bold">if</span> event <span style="color: #666666">==</span> <span style="color: #BA2121">'return'</span>:
<span style="color: #408080; font-style: italic"># возврат из блока</span>
<span style="color: #008000">self</span><span style="color: #666666">.</span>update_locals(frame<span style="color: #666666">.</span>f_locals)
<span style="color: #008000; font-weight: bold">elif</span> event <span style="color: #666666">==</span> <span style="color: #BA2121">'call'</span>:
<span style="color: #408080; font-style: italic"># вызов - открытие блока</span>
<span style="color: #408080; font-style: italic"># не заходим во вложенные вызовы</span>
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>in_class:
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">None</span>
<span style="color: #008000">self</span><span style="color: #666666">.</span>in_class <span style="color: #666666">=</span> <span style="color: #008000">True</span>
<span style="color: #008000; font-weight: bold">else</span>:
<span style="color: #008000">self</span><span style="color: #666666">.</span>update_locals(frame<span style="color: #666666">.</span>f_locals)
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>on_event
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">update_locals</span>(<span style="color: #008000">self</span>, loc):
<span style="color: #408080; font-style: italic"># сравниваем loc с self.prev_locals</span>
<span style="color: #008000; font-weight: bold">for</span> name, prev_val <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>prev_locals<span style="color: #666666">.</span>items():
<span style="color: #008000; font-weight: bold">if</span> name <span style="color: #AA22FF; font-weight: bold">in</span> loc:
<span style="color: #408080; font-style: italic"># если находим перекрытие функции - подменяем ее на объект,</span>
<span style="color: #408080; font-style: italic"># управляющий перегрузкой</span>
<span style="color: #008000; font-weight: bold">if</span> loc[name] <span style="color: #AA22FF; font-weight: bold">is</span> <span style="color: #AA22FF; font-weight: bold">not</span> prev_val <span style="color: #AA22FF; font-weight: bold">and</span> \
( <span style="color: #008000">callable</span>(prev_val) <span style="color: #AA22FF; font-weight: bold">or</span> \
<span style="color: #008000">isinstance</span>(prev_val, OverloadableFunc) )<span style="color: #AA22FF; font-weight: bold">and</span> \
<span style="color: #008000">callable</span>(loc[name]):
loc[name] <span style="color: #666666">=</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>overload(prev_val, loc[name])
<span style="color: #408080; font-style: italic"># делаем копию текущего состояния тела класса</span>
<span style="color: #008000">self</span><span style="color: #666666">.</span>prev_locals <span style="color: #666666">=</span> loc<span style="color: #666666">.</span>copy()
<span style="color: #AA22FF">@staticmethod</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">overload</span>(prev_val, curr_val):
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #AA22FF; font-weight: bold">not</span> <span style="color: #008000">isinstance</span>(prev_val, OverloadableFunc):
overld <span style="color: #666666">=</span> OverloadableFunc()
overld<span style="color: #666666">.</span>add(prev_val)
prev_val <span style="color: #666666">=</span> overld
prev_val<span style="color: #666666">.</span>add(curr_val)
<span style="color: #008000; font-weight: bold">return</span> prev_val
<span style="color: #408080; font-style: italic"># класс имитирует функцию с одним аргументом</span>
<span style="color: #408080; font-style: italic"># и по его типу выбирает соответствующий зарегистрированный обработчик</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">OverloadableFunc</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>):
<span style="color: #008000">self</span><span style="color: #666666">.</span>funcs <span style="color: #666666">=</span> {}
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__get__</span>(<span style="color: #008000">self</span>, obj, cls):
<span style="color: #408080; font-style: italic"># для имитации метода объект этого типа </span>
<span style="color: #408080; font-style: italic"># должен быть свойством</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">closure</span>(val):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>funcs[<span style="color: #008000">type</span>(val)<span style="color: #666666">.</span>__name__](obj, val)
<span style="color: #008000; font-weight: bold">return</span> closure
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">add</span>(<span style="color: #008000">self</span>, func):
<span style="color: #408080; font-style: italic"># добавляем новую функцию-обработчик</span>
tp_name <span style="color: #666666">=</span> func<span style="color: #666666">.</span>__doc__<span style="color: #666666">.</span>strip()<span style="color: #666666">.</span>split(<span style="color: #BA2121">'</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">'</span>)[<span style="color: #666666">0</span>]
<span style="color: #008000">self</span><span style="color: #666666">.</span>funcs[tp_name] <span style="color: #666666">=</span> func
<span style="color: #408080; font-style: italic"># использование в предыдущем посте</span>
</pre></div></span><span style="font-size:110%;display:none" id="5d6f465a2c2911e188e214feb5b819a0"><pre><font face="courier"># -*- coding:utf8 -*-
import sys
def overloadable():
sys.settrace(OverloadTracer().on_event)
def closure(cls):
# удаляем трассировку по выходу из класса
# вообще говоря это можно сделать по 'return'
# но семантика декоратора заставит использовать
# код по назначению
sys.settrace(None)
return cls
return closure
class OverloadTracer(object):
def __init__(self):
self.in_class = False
self.prev_locals = {}
def on_event(self, frame, event, _):
#вызывается при каждом событии трассировки
if event == 'return':
# возврат из блока
self.update_locals(frame.f_locals)
elif event == 'call':
# вызов - открытие блока
# не заходим во вложенные вызовы
if self.in_class:
return None
self.in_class = True
else:
self.update_locals(frame.f_locals)
return self.on_event
def update_locals(self, loc):
# сравниваем loc с self.prev_locals
for name, prev_val in self.prev_locals.items():
if name in loc:
# если находим перекрытие функции - подменяем ее на объект,
# управляющий перегрузкой
if loc[name] is not prev_val and \
( callable(prev_val) or \
isinstance(prev_val, OverloadableFunc) )and \
callable(loc[name]):
loc[name] = self.overload(prev_val, loc[name])
# делаем копию текущего состояния тела класса
self.prev_locals = loc.copy()
@staticmethod
def overload(prev_val, curr_val):
if not isinstance(prev_val, OverloadableFunc):
overld = OverloadableFunc()
overld.add(prev_val)
prev_val = overld
prev_val.add(curr_val)
return prev_val
# класс имитирует функцию с одним аргументом
# и по его типу выбирает соответствующий зарегистрированный обработчик
class OverloadableFunc(object):
def __init__(self):
self.funcs = {}
def __get__(self, obj, cls):
# для имитации метода объект этого типа
# должен быть свойством
def closure(val):
return self.funcs[type(val).__name__](obj, val)
return closure
def add(self, func):
# добавляем новую функцию-обработчик
tp_name = func.__doc__.strip().split('\n')[0]
self.funcs[tp_name] = func
# использование в предыдущем посте</font></pre></span><p style="text-indent:20px"> Поскольку тело класса исполняется то трассировщик может "видеть" как в него добавляются новые методы или подменяются старые и использовать эту информацию что-бы произвести перегрузку функций. На всякий случай - код класса исполняется только один раз - при импорте текущего модуля. По выходу из создания класса трассировка выключается и не оказывает никакого влияния на его инстанцирование или вызов методов.</p><p style="text-indent:20px"> Этот код можно расширить до перегрузки функций (не методов) и добавить поддержку нескольких параметров.</p><p style="text-indent:20px"> Идея с использованием трассировки для расширения синтаксиса была реализована как минимум в одной широко известной библиотеке - <a href="http://pypi.python.org/pypi/DecoratorTools">PEAK.util.decorator</a>. Она позволяет использовать декораторы в python до 2.4.</p>Ссылки:<br> <a name="PEAK.util.decorator"><a href="http://pypi.python.org/pypi/DecoratorTools">pypi.python.org/pypi/DecoratorTools</a></a><br><p style="text-indent:20px">Исходники этого и других постов со скриптами лежат тут - <a href="https://github.com/koder-ua/python-lectures">github.com/koder-ua</a>. При использовании их, пожалуйста, ссылайтесь на <a href="http://koder-ua.blogspot.com/">koder-ua.blogspot.com</a>.</p>
<script type="text/javascript">
function on_hidabble_click()
{
var me = $(this);
var hide_id = me.attr("objtohide");
var controlled_object = $('#' + hide_id);
controlled_object.toggle();
if ( controlled_object.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".hidder").click(on_hidabble_click);
function on_double_hidabble_click()
{
var me = $(this);
var hide_id1 = me.attr("objtohide1");
var hide_id2 = me.attr("objtohide2");
var controlled_object1 = $('#' + hide_id1);
var controlled_object2 = $('#' + hide_id2);
controlled_object1.toggle();
controlled_object2.toggle();
if ( controlled_object1.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".dhidder").click(on_double_hidabble_click);
</script>Anonymoushttp://www.blogger.com/profile/06645078574760734586noreply@blogger.com0tag:blogger.com,1999:blog-1174489715777430743.post-12148975190353747902011-12-20T23:48:00.000+02:002011-12-24T18:40:41.372+02:00Как это сделано?<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<p style="text-indent:20px"> Отсутствие перегрузки функций - это то что мне всегда не нравилось в python. Не то что бы без них невозможно было жить, да и виртуальные методы типа <b>__len__</b> сглаживают проблему, но все-таки. И пусть есть <a href="http://pypi.python.org/pypi/PEAK-Rules">PEAK.rules</a>, но его синтаксис всегда раздражал. Ну вот как можно без боли смотреть на это:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="f4e55eb82e4d11e18f2614feb5b819a0" objtohide2="f4e5a5a82e4d11e18f2614feb5b819a0" >Hightlited/Raw</a><br><span id="f4e55eb82e4d11e18f2614feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">from</span> <span style="color: #0000FF; font-weight: bold">peak.rules</span> <span style="color: #008000; font-weight: bold">import</span> abstract, when
<span style="color: #AA22FF">@abstract</span>()
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">pprint</span>(ob):
<span style="color: #BA2121; font-style: italic">"""A pretty-printing generic function"""</span>
<span style="color: #AA22FF">@when</span>(pprint, (<span style="color: #008000">list</span>,))
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">pprint_list</span>(ob):
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"pretty-printing a list"</span>
<span style="color: #AA22FF">@when</span>(pprint, (<span style="color: #008000">int</span>,))
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">pprint_int</span>(ob):
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"pretty-printing an integer"</span>
<span style="color: #408080; font-style: italic">#......</span>
</pre></div></span><span style="font-size:110%;display:none" id="f4e5a5a82e4d11e18f2614feb5b819a0"><pre><font face="courier">from peak.rules import abstract, when
@abstract()
def pprint(ob):
"""A pretty-printing generic function"""
@when(pprint, (list,))
def pprint_list(ob):
print "pretty-printing a list"
@when(pprint, (int,))
def pprint_int(ob):
print "pretty-printing an integer"
#......</font></pre></span><p style="text-indent:20px"> Во-первых опять нужно придумывать для каждого типа свои имена функций, во-вторых не по-питоновски много лишних нажатий клавиш, даже в С++ это - лишнее: <b>@when(pprint, (</b> :).</p><a name='more'></a><p style="text-indent:20px"> Но как-то ничего принципиально лучше придумать не удавалось. В python 3+ можно будет в конце концов сделать отличную перегрузку методов через метаклассы, но до его массового использования в продакшене пока далековато. И вот недавно, при написании статьи про метаклассы в python 3 и находясь под влияние пересмотра одного видео с последнего <a href="http://blip.tv/pycon-us-videos-2009-2010-2011">pycon-videos</a>, пришла в голову мысль которая оказалась рабочей ( впрочем я бы три раза подумал перед тем как положить такой код в файл, который будет использовать кто-то другой).</p><p style="text-indent:20px"> Ну собственно угадайте как работает написанное ниже (какая магия зашита в <b>method_overloader.overloadable</b>):</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="f4e7135c2e4d11e18f2614feb5b819a0" objtohide2="f4e75af62e4d11e18f2614feb5b819a0" >Hightlited/Raw</a><br><span id="f4e7135c2e4d11e18f2614feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">from</span> <span style="color: #0000FF; font-weight: bold">method_overloader</span> <span style="color: #008000; font-weight: bold">import</span> overloadable
<span style="color: #AA22FF">@overloadable</span>()
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">A</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">overloaded_func</span>(<span style="color: #008000">self</span>, x):
<span style="color: #BA2121">"int"</span>
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #BA2121">"Integer func called {0}"</span><span style="color: #666666">.</span>format(x)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">overloaded_func</span>(<span style="color: #008000">self</span>, x):
<span style="color: #BA2121">"str"</span>
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #BA2121">"String func called {0!r}"</span><span style="color: #666666">.</span>format(x)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">overloaded_func</span>(<span style="color: #008000">self</span>, x):
<span style="color: #BA2121">"float"</span>
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #BA2121">"Float func called {0!r}"</span><span style="color: #666666">.</span>format(x)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">overloaded_func</span>(<span style="color: #008000">self</span>, x):
<span style="color: #BA2121">"list"</span>
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #BA2121">"List func called {0!r}"</span><span style="color: #666666">.</span>format(x)
t <span style="color: #666666">=</span> A()
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"t.overloaded_func(1) ="</span>, t<span style="color: #666666">.</span>overloaded_func(<span style="color: #666666">1</span>)
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"t.overloaded_func('asas') ="</span>, t<span style="color: #666666">.</span>overloaded_func(<span style="color: #BA2121">"asas"</span>)
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"t.overloaded_func(1.1) ="</span>, t<span style="color: #666666">.</span>overloaded_func(<span style="color: #666666">1.1</span>)
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"t.overloaded_func([1, 2, 3]]) ="</span>, t<span style="color: #666666">.</span>overloaded_func([<span style="color: #666666">1</span>, <span style="color: #666666">2</span>, <span style="color: #666666">3</span>])
</pre></div></span><span style="font-size:110%;display:none" id="f4e75af62e4d11e18f2614feb5b819a0"><pre><font face="courier">from method_overloader import overloadable
@overloadable()
class A(object):
def overloaded_func(self, x):
"int"
return "Integer func called {0}".format(x)
def overloaded_func(self, x):
"str"
return "String func called {0!r}".format(x)
def overloaded_func(self, x):
"float"
return "Float func called {0!r}".format(x)
def overloaded_func(self, x):
"list"
return "List func called {0!r}".format(x)
t = A()
print "t.overloaded_func(1) =", t.overloaded_func(1)
print "t.overloaded_func('asas') =", t.overloaded_func("asas")
print "t.overloaded_func(1.1) =", t.overloaded_func(1.1)
print "t.overloaded_func([1, 2, 3]]) =", t.overloaded_func([1, 2, 3])</font></pre></span><p style="text-indent:20px">Запускаем -</p><pre><font face="courier"> .........$ python tracer.py
t.overloaded_func(1) = Integer func called 1
t.overloaded_func('asas') = String func called 'asas'
t.overloaded_func(1.1) = Float func called 1.1
t.overloaded_func([1, 2, 3]]) = List func called [1, 2, 3]</font></pre><p style="text-indent:20px"> Все это на обычном python без подмены механизма импорта, без ковыряния в <a href="http://docs.python.org/library/ast.html">ast</a> и т.п. Ответы можно на koder.mail@gmail.com.</p><p style="text-indent:20px"> P.S. Если что - в python2.X метаклассе невозможно узнать что происходило в теле класса, можно только узнать что вышло в итоге, т.е.:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="f4e7e52a2e4d11e18f2614feb5b819a0" objtohide2="f4e7eafc2e4d11e18f2614feb5b819a0" >Hightlited/Raw</a><br><span id="f4e7e52a2e4d11e18f2614feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">M</span>(<span style="color: #008000">object</span>):
s <span style="color: #666666">=</span> <span style="color: #666666">1</span>
s <span style="color: #666666">=</span> <span style="color: #666666">2</span>
</pre></div></span><span style="font-size:110%;display:none" id="f4e7eafc2e4d11e18f2614feb5b819a0"><pre><font face="courier">class M(object):
s = 1
s = 2</font></pre></span><p style="text-indent:20px"> в метакласс класса прийдет в качестве словаря класса {<b>s</b> : 2} и узнать что еще было <b>s = 1</b> в метаклассе нельзя.</p>Ссылки:<br> <a name="PEAK.rules"><a href="http://pypi.python.org/pypi/PEAK-Rules">pypi.python.org/pypi/PEAK-Rules</a></a><br> <a name="pycon-videos"><a href="http://blip.tv/pycon-us-videos-2009-2010-2011">blip.tv/pycon-us-videos-2009-2010-2011</a></a><br> <a name="ast"><a href="http://docs.python.org/library/ast.html">docs.python.org/library/ast.html</a></a><br><p style="text-indent:20px">Исходники этого и других постов со скриптами лежат тут - <a href="https://github.com/koder-ua/python-lectures">github.com/koder-ua</a>. При использовании их, пожалуйста, ссылайтесь на <a href="http://koder-ua.blogspot.com/">koder-ua.blogspot.com</a>.</p>
<script type="text/javascript">
function on_hidabble_click()
{
var me = $(this);
var hide_id = me.attr("objtohide");
var controlled_object = $('#' + hide_id);
controlled_object.toggle();
if ( controlled_object.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".hidder").click(on_hidabble_click);
function on_double_hidabble_click()
{
var me = $(this);
var hide_id1 = me.attr("objtohide1");
var hide_id2 = me.attr("objtohide2");
var controlled_object1 = $('#' + hide_id1);
var controlled_object2 = $('#' + hide_id2);
controlled_object1.toggle();
controlled_object2.toggle();
if ( controlled_object1.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".dhidder").click(on_double_hidabble_click);
</script>Anonymoushttp://www.blogger.com/profile/06645078574760734586noreply@blogger.com6tag:blogger.com,1999:blog-1174489715777430743.post-47436182817534679022011-12-12T04:15:00.000+02:002011-12-24T18:38:11.476+02:00Интерфейсы в python или "Предъявите документы!"<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<p style="text-indent:20px"> При написании не тривиальных приложений возникает вопрос: над какими библиотеками делать еще один абстрактный слой, а над какими - нет? Какие абстракции делать?</p><p style="text-indent:20px"> Стоит ли делать прослойку над, например, <a href="http://www.sqlalchemy.org/">SQLAlchemy</a>? Это же и так прослойка над SQL и <a href="http://www.python.org/dev/peps/pep-0249/">DBAPI</a>. Имеет ли смысл делать уровни абстракций над такими достаточно хорошими и отточенными в смысле интерфейсов библиотеками?</p><p style="text-indent:20px"> Ответ очень простой - библиотеки представляют API который должен быть применим для широкого спектра приложений. Они отображают низкоуровневые (с точки зрения их API ) вызовы на более высокоуровневый, но абстрактный интерфейс. Характерный пример - библиотеки передачи сообщений. Они позволяют не думать о сокетах, упаковке/распаковке <b>float</b>/<b>int</b> и т.п., а просто передавать структуры данных.</p><a name='more'></a><p style="text-indent:20px"> Типичный API системы пересылки сообщений выглядит как:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="8cadae0e2e4d11e1b6dc14feb5b819a0" objtohide2="8cadee3c2e4d11e1b6dc14feb5b819a0" >Hightlited/Raw</a><br><span id="8cadae0e2e4d11e1b6dc14feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Messaging</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">send_message_async</span>(<span style="color: #008000">self</span>, dest, message_tp, message_data):
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">send_message_sync</span>(<span style="color: #008000">self</span>, dest, message_tp, message_data):
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">get_message</span>(<span style="color: #008000">self</span>):
<span style="color: #008000; font-weight: bold">pass</span>
</pre></div></span><span style="font-size:110%;display:none" id="8cadee3c2e4d11e1b6dc14feb5b819a0"><pre><font face="courier">class Messaging(object):
def send_message_async(self, dest, message_tp, message_data):
pass
def send_message_sync(self, dest, message_tp, message_data):
pass
def get_message(self):
pass</font></pre></span><p style="text-indent:20px"> Но программе не нужно посылать никакие сообщения! Ей нужно выполнить действия - показать пользователю GUI, узнать завершился ли удаленный процесс, etc. API сообщений, которое было-бы идеально для некоторой программы выглядит примерно так:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="8caebd442e4d11e1b6dc14feb5b819a0" objtohide2="8caefbe22e4d11e1b6dc14feb5b819a0" >Hightlited/Raw</a><br><span id="8caebd442e4d11e1b6dc14feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">MyAPI</span>(<span style="color: #008000">object</span>):
<span style="color: #AA22FF">@exception_on_false</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">show_ui_message</span>(<span style="color: #008000">self</span>, level, text):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>messanger<span style="color: #666666">.</span>send_message_async(<span style="color: #008000">self</span><span style="color: #666666">.</span>UI_PROC_ID,
SHOW_DIALOG,
<span style="color: #008000">dict</span>(level<span style="color: #666666">=</span>level, text<span style="color: #666666">=</span>text))
<span style="color: #AA22FF">@exception_on_false</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">reboot_vm</span>(<span style="color: #008000">self</span>, ip):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>messanger<span style="color: #666666">.</span>send_message_async(
<span style="color: #008000">self</span><span style="color: #666666">.</span>get_remote_agent_id(ip),
REBOOT_VM,
<span style="color: #008000">None</span>)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">ls_remote</span>(<span style="color: #008000">self</span>, ip, remote_path):
tp, res <span style="color: #666666">=</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>messanger<span style="color: #666666">.</span>send_message_sync(
<span style="color: #008000">self</span><span style="color: #666666">.</span>get_remote_agent_id(ip),
EXEC_CMD,
<span style="color: #BA2121">'ls -l {0}'</span><span style="color: #666666">.</span>format(remote_path))
<span style="color: #008000; font-weight: bold">if</span> tp <span style="color: #666666">==</span> EXECUTION_FINISHED_OK:
<span style="color: #008000; font-weight: bold">return</span> res
<span style="color: #008000; font-weight: bold">else</span>:
<span style="color: #008000; font-weight: bold">raise</span> <span style="color: #D2413A; font-weight: bold">RuntimeError</span>(<span style="color: #BA2121">"Cmd ... finished with error code {0}"</span><span style="color: #666666">.</span>\
format(res))
</pre></div></span><span style="font-size:110%;display:none" id="8caefbe22e4d11e1b6dc14feb5b819a0"><pre><font face="courier">class MyAPI(object):
@exception_on_false
def show_ui_message(self, level, text):
return self.messanger.send_message_async(self.UI_PROC_ID,
SHOW_DIALOG,
dict(level=level, text=text))
@exception_on_false
def reboot_vm(self, ip):
return self.messanger.send_message_async(
self.get_remote_agent_id(ip),
REBOOT_VM,
None)
def ls_remote(self, ip, remote_path):
tp, res = self.messanger.send_message_sync(
self.get_remote_agent_id(ip),
EXEC_CMD,
'ls -l {0}'.format(remote_path))
if tp == EXECUTION_FINISHED_OK:
return res
else:
raise RuntimeError("Cmd ... finished with error code {0}".\
format(res))</font></pre></span><p style="text-indent:20px"> Очень принципиальный момент - конечный API должен отражать задачи, стоящие перед программой. Четкое отделение основной логики программы от деталей реализации имеет минимум два очень важных плюса - позволяет сделать главный код легче для чтения (убирает лишние абстракции) и максимально отвязать программу от API библиотек (локализовать привязку).</p><p style="text-indent:20px"> Это особенная прослойка, это "последняя линия". Если остальные API предоставляют нам абстракции, то эта прослойка не должна добавлять ничего лишнего, она избавляет нас от более не нужных абстракций и говорит языком предметной области программы.</p><p style="text-indent:20px"> Вам нужно хранить в базе список фруктов? Сделайте функцию <b>store_fruits</b>. Такая функция позволить вам перейти от PostgreSQL к Cassandra, а потом к текстовым файлам (маловероятная ситуация, но не суть) без влияния на остальную программу. Потому что программе все равно где лежат данные. Программу интересует только что они сохраняются и восстанавливаются.</p><p style="text-indent:20px"> Мы никак не может защититься от изменения требований к программе и вместе с изменениями требований будет меняться и API, который предоставляет наш слой абстракции. Но вот изменения в типе базы/структуре базы/ORM не будет приводить к изменению кода. Если смена БД или ORM - маловероятная ситуация, то вот добавление нового поля вида <b>deleted</b>, означающего, что запись вроде как удалена и почти нигде не должна использоваться - весьма частый случай.</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="8cb041dc2e4d11e1b6dc14feb5b819a0" objtohide2="8cb0490c2e4d11e1b6dc14feb5b819a0" >Hightlited/Raw</a><br><span id="8cb041dc2e4d11e1b6dc14feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #408080; font-style: italic"># почти реальный запрос прямо из функции, отвечающей за логики программы </span>
services <span style="color: #666666">=</span> session<span style="color: #666666">.</span>query(Service)<span style="color: #666666">.</span>\
<span style="color: #008000">filter</span>(Service<span style="color: #666666">.</span>zone_id <span style="color: #666666">==</span> zone_id)<span style="color: #666666">.</span>\
<span style="color: #008000">filter</span>(Service<span style="color: #666666">.</span>service_id <span style="color: #666666">==</span> service_id)<span style="color: #666666">.</span>\
with_lockmode(<span style="color: #BA2121">'update'</span>)<span style="color: #666666">.</span>\
limit(<span style="color: #666666">10</span>)<span style="color: #666666">.</span>all()
<span style="color: #408080; font-style: italic"># Чего-чего????? </span>
<span style="color: #408080; font-style: italic"># комментарий к запросу немного бы спас ситуацию</span>
<span style="color: #408080; font-style: italic"># но вместо решения проблем с помощью комментирования их лучше не создавать </span>
<span style="color: #408080; font-style: italic"># Этот код не требует комментариев</span>
<span style="color: #008000; font-weight: bold">for</span> service <span style="color: #AA22FF; font-weight: bold">in</span> db<span style="color: #666666">.</span>get_10_services(zone_id, service_id):
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #408080; font-style: italic"># в файле db.py</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">get_10_services</span>(<span style="color: #008000">self</span>, zone_id, service_id):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>session<span style="color: #666666">.</span>query(Service)<span style="color: #666666">.</span>\
<span style="color: #008000">filter</span>(Service<span style="color: #666666">.</span>zone_id <span style="color: #666666">==</span> zone_id)<span style="color: #666666">.</span>\
<span style="color: #008000">filter</span>(Service<span style="color: #666666">.</span>service_id <span style="color: #666666">==</span> service_id)<span style="color: #666666">.</span>\
with_lockmode(<span style="color: #BA2121">'update'</span>)<span style="color: #666666">.</span>\
limit(<span style="color: #666666">10</span>)<span style="color: #666666">.</span>all()
</pre></div></span><span style="font-size:110%;display:none" id="8cb0490c2e4d11e1b6dc14feb5b819a0"><pre><font face="courier"># почти реальный запрос прямо из функции, отвечающей за логики программы
services = session.query(Service).\
filter(Service.zone_id == zone_id).\
filter(Service.service_id == service_id).\
with_lockmode('update').\
limit(10).all()
# Чего-чего?????
# комментарий к запросу немного бы спас ситуацию
# но вместо решения проблем с помощью комментирования их лучше не создавать
# Этот код не требует комментариев
for service in db.get_10_services(zone_id, service_id):
pass
# в файле db.py
def get_10_services(self, zone_id, service_id):
return self.session.query(Service).\
filter(Service.zone_id == zone_id).\
filter(Service.service_id == service_id).\
with_lockmode('update').\
limit(10).all()</font></pre></span><p style="text-indent:20px">Еще одна ошибка - попытка сэкономить на таком API и сделать в этом духе:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="8cb0d5ac2e4d11e1b6dc14feb5b819a0" objtohide2="8cb0d8042e4d11e1b6dc14feb5b819a0" >Hightlited/Raw</a><br><span id="8cb0d5ac2e4d11e1b6dc14feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">sqlalchemy</span> <span style="color: #008000; font-weight: bold">as</span> <span style="color: #0000FF; font-weight: bold">sa</span>
<span style="color: #408080; font-style: italic"># one Funсtion to rule them all!!</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">get_user</span>(session, <span style="color: #666666">*</span>opts):
<span style="color: #008000; font-weight: bold">return</span> session<span style="color: #666666">.</span>query(User)<span style="color: #666666">.</span>filter(sa<span style="color: #666666">.</span>and_(<span style="color: #666666">*</span>opts))<span style="color: #666666">.</span>all()
<span style="color: #408080; font-style: italic"># этот код уже требует знания что там у нас за sqlalchemy такая</span>
<span style="color: #408080; font-style: italic"># и на mongo его уже не переписать так-же легко</span>
<span style="color: #408080; font-style: italic"># тут "торчат уши" sqlalchemy. Да, мы съекономили 10-20 нажатий клавишь</span>
<span style="color: #408080; font-style: italic"># на каждый вызов, но это не "the very last API"</span>
<span style="color: #008000; font-weight: bold">for</span> user <span style="color: #AA22FF; font-weight: bold">in</span> get_user(User<span style="color: #666666">.</span>name <span style="color: #666666">==</span> <span style="color: #BA2121">'vasya'</span>, User<span style="color: #666666">.</span>age <span style="color: #666666">></span> datetime<span style="color: #666666">.</span>now()):
<span style="color: #008000; font-weight: bold">pass</span>
</pre></div></span><span style="font-size:110%;display:none" id="8cb0d8042e4d11e1b6dc14feb5b819a0"><pre><font face="courier">import sqlalchemy as sa
# one Funсtion to rule them all!!
def get_user(session, *opts):
return session.query(User).filter(sa.and_(*opts)).all()
# этот код уже требует знания что там у нас за sqlalchemy такая
# и на mongo его уже не переписать так-же легко
# тут "торчат уши" sqlalchemy. Да, мы съекономили 10-20 нажатий клавишь
# на каждый вызов, но это не "the very last API"
for user in get_user(User.name == 'vasya', User.age > datetime.now()):
pass</font></pre></span><p style="text-indent:20px"> Безусловно у любого абстрагирования есть минимум один существенный минус - пользуясь им люди хуже понимают что происходит на уровнях ниже. При возникновении проблем внутри абстракций или еще ниже (например могут быть проблемы с сетью) на их решение может уйти много времени - а проблемы возникают постоянно. Во-вторых исчезает контроль над ситуацией. Любая сложная библиотека несет свои неожиданности в добавок к особенностям нижнего уровня. В итоге вопрос "почему функция выборки из базы зависает" может стать не решаемым.</p><p style="text-indent:20px"> Я за последнее время видел некоторое количество достаточно опытных программистов, которые почти ничего не знают про сокеты и TCP, потому что RabbitMQ и про потоки, потому что фреймфорк подставьтет-тут-свой-фреймфорк. Нет, это не проявления вселенского зла. Отлично что программирование упрощается, но эта категория программистов - клиенты обеих проблем сверху.</p><p style="text-indent:20px">Впрочем это уже другой вопрос. А наш вопрос - абстракции :).</p><p style="text-indent:20px"> Уровни абстракции должны быть Надежными и легкими для изучения. А ваши абстракции "последнего рубежа" должны отражать проблемную область программы и закрывать ими я бы стал почти все нетривиальные внешние зависимости, которые используются в значительной части кода программы и привносят свои абстракции.</p><p style="text-indent:20px">P.S. Обычно такой подход хорошо работает, но как и все обобщенные рассуждения эти мысли стоит материализовывать без фанатизма - случаи то разные бывают.</p>Ссылки:<br> <a name="SQLAlchemy"><a href="http://www.sqlalchemy.org/">www.sqlalchemy.org</a></a><br> <a name="DBAPI"><a href="http://www.python.org/dev/peps/pep-0249/">www.python.org/dev/peps/pep-0249</a></a><br><p style="text-indent:20px">Исходники этого и других постов со скриптами лежат тут - <a href="https://github.com/koder-ua/python-lectures">github.com/koder-ua</a>. При использовании их, пожалуйста, ссылайтесь на <a href="http://koder-ua.blogspot.com/">koder-ua.blogspot.com</a>.</p>
<script type="text/javascript">
function on_hidabble_click()
{
var me = $(this);
var hide_id = me.attr("objtohide");
var controlled_object = $('#' + hide_id);
controlled_object.toggle();
if ( controlled_object.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".hidder").click(on_hidabble_click);
function on_double_hidabble_click()
{
var me = $(this);
var hide_id1 = me.attr("objtohide1");
var hide_id2 = me.attr("objtohide2");
var controlled_object1 = $('#' + hide_id1);
var controlled_object2 = $('#' + hide_id2);
controlled_object1.toggle();
controlled_object2.toggle();
if ( controlled_object1.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".dhidder").click(on_double_hidabble_click);
</script>Anonymoushttp://www.blogger.com/profile/06645078574760734586noreply@blogger.com0tag:blogger.com,1999:blog-1174489715777430743.post-65542206986340965812011-12-11T01:08:00.000+02:002011-12-11T22:12:11.934+02:00Об уровнях абстракций - The Very Last API<p style="text-indent:20px"> При написании не тривиальных приложений возникает вопрос: над какими библиотеками делать еще один абстрактный слой, а над какими - нет? Какие абстракции делать?</p><p style="text-indent:20px"> Стоит ли делать прослойку над, например, <a href="#SQLAlchemy">SQLAlchemy</a>? Это же и так прослойка над SQL и <a href="#DBAPI">DBAPI</a>. Имеет ли смысл делать уровни абстракций над такими достаточно хорошими и отточенными в смысле интерфейсов библиотеками?</p><p style="text-indent:20px"> Ответ очень простой - библиотеки представляют API который должен быть применим для широкого спектра приложений. Они отображают низкоуровневые (с точки зрения их API ) вызовы на более высокоуровневый, но абстрактный интерфейс. Характерный пример - библиотеки передачи сообщений. Они позволяют не думать о сокетах, упаковке/распаковке <b>float</b>/<b>int</b> и т.п., а просто передавать структуры данных.</p><a name='more'></a><p style="text-indent:20px"> Типичный API системы пересылки сообщений выглядит как:</p><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Messaging</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">send_message_async</span>(<span style="color: #008000">self</span>, dest, message_tp, message_data):
<span style="color: #408080; font-style: italic"># some code</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">send_message_sync</span>(<span style="color: #008000">self</span>, dest, message_tp, message_data):
<span style="color: #408080; font-style: italic"># some code</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">get_message</span>(<span style="color: #008000">self</span>):
<span style="color: #408080; font-style: italic"># some code</span>
</pre></div><p style="text-indent:20px"> Но программе не нужно посылать никакие сообщения! Ей нужно выполнить действия - показать пользователю GUI, узнать завершился ли удаленный процесс, etc. API сообщений, которое было-бы идеально для некоторой программы выглядит примерно так:</p><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">MyAPI</span>(<span style="color: #008000">object</span>):
<span style="color: #AA22FF">@exception_on_false</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">show_ui_message</span>(<span style="color: #008000">self</span>, level, text):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>messanger<span style="color: #666666">.</span>send_message_async(<span style="color: #008000">self</span><span style="color: #666666">.</span>UI_PROC_ID,
SHOW_DIALOG,
<span style="color: #008000">dict</span>(level<span style="color: #666666">=</span>level, text<span style="color: #666666">=</span>text))
<span style="color: #AA22FF">@exception_on_false</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">reboot_vm</span>(<span style="color: #008000">self</span>, ip):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>messanger<span style="color: #666666">.</span>send_message_async(
<span style="color: #008000">self</span><span style="color: #666666">.</span>get_remote_agent_id(ip),
REBOOT_VM,
<span style="color: #008000">None</span>)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">ls_remote</span>(<span style="color: #008000">self</span>, ip, remote_path):
tp, res <span style="color: #666666">=</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>messanger<span style="color: #666666">.</span>send_message_sync(
<span style="color: #008000">self</span><span style="color: #666666">.</span>get_remote_agent_id(ip),
EXEC_CMD,
<span style="color: #BA2121">'ls -l {0}'</span><span style="color: #666666">.</span>format(remote_path))
<span style="color: #008000; font-weight: bold">if</span> tp <span style="color: #666666">==</span> EXECUTION_FINISHED_OK:
<span style="color: #008000; font-weight: bold">return</span> res
<span style="color: #008000; font-weight: bold">else</span>:
<span style="color: #008000; font-weight: bold">raise</span> <span style="color: #D2413A; font-weight: bold">RuntimeError</span>(<span style="color: #BA2121">"Cmd ... finished with error code {0}"</span><span style="color: #666666">.</span>\
format(res))
</pre></div><p style="text-indent:20px"> Очень принципиальный момент - конечный API должен отражать задачи, стоящие перед программой. Четкое отделение основной логики программы от деталей реализации имеет минимум два очень важных плюса - позволяет сделать главный код легче для чтения (убирает лишние абстракции) и максимально отвязать программу от API библиотек (локализовать привязку).</p><p style="text-indent:20px"> Это особенная прослойка, это "последняя линия". Если остальные API предоставляют нам абстракции, то эта прослойка не должна добавлять ничего лишнего, она избавляет нас от более не нужных абстракций и говорит языком предметной области программы.</p><p style="text-indent:20px"> Вам нужно хранить в базе список фруктов? Сделайте функцию <b>store_fruits</b>. Такая функция позволить вам перейти от PostgreSQL к Cassandra, а потом к текстовым файлам (маловероятная ситуация, но не суть) без влияния на остальную программу. Потому что программе все равно где лежат данные. Программу интересует только что они сохраняются и восстанавливаются.</p><p style="text-indent:20px"> Мы никак не может защититься от изменения требований к программе и вместе с изменениями требований нужно будет меняться и API, который предоставляет наш слой абстракции. Но вот изменения в типе базы/структуре базы/ORM не будет приводить к изменению кода. Если смена БД или ORM - маловероятная ситуация, то вот добавление нового поля вида <b>deleted</b>, означающего, что запись вроде как удалена и почти нигде не должна использоваться - весьма частый случай.</p><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #408080; font-style: italic"># почти реальный запрос прямо из функции, отвечающей за логики программы </span>
services <span style="color: #666666">=</span> session<span style="color: #666666">.</span>query(Service)<span style="color: #666666">.</span>\
<span style="color: #008000">filter</span>(Service<span style="color: #666666">.</span>zone_id <span style="color: #666666">==</span> zone_id)<span style="color: #666666">.</span>\
<span style="color: #008000">filter</span>(Service<span style="color: #666666">.</span>service_id <span style="color: #666666">==</span> service_id)<span style="color: #666666">.</span>\
with_lockmode(<span style="color: #BA2121">'update'</span>)<span style="color: #666666">.</span>\
limit(<span style="color: #666666">10</span>)<span style="color: #666666">.</span>all()
<span style="color: #408080; font-style: italic"># Чего-чего????? </span>
<span style="color: #408080; font-style: italic"># комментарий к запросу немного бы спас ситуацию</span>
<span style="color: #408080; font-style: italic"># но вместо решения проблем с помощью комментирования их лучше не создавать </span>
<span style="color: #408080; font-style: italic"># Этот код не требует комментариев</span>
<span style="color: #008000; font-weight: bold">for</span> service <span style="color: #AA22FF; font-weight: bold">in</span> db<span style="color: #666666">.</span>get_10_services(zone_id, service_id):
<span style="color: #408080; font-style: italic"># some code</span>
<span style="color: #408080; font-style: italic"># в файле db.py</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">get_10_services</span>(<span style="color: #008000">self</span>, zone_id, service_id):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>session<span style="color: #666666">.</span>query(Service)<span style="color: #666666">.</span>\
<span style="color: #008000">filter</span>(Service<span style="color: #666666">.</span>zone_id <span style="color: #666666">==</span> zone_id)<span style="color: #666666">.</span>\
<span style="color: #008000">filter</span>(Service<span style="color: #666666">.</span>service_id <span style="color: #666666">==</span> service_id)<span style="color: #666666">.</span>\
with_lockmode(<span style="color: #BA2121">'update'</span>)<span style="color: #666666">.</span>\
limit(<span style="color: #666666">10</span>)<span style="color: #666666">.</span>all()
</pre></div><p style="text-indent:20px">Еще одна ошибка - попытка сэкономить на таком API и сделать в этом духе:</p><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">sqlalchemy</span> <span style="color: #008000; font-weight: bold">as</span> <span style="color: #0000FF; font-weight: bold">sa</span>
<span style="color: #408080; font-style: italic"># one Function to rule them all!!</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">get_user</span>(session, <span style="color: #666666">*</span>opts):
<span style="color: #008000; font-weight: bold">return</span> session<span style="color: #666666">.</span>query(User)<span style="color: #666666">.</span>filter(sa<span style="color: #666666">.</span>and_(<span style="color: #666666">*</span>opts))<span style="color: #666666">.</span>all()
<span style="color: #408080; font-style: italic"># этот код уже требует знания что там у нас за sqlalchemy такая</span>
<span style="color: #408080; font-style: italic"># и на mongo его уже не переписать так-же легко</span>
<span style="color: #408080; font-style: italic"># тут "торчат уши" sqlalchemy. Да, мы съекономили 10-20 нажатий клавишь</span>
<span style="color: #408080; font-style: italic"># на каждый вызов, но это не "the very last API"</span>
<span style="color: #008000; font-weight: bold">for</span> user <span style="color: #AA22FF; font-weight: bold">in</span> get_user(User<span style="color: #666666">.</span>name <span style="color: #666666">==</span> <span style="color: #BA2121">'vasya'</span>, User<span style="color: #666666">.</span>age <span style="color: #666666">></span> datetime<span style="color: #666666">.</span>now()):
<span style="color: #008000; font-weight: bold">pass</span>
</pre></div><p style="text-indent:20px"> Безусловно у любого абстрагирования есть минимум один существенный минус - пользуясь им люди хуже понимают что происходит на уровнях ниже. При возникновении проблем внутри абстракций или еще ниже (например могут быть проблемы с сетью) на их решение может уйти много времени - а проблемы возникают постоянно. Во-вторых исчезает контроль над ситуацией. Любая сложная библиотека несет свои неожиданности в добавок к особенностям нижнего уровня. В итоге вопрос "почему функция выборки из базы зависает" может стать не решаемым.</p><p style="text-indent:20px"> Я за последнее время видел некоторое количество достаточно опытных программистов, которые почти ничего не знают про сокеты и TCP, потому что RabbitMQ и про потоки, потому что фреймфорк подставьтет-тут-свой-фреймфорк. Нет, это не проявления вселенского зла. Отлично что программирование упрощается, но эта категория программистов - клиенты обеих проблем сверху.</p><p style="text-indent:20px">Впрочем это уже другой вопрос. А наш вопрос - абстракции :).</p><p style="text-indent:20px"> Уровни абстракции должны быть Надежными и легкими для изучения. А ваши абстракции "последнего рубежа" должны отражать проблемную область программы и закрывать ими я бы стал почти все нетривиальные внешние зависимости, которые используются в значительной части кода программы и привносят свои абстракции.</p><p style="text-indent:20px">P.S. Обычно такой подход хорошо работает, но как и все обобщенные рассуждения эти мысли стоит материализовывать без фанатизма - случаи то разные бывают.</p>Ссылки:<br> <a name="SQLAlchemy"><a href="http://www.sqlalchemy.org/">www.sqlalchemy.org/</a></a><br> <a name="DBAPI"><a href="http://www.python.org/dev/peps/pep-0249/">www.python.org/dev/peps/pep-0249/</a></a><br>Исходники этого и других постов со скриптами лежат тут - <a href="https://github.com/koder-ua/python-lectures">github.com/koder-ua</a>.При использовании их, пожалуйста, ссылайтесь на <a href="http://koder-ua.blogspot.com/">koder-ua.blogspot.com</a>.Anonymoushttp://www.blogger.com/profile/06645078574760734586noreply@blogger.com0tag:blogger.com,1999:blog-1174489715777430743.post-76781403341214765942011-12-09T19:35:00.000+02:002011-12-24T18:34:59.044+02:00Оператор with<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<br><h2>Теория</h2><p style="text-indent:20px"> Оператор <a href="http://docs.python.org/reference/compound_stmts.html#the-with-statement">with</a> появился в python 2.5, но, не смотря на это, используется до сих пор недостаточно широко. Являясь упрощенной версией анонимных блоков кода <b>with</b> позволяет:</p><ul><li>исполнить код до начала блока<li>исполнить код по выходу из блока, независимо от того это выход по исключению с помощью <b>return</b> или другим способом<li>обработать исключение, возникшее в блоке.</ul><p style="text-indent:20px">Синтаксически <b>with</b> выглядит следующим образом:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="fc364ee42e4c11e18fd214feb5b819a0" objtohide2="fc368d5a2e4c11e18fd214feb5b819a0" >Hightlited/Raw</a><br><span id="fc364ee42e4c11e18fd214feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">with</span> operation:
code
</pre></div></span><span style="font-size:110%;display:none" id="fc368d5a2e4c11e18fd214feb5b819a0"><pre><font face="courier">with operation:
code</font></pre></span><p style="text-indent:20px"> <b>operation</b> может быть объектом, выражением или конструкцией вида <b>expression as var</b>. Как и много других конструкций он является синтаксическим сахаром для более громоздкого выражения:</p><a name='more'></a><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="fc3730702e4c11e18fd214feb5b819a0" objtohide2="fc3776662e4c11e18fd214feb5b819a0" >Hightlited/Raw</a><br><span id="fc3730702e4c11e18fd214feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">with</span> operation <span style="color: #008000; font-weight: bold">as</span> var:
code
</pre></div></span><span style="font-size:110%;display:none" id="fc3776662e4c11e18fd214feb5b819a0"><pre><font face="courier">with operation as var:
code</font></pre></span><p style="text-indent:20px">=></p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="fc383d122e4c11e18fd214feb5b819a0" objtohide2="fc3844e22e4c11e18fd214feb5b819a0" >Hightlited/Raw</a><br><span id="fc383d122e4c11e18fd214feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">_obj <span style="color: #666666">=</span> operation
<span style="color: #408080; font-style: italic"># вход в блок</span>
var <span style="color: #666666">=</span> _obj<span style="color: #666666">.</span>__enter__()
<span style="color: #008000; font-weight: bold">try</span>:
code
<span style="color: #008000; font-weight: bold">except</span> <span style="color: #D2413A; font-weight: bold">Exception</span> <span style="color: #008000; font-weight: bold">as</span> exc:
<span style="color: #408080; font-style: italic"># если произошло исключение - передаем его управляющему объекту</span>
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #AA22FF; font-weight: bold">not</span> _obj<span style="color: #666666">.</span>__exit__(<span style="color: #666666">*</span>sys<span style="color: #666666">.</span>exception_info()):
<span style="color: #408080; font-style: italic"># если он вернул False(None) возбуждаем его</span>
<span style="color: #008000; font-weight: bold">raise</span>
<span style="color: #408080; font-style: italic"># если True - подавляем исключение</span>
<span style="color: #008000; font-weight: bold">else</span>:
<span style="color: #408080; font-style: italic"># если не было исключения - передаем None * 3</span>
_obj<span style="color: #666666">.</span>__exit__(<span style="color: #008000">None</span>, <span style="color: #008000">None</span>, <span style="color: #008000">None</span>)
</pre></div></span><span style="font-size:110%;display:none" id="fc3844e22e4c11e18fd214feb5b819a0"><pre><font face="courier">_obj = operation
# вход в блок
var = _obj.__enter__()
try:
code
except Exception as exc:
# если произошло исключение - передаем его управляющему объекту
if not _obj.__exit__(*sys.exception_info()):
# если он вернул False(None) возбуждаем его
raise
# если True - подавляем исключение
else:
# если не было исключения - передаем None * 3
_obj.__exit__(None, None, None)</font></pre></span><p style="text-indent:20px"> Более подробно с <b>with</b> можно ознакомиться в соответствующем <a href="http://www.python.org/dev/peps/pep-0343/">PEP-343</a>. <b>with</b> управляется объектом, называемым менеджером контекста (МК) - <b>_obj</b> в примере выше. Есть два основных способа написания МК - класс с методами <b>__enter__</b> и <b>__exit__</b> и генератор:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="fc59004c2e4c11e18fd214feb5b819a0" objtohide2="fc59020e2e4c11e18fd214feb5b819a0" >Hightlited/Raw</a><br><span id="fc59004c2e4c11e18fd214feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">os</span>
<span style="color: #008000; font-weight: bold">from</span> <span style="color: #0000FF; font-weight: bold">contextlib</span> <span style="color: #008000; font-weight: bold">import</span> contextmanager
<span style="color: #408080; font-style: italic"># Это только пример.</span>
<span style="color: #408080; font-style: italic"># Использование такого кода для генерации временных файлов</span>
<span style="color: #408080; font-style: italic"># небезопасно. Используйте функции 'os.tmpfile'.</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">TempoFileCreator</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>):
<span style="color: #008000">self</span><span style="color: #666666">.</span>fname <span style="color: #666666">=</span> <span style="color: #008000">None</span>
<span style="color: #008000">self</span><span style="color: #666666">.</span>fd <span style="color: #666666">=</span> <span style="color: #008000">None</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__inter__</span>(<span style="color: #008000">self</span>):
<span style="color: #408080; font-style: italic"># вызывается по входу в блок</span>
<span style="color: #008000">self</span><span style="color: #666666">.</span>fname <span style="color: #666666">=</span> os<span style="color: #666666">.</span>tmpnam()
<span style="color: #008000">self</span><span style="color: #666666">.</span>fd <span style="color: #666666">=</span> <span style="color: #008000">open</span>(<span style="color: #008000">self</span><span style="color: #666666">.</span>fname, <span style="color: #BA2121">"w+"</span>)
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>fname, <span style="color: #008000">self</span><span style="color: #666666">.</span>fd
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__exit__</span>(<span style="color: #008000">self</span>, exc_type, exc_val, traceback):
<span style="color: #408080; font-style: italic"># вызывается по выходу из блока</span>
<span style="color: #408080; font-style: italic"># если в блоке выброшено исключение, то</span>
<span style="color: #408080; font-style: italic"># его тип, значение и трейс будут переданы в параметрах</span>
<span style="color: #008000">self</span><span style="color: #666666">.</span>fd<span style="color: #666666">.</span>close()
os<span style="color: #666666">.</span>unlink(<span style="color: #008000">self</span><span style="color: #666666">.</span>fname)
<span style="color: #008000">self</span><span style="color: #666666">.</span>fd <span style="color: #666666">=</span> <span style="color: #008000">None</span>
<span style="color: #008000">self</span><span style="color: #666666">.</span>fname <span style="color: #666666">=</span> <span style="color: #008000">None</span>
<span style="color: #408080; font-style: italic"># здесь написано return None => исключение не будет подавляться </span>
<span style="color: #AA22FF">@contextmanager</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">tempo_file</span>():
<span style="color: #408080; font-style: italic"># полностью равноценно классу TempoFileCreator</span>
fname <span style="color: #666666">=</span> os<span style="color: #666666">.</span>tmpnam()
fd <span style="color: #666666">=</span> <span style="color: #008000">open</span>(fname, <span style="color: #BA2121">"w+"</span>)
<span style="color: #008000; font-weight: bold">try</span>:
<span style="color: #008000; font-weight: bold">yield</span> fname, fd
<span style="color: #408080; font-style: italic">#сейчас исполняется блок</span>
<span style="color: #008000; font-weight: bold">finally</span>:
<span style="color: #408080; font-style: italic"># это наш __exit__</span>
fd<span style="color: #666666">.</span>close()
os<span style="color: #666666">.</span>unlink(fd)
</pre></div></span><span style="font-size:110%;display:none" id="fc59020e2e4c11e18fd214feb5b819a0"><pre><font face="courier">import os
from contextlib import contextmanager
# Это только пример.
# Использование такого кода для генерации временных файлов
# небезопасно. Используйте функции 'os.tmpfile'.
class TempoFileCreator(object):
def __init__(self):
self.fname = None
self.fd = None
def __inter__(self):
# вызывается по входу в блок
self.fname = os.tmpnam()
self.fd = open(self.fname, "w+")
return self.fname, self.fd
def __exit__(self, exc_type, exc_val, traceback):
# вызывается по выходу из блока
# если в блоке выброшено исключение, то
# его тип, значение и трейс будут переданы в параметрах
self.fd.close()
os.unlink(self.fname)
self.fd = None
self.fname = None
# здесь написано return None => исключение не будет подавляться
@contextmanager
def tempo_file():
# полностью равноценно классу TempoFileCreator
fname = os.tmpnam()
fd = open(fname, "w+")
try:
yield fname, fd
#сейчас исполняется блок
finally:
# это наш __exit__
fd.close()
os.unlink(fd)</font></pre></span><p style="text-indent:20px">Использование:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="fc738a482e4c11e18fd214feb5b819a0" objtohide2="fc738c142e4c11e18fd214feb5b819a0" >Hightlited/Raw</a><br><span id="fc738a482e4c11e18fd214feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">with</span> tempo_file() <span style="color: #008000; font-weight: bold">as</span> (fname, fd):
<span style="color: #408080; font-style: italic"># читаем-пишем в файл</span>
<span style="color: #408080; font-style: italic"># по выходу из блока он будет удален</span>
<span style="color: #008000; font-weight: bold">pass</span>
</pre></div></span><span style="font-size:110%;display:none" id="fc738c142e4c11e18fd214feb5b819a0"><pre><font face="courier">with tempo_file() as (fname, fd):
# читаем-пишем в файл
# по выходу из блока он будет удален
pass</font></pre></span><p style="text-indent:20px"> Ядро python реализует только первый вариант для контекст менеджера, второй реализуется в <b>contextlib.contextmanager</b>.</p><p style="text-indent:20px"> В том случае если во внутреннем блоке кода есть оператор <b>yield</b>, т.е. мы работаем в генераторе, <b>__exit__</b> будет вызван по выходу из генератора или по его удалению. Таким образом если ссылку на генератор сохранить, то <b>__exit__</b> не будет вызван до тех пор, пока ссылка будет существовать:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="fc8a81ee2e4c11e18fd214feb5b819a0" objtohide2="fc8a83b02e4c11e18fd214feb5b819a0" >Hightlited/Raw</a><br><span id="fc8a81ee2e4c11e18fd214feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #AA22FF">@contextmanager</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">cmanager</span>():
<span style="color: #008000; font-weight: bold">yield</span>
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"Exit"</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">some_func</span>():
<span style="color: #008000; font-weight: bold">with</span> cmanager():
<span style="color: #008000; font-weight: bold">yield</span> <span style="color: #666666">1</span>
it <span style="color: #666666">=</span> some_func()
<span style="color: #008000; font-weight: bold">for</span> val <span style="color: #AA22FF; font-weight: bold">in</span> it:
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #408080; font-style: italic"># Exit напечатается здесь</span>
it <span style="color: #666666">=</span> some_func()
<span style="color: #008000; font-weight: bold">del</span> it <span style="color: #408080; font-style: italic"># или по выходу из текущего блока</span>
<span style="color: #408080; font-style: italic"># Exit напечатается здесь</span>
</pre></div></span><span style="font-size:110%;display:none" id="fc8a83b02e4c11e18fd214feb5b819a0"><pre><font face="courier">@contextmanager
def cmanager():
yield
print "Exit"
def some_func():
with cmanager():
yield 1
it = some_func()
for val in it:
pass
# Exit напечатается здесь
it = some_func()
del it # или по выходу из текущего блока
# Exit напечатается здесь</font></pre></span><p style="text-indent:20px"> Подводя итоги - <b>with</b> позволяет сэкономить 2-4 строки кода на каждое использование и повышает читаемость программы, меньше отвлекая нас от логики деталями реализации.</p><br><h2>Практика</h2><p style="text-indent:20px"> Начнем с примеров, которые встречаются в стандартной библиотеке и будем постепенно переходить к менее распространенным вариантам использования.</p><ul><li>Открытие/создание объекта по входу в блок - закрытие/удаление по выходу:</ul><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="fca57abc2e4c11e18fd214feb5b819a0" objtohide2="fca57cf62e4c11e18fd214feb5b819a0" >Hightlited/Raw</a><br><span id="fca57abc2e4c11e18fd214feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">with</span> <span style="color: #008000">open</span>(<span style="color: #BA2121">'/tmp/tt.txt'</span>) <span style="color: #008000; font-weight: bold">as</span> fd:
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #408080; font-style: italic"># здесь файл закрывается</span>
<span style="color: #408080; font-style: italic"># переменная fd доступна, но файл уже закрыт</span>
<span style="color: #408080; font-style: italic"># <closed file '/tmp/tt.txt', mode 'r' at 0x1beeed0></span>
</pre></div></span><span style="font-size:110%;display:none" id="fca57cf62e4c11e18fd214feb5b819a0"><pre><font face="courier">with open('/tmp/tt.txt') as fd:
pass
# здесь файл закрывается
# переменная fd доступна, но файл уже закрыт
# <closed file '/tmp/tt.txt', mode 'r' at 0x1beeed0></font></pre></span><p style="text-indent:20px"> Чаще всего в python программах не закрывают файл вручную, обоснованно полагаясь на подсчет ссылок. Блоки <b>with</b> кроме явного указания области, где файл открыт имеют еще одно небольшое преимущество, связанное с особенностями обработки исключений:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="fcc110922e4c11e18fd214feb5b819a0" objtohide2="fcc1125e2e4c11e18fd214feb5b819a0" >Hightlited/Raw</a><br><span id="fcc110922e4c11e18fd214feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">i_am_not_always_close_files</span>(fname):
fd <span style="color: #666666">=</span> <span style="color: #008000">open</span>(fname)
i_am_not_always_close_files(<span style="color: #BA2121">"/tmp/x.txt"</span>)
<span style="color: #408080; font-style: italic"># в этой точке файл уже закрыт</span>
</pre></div></span><span style="font-size:110%;display:none" id="fcc1125e2e4c11e18fd214feb5b819a0"><pre><font face="courier">def i_am_not_always_close_files(fname):
fd = open(fname)
i_am_not_always_close_files("/tmp/x.txt")
# в этой точке файл уже закрыт</font></pre></span><p style="text-indent:20px"> Если внутри фцнкции <b>i_am_not_always_close_files</b> будет возбуждено исключение, то файл не закроется до того момента, пока оно не будет обработано:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="fcdc3ae82e4c11e18fd214feb5b819a0" objtohide2="fcdc3caa2e4c11e18fd214feb5b819a0" >Hightlited/Raw</a><br><span id="fcdc3ae82e4c11e18fd214feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">sys</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">i_am_not_always_close_files</span>(fname):
fd <span style="color: #666666">=</span> <span style="color: #008000">open</span>(fname)
<span style="color: #008000; font-weight: bold">raise</span> <span style="color: #D2413A; font-weight: bold">RuntimeError</span>(<span style="color: #BA2121">''</span>)
<span style="color: #008000; font-weight: bold">try</span>:
i_am_not_always_close_files(<span style="color: #BA2121">"/tmp/x.txt"</span>)
<span style="color: #008000; font-weight: bold">except</span> <span style="color: #D2413A; font-weight: bold">RuntimeError</span>:
<span style="color: #408080; font-style: italic">#тут файл еще открыт</span>
traceback <span style="color: #666666">=</span> sys<span style="color: #666666">.</span>exc_info()[<span style="color: #666666">2</span>]
<span style="color: #408080; font-style: italic"># спуск на один кадр стека глубже</span>
<span style="color: #408080; font-style: italic"># 'fd' в его локальных переменных</span>
<span style="color: #008000; font-weight: bold">print</span> traceback<span style="color: #666666">.</span>tb_next<span style="color: #666666">.</span>tb_frame<span style="color: #666666">.</span>f_locals[<span style="color: #BA2121">'fd'</span>]
<span style="color: #408080; font-style: italic"># <open file '/tmp/tt.txt', mode 'r' at 0x1d31030></span>
<span style="color: #408080; font-style: italic"># в этой точке файл уже закрыт</span>
</pre></div></span><span style="font-size:110%;display:none" id="fcdc3caa2e4c11e18fd214feb5b819a0"><pre><font face="courier">import sys
def i_am_not_always_close_files(fname):
fd = open(fname)
raise RuntimeError('')
try:
i_am_not_always_close_files("/tmp/x.txt")
except RuntimeError:
#тут файл еще открыт
traceback = sys.exc_info()[2]
# спуск на один кадр стека глубже
# 'fd' в его локальных переменных
print traceback.tb_next.tb_frame.f_locals['fd']
# <open file '/tmp/tt.txt', mode 'r' at 0x1d31030>
# в этой точке файл уже закрыт</font></pre></span><p style="text-indent:20px"> Дело в том, что пока жив объект-исключение он хранит путь исключения со всеми кадрами стека. При использовании <b>with</b> файл закрывается по выходу блока кода, обрамляемого в <b>with</b>, так что в обработчике исключения файл был бы уже закрыт. Впрочем это обычно не существенное различие.</p><p style="text-indent:20px"> Еще пример:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="fcdc75942e4c11e18fd214feb5b819a0" objtohide2="fcdc77062e4c11e18fd214feb5b819a0" >Hightlited/Raw</a><br><span id="fcdc75942e4c11e18fd214feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #408080; font-style: italic"># создадим виртуальную машину</span>
<span style="color: #008000; font-weight: bold">with</span> create_virtual_machine(root_passwd) <span style="color: #008000; font-weight: bold">as</span> vm_ip:
<span style="color: #408080; font-style: italic"># выполним на ней тестирования скрипта автоматической установки</span>
test_auto_deploy_script(vm_ip, root_passwd)
<span style="color: #408080; font-style: italic"># по выходу уничтожим vm_ip</span>
</pre></div></span><span style="font-size:110%;display:none" id="fcdc77062e4c11e18fd214feb5b819a0"><pre><font face="courier"># создадим виртуальную машину
with create_virtual_machine(root_passwd) as vm_ip:
# выполним на ней тестирования скрипта автоматической установки
test_auto_deploy_script(vm_ip, root_passwd)
# по выходу уничтожим vm_ip</font></pre></span><ul><li>Захват/освобождение объекта Эту семантику поддерживают все стандартные объекты синхронизации</ul><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="fcfa083e2e4c11e18fd214feb5b819a0" objtohide2="fcfa0a0a2e4c11e18fd214feb5b819a0" >Hightlited/Raw</a><br><span id="fcfa083e2e4c11e18fd214feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">threading</span>
lock <span style="color: #666666">=</span> Threading<span style="color: #666666">.</span>Lock()
<span style="color: #008000; font-weight: bold">with</span> lock:
<span style="color: #408080; font-style: italic"># блокровка захваченна</span>
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #408080; font-style: italic"># блокировка отпущенна</span>
</pre></div></span><span style="font-size:110%;display:none" id="fcfa0a0a2e4c11e18fd214feb5b819a0"><pre><font face="courier">
import threading
lock = Threading.Lock()
with lock:
# блокровка захваченна
pass
# блокировка отпущенна</font></pre></span><ul><li>Временное изменение настроек (примеры из документации python)</ul><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="fd39e7602e4c11e18fd214feb5b819a0" objtohide2="fd39e92c2e4c11e18fd214feb5b819a0" >Hightlited/Raw</a><br><span id="fd39e7602e4c11e18fd214feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">warnings</span>
<span style="color: #008000; font-weight: bold">from</span> <span style="color: #0000FF; font-weight: bold">decimal</span> <span style="color: #008000; font-weight: bold">import</span> localcontext
<span style="color: #008000; font-weight: bold">with</span> warnings<span style="color: #666666">.</span>catch_warnings():
warnings<span style="color: #666666">.</span>simplefilter(<span style="color: #BA2121">"ignore"</span>)
<span style="color: #408080; font-style: italic"># в этом участке кода все предепреждения игнорируются</span>
<span style="color: #008000; font-weight: bold">with</span> localcontext() <span style="color: #008000; font-weight: bold">as</span> ctx:
ctx<span style="color: #666666">.</span>prec <span style="color: #666666">=</span> <span style="color: #666666">42</span> <span style="color: #408080; font-style: italic"># расчеты с типом Decimal выполняются с </span>
<span style="color: #408080; font-style: italic"># заоблачной точностью</span>
s <span style="color: #666666">=</span> calculate_something()
</pre></div></span><span style="font-size:110%;display:none" id="fd39e92c2e4c11e18fd214feb5b819a0"><pre><font face="courier">import warnings
from decimal import localcontext
with warnings.catch_warnings():
warnings.simplefilter("ignore")
# в этом участке кода все предепреждения игнорируются
with localcontext() as ctx:
ctx.prec = 42 # расчеты с типом Decimal выполняются с
# заоблачной точностью
s = calculate_something()</font></pre></span><ul><li>Смена текущей директории (пример использования библиотеки <a href="http://fabfile.org">fabric</a>)</ul><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="fd50b4542e4c11e18fd214feb5b819a0" objtohide2="fd50b6162e4c11e18fd214feb5b819a0" >Hightlited/Raw</a><br><span id="fd50b4542e4c11e18fd214feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">from</span> <span style="color: #0000FF; font-weight: bold">fabric.context_managers</span> <span style="color: #008000; font-weight: bold">import</span> lcd
os<span style="color: #666666">.</span>chdir(<span style="color: #BA2121">'/opt'</span>)
<span style="color: #008000; font-weight: bold">print</span> os<span style="color: #666666">.</span>getcwd() <span style="color: #408080; font-style: italic"># => /opt</span>
<span style="color: #008000; font-weight: bold">with</span> lcd(<span style="color: #BA2121">'/tmp'</span>):
<span style="color: #008000; font-weight: bold">print</span> os<span style="color: #666666">.</span>getcwd() <span style="color: #408080; font-style: italic"># => /tmp</span>
<span style="color: #008000; font-weight: bold">print</span> os<span style="color: #666666">.</span>getcwd() <span style="color: #408080; font-style: italic"># => /opt</span>
</pre></div></span><span style="font-size:110%;display:none" id="fd50b6162e4c11e18fd214feb5b819a0"><pre><font face="courier">from fabric.context_managers import lcd
os.chdir('/opt')
print os.getcwd() # => /opt
with lcd('/tmp'):
print os.getcwd() # => /tmp
print os.getcwd() # => /opt</font></pre></span><p style="text-indent:20px"> Нужно помнить, что изменение таким образом глобальных настроек в многопоточной программе может доставить много веселых минут при отладке.</p><ul><li>Подмена/восстановление объекта (временный <a href="http://en.wikipedia.org/wiki/Monkey_patch">monkey patching</a>, пример использования библиотеки <a href="http://www.voidspace.org.uk/python/mock/compare.html#mocking-a-context-manager">mock</a>)</ul><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="fd74b3f42e4c11e18fd214feb5b819a0" objtohide2="fd74b6242e4c11e18fd214feb5b819a0" >Hightlited/Raw</a><br><span id="fd74b3f42e4c11e18fd214feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">mock</span>
my_mock <span style="color: #666666">=</span> mock<span style="color: #666666">.</span>MagicMock()
<span style="color: #008000; font-weight: bold">with</span> mock<span style="color: #666666">.</span>patch(<span style="color: #BA2121">'__builtin__.open'</span>, my_mock):
<span style="color: #408080; font-style: italic"># open подменена на mock.MagicMock</span>
<span style="color: #008000; font-weight: bold">with</span> <span style="color: #008000">open</span>(<span style="color: #BA2121">'foo'</span>) <span style="color: #008000; font-weight: bold">as</span> h:
<span style="color: #008000; font-weight: bold">pass</span>
</pre></div></span><span style="font-size:110%;display:none" id="fd74b6242e4c11e18fd214feb5b819a0"><pre><font face="courier">import mock
my_mock = mock.MagicMock()
with mock.patch('__builtin__.open', my_mock):
# open подменена на mock.MagicMock
with open('foo') as h:
pass</font></pre></span><ul><li>Транзакции баз данных....</ul><p style="text-indent:20px">Менеджер транзакций для <a href="http://www.sqlalchemy.org/">sqlalchemy</a></p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="fd8f7e282e4c11e18fd214feb5b819a0" objtohide2="fd8f83322e4c11e18fd214feb5b819a0" >Hightlited/Raw</a><br><span id="fd8f7e282e4c11e18fd214feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">from</span> <span style="color: #0000FF; font-weight: bold">config</span> <span style="color: #008000; font-weight: bold">import</span> DB_URI
<span style="color: #008000; font-weight: bold">from</span> <span style="color: #0000FF; font-weight: bold">db_session</span> <span style="color: #008000; font-weight: bold">import</span> get_session
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">DBWrapper</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>):
<span style="color: #008000">self</span><span style="color: #666666">.</span>session <span style="color: #666666">=</span> <span style="color: #008000">None</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__enter__</span>(<span style="color: #008000">self</span>):
<span style="color: #008000">self</span><span style="color: #666666">.</span>session <span style="color: #666666">=</span> get_session(DB_URI)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__exit__</span>(<span style="color: #008000">self</span>, exc, <span style="color: #666666">*</span>args):
<span style="color: #408080; font-style: italic"># при выходе из 'with':</span>
<span style="color: #008000; font-weight: bold">if</span> exc <span style="color: #AA22FF; font-weight: bold">is</span> <span style="color: #008000">None</span>:
<span style="color: #408080; font-style: italic"># если все прошло успешно коммитим </span>
<span style="color: #408080; font-style: italic"># транзакцию и закрываем курсор</span>
<span style="color: #008000">self</span><span style="color: #666666">.</span>session<span style="color: #666666">.</span>commit()
<span style="color: #408080; font-style: italic"># если было исключение - откатываем</span>
<span style="color: #008000">self</span><span style="color: #666666">.</span>session<span style="color: #666666">.</span>close()
<span style="color: #408080; font-style: italic"># тут методы, скрывающие работу с базой</span>
<span style="color: #008000; font-weight: bold">with</span> DBWrapper() <span style="color: #008000; font-weight: bold">as</span> dbw: <span style="color: #408080; font-style: italic"># открываем транзакцию</span>
dbw<span style="color: #666666">.</span>get_some_data()
dbw<span style="color: #666666">.</span>update_some_data(<span style="color: #BA2121">"..."</span>)
</pre></div></span><span style="font-size:110%;display:none" id="fd8f83322e4c11e18fd214feb5b819a0"><pre><font face="courier">from config import DB_URI
from db_session import get_session
class DBWrapper(object):
def __init__(self):
self.session = None
def __enter__(self):
self.session = get_session(DB_URI)
def __exit__(self, exc, *args):
# при выходе из 'with':
if exc is None:
# если все прошло успешно коммитим
# транзакцию и закрываем курсор
self.session.commit()
# если было исключение - откатываем
self.session.close()
# тут методы, скрывающие работу с базой
with DBWrapper() as dbw: # открываем транзакцию
dbw.get_some_data()
dbw.update_some_data("...")</font></pre></span><ul><li>....и не только баз данных</ul><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="fdb912ce2e4c11e18fd214feb5b819a0" objtohide2="fdb9149a2e4c11e18fd214feb5b819a0" >Hightlited/Raw</a><br><span id="fdb912ce2e4c11e18fd214feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">from</span> <span style="color: #0000FF; font-weight: bold">threading</span> <span style="color: #008000; font-weight: bold">import</span> local
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">subprocess</span>
<span style="color: #408080; font-style: italic"># обобщенная транзакция - выполняет набор обратных действий</span>
<span style="color: #408080; font-style: italic"># при возникновении в блоке 'with' не обработанного исключения</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Transaction</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>, parent):
<span style="color: #008000">self</span><span style="color: #666666">.</span>rollback_cmds <span style="color: #666666">=</span> []
<span style="color: #008000">self</span><span style="color: #666666">.</span>set_parent(parent)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">set_parent</span>(<span style="color: #008000">self</span>, parent):
<span style="color: #408080; font-style: italic"># родительская транзакция</span>
<span style="color: #408080; font-style: italic"># если откатывается родительская транзакция, то она автоматом</span>
<span style="color: #408080; font-style: italic"># откатывает и дочерние, даже если они было уже успешно закрыты</span>
<span style="color: #408080; font-style: italic"># если откатывается дочерняя, то родительская может продолжить</span>
<span style="color: #408080; font-style: italic"># исполнение, если код выше по стеку обработает исключение</span>
<span style="color: #008000; font-weight: bold">if</span> parent <span style="color: #AA22FF; font-weight: bold">is</span> <span style="color: #AA22FF; font-weight: bold">not</span> <span style="color: #008000">None</span>:
<span style="color: #008000">self</span><span style="color: #666666">.</span>parent_add <span style="color: #666666">=</span> parent<span style="color: #666666">.</span>add
<span style="color: #008000; font-weight: bold">else</span>:
<span style="color: #008000">self</span><span style="color: #666666">.</span>parent_add <span style="color: #666666">=</span> <span style="color: #008000; font-weight: bold">lambda</span> <span style="color: #666666">*</span>cmd : <span style="color: #008000">None</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__enter__</span>(<span style="color: #008000">self</span>):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">self</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__exit__</span>(<span style="color: #008000">self</span>, exc, <span style="color: #666666">*</span>dt):
<span style="color: #008000; font-weight: bold">if</span> exc <span style="color: #AA22FF; font-weight: bold">is</span> <span style="color: #008000">None</span>:
<span style="color: #008000">self</span><span style="color: #666666">.</span>commit()
<span style="color: #008000; font-weight: bold">else</span>:
<span style="color: #008000">self</span><span style="color: #666666">.</span>rollback()
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">add</span>(<span style="color: #008000">self</span>, cmd):
<span style="color: #008000">self</span><span style="color: #666666">.</span>parent_add(cmd)
<span style="color: #008000">self</span><span style="color: #666666">.</span>transaction<span style="color: #666666">.</span>append(cmd)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">commit</span>(<span style="color: #008000">self</span>):
<span style="color: #008000">self</span><span style="color: #666666">.</span>transaction <span style="color: #666666">=</span> []
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">rollback</span>(<span style="color: #008000">self</span>):
<span style="color: #008000; font-weight: bold">for</span> cmd <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">reversed</span>(<span style="color: #008000">self</span><span style="color: #666666">.</span>transaction):
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #008000">isinstance</span>(cmd, <span style="color: #008000">basestring</span>):
subprocess<span style="color: #666666">.</span>check_call(cmd, shell<span style="color: #666666">=</span><span style="color: #008000">True</span>)
<span style="color: #008000; font-weight: bold">else</span>:
cmd[<span style="color: #666666">0</span>](<span style="color: #666666">*</span>cmd[<span style="color: #666666">1</span>:])
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">AutoInheritedTransaction</span>(<span style="color: #008000">object</span>):
<span style="color: #408080; font-style: italic"># словарь, id потока => [список вложенных транзакций]</span>
<span style="color: #408080; font-style: italic"># позволяет автоматически находить родительскую транзакцию</span>
<span style="color: #408080; font-style: italic"># в том случае, если для каждого потока может быть не более</span>
<span style="color: #408080; font-style: italic"># одной цепи вложенных транзакций </span>
transactions <span style="color: #666666">=</span> local()
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>):
<span style="color: #008000">super</span>(AutoInheritedTransaction, <span style="color: #008000">self</span>)<span style="color: #666666">.</span>__init__(<span style="color: #008000">self</span><span style="color: #666666">.</span>current())
<span style="color: #008000">self</span><span style="color: #666666">.</span>register()
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">register</span>(<span style="color: #008000">self</span>):
<span style="color: #008000">self</span><span style="color: #666666">.</span>transaction<span style="color: #666666">.</span>list <span style="color: #666666">=</span> <span style="color: #008000">getattr</span>(<span style="color: #008000">self</span><span style="color: #666666">.</span>transaction, <span style="color: #BA2121">'list'</span>) <span style="color: #666666">+</span> [<span style="color: #008000">self</span>]
<span style="color: #AA22FF">@classmethod</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">current</span>(cls):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">getattr</span>(<span style="color: #008000">self</span><span style="color: #666666">.</span>transaction, <span style="color: #BA2121">'list'</span>, [<span style="color: #008000">None</span>])[<span style="color: #666666">-1</span>]
used_loop_devs <span style="color: #666666">=</span> []
<span style="color: #008000; font-weight: bold">with</span> AutoInheritedTransaction() <span style="color: #008000; font-weight: bold">as</span> tr:
<span style="color: #408080; font-style: italic"># создаем loop устройство</span>
loop_name <span style="color: #666666">=</span> subprocess<span style="color: #666666">.</span>check_output(<span style="color: #BA2121">"losetup -f --show /tmp/fs_image"</span>)
<span style="color: #408080; font-style: italic"># вызов для его удаления</span>
tr<span style="color: #666666">.</span>add(<span style="color: #BA2121">"losetup -d "</span> <span style="color: #666666">+</span> loop_name)
<span style="color: #408080; font-style: italic"># записываем новое устройство в массив</span>
used_loop_devs<span style="color: #666666">.</span>append(loop_name)
tr<span style="color: #666666">.</span>add(<span style="color: #008000; font-weight: bold">lambda</span> : used_loop_devs<span style="color: #666666">.</span>remove(
used_loop_devs<span style="color: #666666">.</span>index(
loop_name)))
<span style="color: #408080; font-style: italic"># монтируем его</span>
subprocess<span style="color: #666666">.</span>check_output(<span style="color: #BA2121">"mount {0} /mnt/some_dir"</span>)
tr<span style="color: #666666">.</span>add(<span style="color: #BA2121">"umount /mnt/some_dir"</span>)
some_code
</pre></div></span><span style="font-size:110%;display:none" id="fdb9149a2e4c11e18fd214feb5b819a0"><pre><font face="courier">from threading import local
import subprocess
# обобщенная транзакция - выполняет набор обратных действий
# при возникновении в блоке 'with' не обработанного исключения
class Transaction(object):
def __init__(self, parent):
self.rollback_cmds = []
self.set_parent(parent)
def set_parent(self, parent):
# родительская транзакция
# если откатывается родительская транзакция, то она автоматом
# откатывает и дочерние, даже если они было уже успешно закрыты
# если откатывается дочерняя, то родительская может продолжить
# исполнение, если код выше по стеку обработает исключение
if parent is not None:
self.parent_add = parent.add
else:
self.parent_add = lambda *cmd : None
def __enter__(self):
return self
def __exit__(self, exc, *dt):
if exc is None:
self.commit()
else:
self.rollback()
def add(self, cmd):
self.parent_add(cmd)
self.transaction.append(cmd)
def commit(self):
self.transaction = []
def rollback(self):
for cmd in reversed(self.transaction):
if isinstance(cmd, basestring):
subprocess.check_call(cmd, shell=True)
else:
cmd[0](*cmd[1:])
class AutoInheritedTransaction(object):
# словарь, id потока => [список вложенных транзакций]
# позволяет автоматически находить родительскую транзакцию
# в том случае, если для каждого потока может быть не более
# одной цепи вложенных транзакций
transactions = local()
def __init__(self):
super(AutoInheritedTransaction, self).__init__(self.current())
self.register()
def register(self):
self.transaction.list = getattr(self.transaction, 'list') + [self]
@classmethod
def current(cls):
return getattr(self.transaction, 'list', [None])[-1]
used_loop_devs = []
with AutoInheritedTransaction() as tr:
# создаем loop устройство
loop_name = subprocess.check_output("losetup -f --show /tmp/fs_image")
# вызов для его удаления
tr.add("losetup -d " + loop_name)
# записываем новое устройство в массив
used_loop_devs.append(loop_name)
tr.add(lambda : used_loop_devs.remove(
used_loop_devs.index(
loop_name)))
# монтируем его
subprocess.check_output("mount {0} /mnt/some_dir")
tr.add("umount /mnt/some_dir")
some_code</font></pre></span><p style="text-indent:20px"> Эта модель программирования позволяет группировать в одной точке код прямой и обратной операции и избавляет от вложенных <b>try</b>/<b>finally</b>. Также <b>with</b> предоставляет естественный интерфейс для <a href="http://en.wikipedia.org/wiki/Software_Transaction_Memory">STM</a>. <a href="https://bitbucket.org/arigo/cpython-withatomic">cpython-withatomic</a> - один из вариантов STM для руthon с поддержкой <b>with</b>.</p><ul><li>Подавление исключений</ul><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="fdd499fe2e4c11e18fd214feb5b819a0" objtohide2="fdd49bca2e4c11e18fd214feb5b819a0" >Hightlited/Raw</a><br><span id="fdd499fe2e4c11e18fd214feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">supress</span>(<span style="color: #666666">*</span>ex_types):
<span style="color: #408080; font-style: italic"># стоит добавить логирования подавляемого исключения</span>
<span style="color: #008000; font-weight: bold">try</span>:
<span style="color: #008000; font-weight: bold">yield</span>
<span style="color: #008000; font-weight: bold">except</span> <span style="color: #D2413A; font-weight: bold">Exception</span> <span style="color: #008000; font-weight: bold">as</span> x:
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #AA22FF; font-weight: bold">not</span> <span style="color: #008000">isinstance</span>(x, ex_types):
<span style="color: #008000; font-weight: bold">raise</span>
<span style="color: #008000; font-weight: bold">with</span> supress(<span style="color: #D2413A; font-weight: bold">OSError</span>):
os<span style="color: #666666">.</span>unlink(<span style="color: #BA2121">"some_file"</span>)
</pre></div></span><span style="font-size:110%;display:none" id="fdd49bca2e4c11e18fd214feb5b819a0"><pre><font face="courier">def supress(*ex_types):
# стоит добавить логирования подавляемого исключения
try:
yield
except Exception as x:
if not isinstance(x, ex_types):
raise
with supress(OSError):
os.unlink("some_file")</font></pre></span><ul><li>Генерация XML/HTML других структурированных языков.</ul><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="fdf020d42e4c11e18fd214feb5b819a0" objtohide2="fdf022962e4c11e18fd214feb5b819a0" >Hightlited/Raw</a><br><span id="fdf020d42e4c11e18fd214feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">from</span> <span style="color: #0000FF; font-weight: bold">xmlbuilder</span> <span style="color: #008000; font-weight: bold">import</span> XMLBuilder
<span style="color: #408080; font-style: italic"># новый xml документ</span>
x <span style="color: #666666">=</span> XMLBuilder(<span style="color: #BA2121">'root'</span>)
x<span style="color: #666666">.</span>some_tag
x<span style="color: #666666">.</span>some_tag_with_data(<span style="color: #BA2121">'text'</span>, a<span style="color: #666666">=</span><span style="color: #BA2121">'12'</span>)
<span style="color: #408080; font-style: italic"># вложенные теги</span>
<span style="color: #008000; font-weight: bold">with</span> x<span style="color: #666666">.</span>some_tree(a<span style="color: #666666">=</span><span style="color: #BA2121">'1'</span>):
<span style="color: #008000; font-weight: bold">with</span> x<span style="color: #666666">.</span>data:
x<span style="color: #666666">.</span>mmm
x<span style="color: #666666">.</span>node(val<span style="color: #666666">=</span><span style="color: #BA2121">'11'</span>)
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #008000">str</span>(x) <span style="color: #408080; font-style: italic"># <= string object</span>
</pre></div></span><span style="font-size:110%;display:none" id="fdf022962e4c11e18fd214feb5b819a0"><pre><font face="courier">
from xmlbuilder import XMLBuilder
# новый xml документ
x = XMLBuilder('root')
x.some_tag
x.some_tag_with_data('text', a='12')
# вложенные теги
with x.some_tree(a='1'):
with x.data:
x.mmm
x.node(val='11')
print str(x) # <= string object</font></pre></span><p style="text-indent:20px">Получим в итоге:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="fdf086b42e4c11e18fd214feb5b819a0" objtohide2="fdf088302e4c11e18fd214feb5b819a0" >Hightlited/Raw</a><br><span id="fdf086b42e4c11e18fd214feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #BC7A00"><?xml version="1.0" encoding="utf-8" ?></span>
<span style="color: #008000; font-weight: bold"><root></span>
<span style="color: #008000; font-weight: bold"><some_tag</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><some_tag_with_data</span> <span style="color: #7D9029">a=</span><span style="color: #BA2121">"12"</span><span style="color: #008000; font-weight: bold">></span>text<span style="color: #008000; font-weight: bold"></some_tag_with_data></span>
<span style="color: #008000; font-weight: bold"><some_tree</span> <span style="color: #7D9029">a=</span><span style="color: #BA2121">"1"</span><span style="color: #008000; font-weight: bold">></span>
<span style="color: #008000; font-weight: bold"><data></span>
<span style="color: #008000; font-weight: bold"><mmm</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><node</span> <span style="color: #7D9029">val=</span><span style="color: #BA2121">"11"</span> <span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"></data></span>
<span style="color: #008000; font-weight: bold"></some_tree></span>
<span style="color: #008000; font-weight: bold"></root></span>
</pre></div></span><span style="font-size:110%;display:none" id="fdf088302e4c11e18fd214feb5b819a0"><pre><font face="courier"><?xml version="1.0" encoding="utf-8" ?>
<root>
<some_tag />
<some_tag_with_data a="12">text</some_tag_with_data>
<some_tree a="1">
<data>
<mmm />
<node val="11" />
</data>
</some_tree>
</root></font></pre></span><p style="text-indent:20px">Код библиотеки находится на <a href="https://github.com/koder-ua/megarepo/tree/master/xmlbuilder/xmlbuilder">xmlbuilder</a>.</p><ul><li>Трассировка блока в логере (установка <b>sys.settrace</b>)</ul><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="fe0c23c42e4c11e18fd214feb5b819a0" objtohide2="fe0c259a2e4c11e18fd214feb5b819a0" >Hightlited/Raw</a><br><span id="fe0c23c42e4c11e18fd214feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">sys</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">contextlib</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">on_event</span>(fr, evt, data):
<span style="color: #008000; font-weight: bold">print</span> fr, evt, data
<span style="color: #008000; font-weight: bold">return</span> on_event
<span style="color: #AA22FF">@contextlib.contextmanager</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">trace_me</span>():
prev_trace <span style="color: #666666">=</span> sys<span style="color: #666666">.</span>gettrace()
sys<span style="color: #666666">.</span>settrace(on_event)
<span style="color: #008000; font-weight: bold">try</span>:
<span style="color: #008000; font-weight: bold">yield</span>
<span style="color: #008000; font-weight: bold">finally</span>:
sys<span style="color: #666666">.</span>settrace(prev_trace)
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"after finally"</span>
<span style="color: #008000; font-weight: bold">with</span> trace_me():
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"in with"</span>
x <span style="color: #666666">=</span> <span style="color: #666666">1</span>
y <span style="color: #666666">=</span> <span style="color: #666666">2</span>
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"before gettrace"</span>
sys<span style="color: #666666">.</span>gettrace()
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"after gettrace"</span>
</pre></div></span><span style="font-size:110%;display:none" id="fe0c259a2e4c11e18fd214feb5b819a0"><pre><font face="courier">import sys
import contextlib
def on_event(fr, evt, data):
print fr, evt, data
return on_event
@contextlib.contextmanager
def trace_me():
prev_trace = sys.gettrace()
sys.settrace(on_event)
try:
yield
finally:
sys.settrace(prev_trace)
print "after finally"
with trace_me():
print "in with"
x = 1
y = 2
print "before gettrace"
sys.gettrace()
print "after gettrace"</font></pre></span><p style="text-indent:20px">Этот код напечатает:</p><pre><font face="courier"> in with
before gettrace
after gettrace
<frame object at 0x19534f0> call None
<frame object at 0x19534f0> line None
<frame object at 0x19534f0> line None
<frame object at 0x19534f0> line None
<frame object at 0x1943ae0> call None
<frame object at 0x1943ae0> line None
after finally</font></pre><p style="text-indent:20px">Для лучшего понимания трассировки питона - <a href="http://blip.tv/pycon-us-videos-2009-2010-2011/pycon-2011-python-aware-python-4896752">python-aware-python</a>.</p>Ссылки:<br> <a name="PEP-343"><a href="http://www.python.org/dev/peps/pep-0343/">www.python.org/dev/peps/pep-0343</a></a><br> <a name="with"><a href="http://docs.python.org/reference/compound_stmts.html#the-with-statement">docs.python.org/reference/compound_stmts.html#the-with-statement</a></a><br> <a name="xmlbuilder"><a href="https://github.com/koder-ua/megarepo/tree/master/xmlbuilder/xmlbuilder">github.com/koder-ua/megarepo/tree/master/xmlbuilder/xmlbuilder</a></a><br> <a name="mock"><a href="http://www.voidspace.org.uk/python/mock/compare.html#mocking-a-context-manager">www.voidspace.org.uk/python/mock/compare.html#mocking-a-context-manager</a></a><br> <a name="monkey_patching"><a href="http://en.wikipedia.org/wiki/Monkey_patch">en.wikipedia.org/wiki/Monkey_patch</a></a><br> <a name="sqlalchemy"><a href="http://www.sqlalchemy.org/">www.sqlalchemy.org</a></a><br> <a name="fabric"><a href="http://fabfile.org">fabfile.org</a></a><br> <a name="STM"><a href="http://en.wikipedia.org/wiki/Software_Transaction_Memory">en.wikipedia.org/wiki/Software_Transaction_Memory</a></a><br> <a name="cpython-withatomic"><a href="https://bitbucket.org/arigo/cpython-withatomic">bitbucket.org/arigo/cpython-withatomic</a></a><br> <a name="python-aware-python"><a href="http://blip.tv/pycon-us-videos-2009-2010-2011/pycon-2011-python-aware-python-4896752">blip.tv/pycon-us-videos-2009-2010-2011/pycon-2011-python-aware-python-4896752</a></a><br><p style="text-indent:20px">Исходники этого и других постов со скриптами лежат тут - <a href="https://github.com/koder-ua/python-lectures">github.com/koder-ua</a>. При использовании их, пожалуйста, ссылайтесь на <a href="http://koder-ua.blogspot.com/">koder-ua.blogspot.com</a>.</p>
<script type="text/javascript">
function on_hidabble_click()
{
var me = $(this);
var hide_id = me.attr("objtohide");
var controlled_object = $('#' + hide_id);
controlled_object.toggle();
if ( controlled_object.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".hidder").click(on_hidabble_click);
function on_double_hidabble_click()
{
var me = $(this);
var hide_id1 = me.attr("objtohide1");
var hide_id2 = me.attr("objtohide2");
var controlled_object1 = $('#' + hide_id1);
var controlled_object2 = $('#' + hide_id2);
controlled_object1.toggle();
controlled_object2.toggle();
if ( controlled_object1.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".dhidder").click(on_double_hidabble_click);
</script>Anonymoushttp://www.blogger.com/profile/06645078574760734586noreply@blogger.com2tag:blogger.com,1999:blog-1174489715777430743.post-80657228704667792882011-12-07T02:22:00.000+02:002011-12-24T18:32:17.328+02:00Динамические атрибуты в python. Часть 1, теоретическая - "в поисках атрибута"<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<p style="text-indent:20px"> Disclamer: рассмотрены только <b>новые</b> классы - все наследуемые от <b>object</b>. В "старых" классах все работает немного по-другому.</p><p style="text-indent:20px"> Во всей статье классы именуются с заглавной буквы, а экземпляры классов со строчной. <b>A</b> это класс экземпляра <b>a</b>.</p><p style="text-indent:20px"> Вопрос о том что происходит когда python исполняет конструкцию <b>a.b</b> очень важен для понимания многих других тем. Особенно учитывая что имитация атрибутов один из наиболее часто используемых приемов для написания библиотек в pythonic стиле.</p><a name='more'></a><p style="text-indent:20px"> Сначала посмотрим где принципиально может быть '"b"'. Из документации <a href="http://docs.python.org/reference/datamodel.html#customizing-attribute-access">python</a> можно узнать о следующих возможных вариантах.</p><ul><li><b>A.__getattrib__(a, "b")</b><li>'<b>a.__dict__["b"]'' ; пока без '__slots__</b><li>'<b>A.__dict__["b"]'', вместо 'A</b> может быть один из базовых для <b>A</b> классов.<li><b>A.__dict__["b"](a)</b> - свойство (property)<li><b>A.__getattr__(a, "b")</b></ul><p style="text-indent:20px"> <b>A.__getattrib__(a, "b")</b> гарантировано вызывается первым, <b>A.__getattr__(a, "b")</b> последним, а <b>a.__dict__["b"]</b> имеет приоритет над 'A.__dict__["b"]'. Без ответов остаются вопросы о приоритете property и о том, что происходит если на отдельных фазах будут возбужденны исключения.</p><p style="text-indent:20px"> Ответить на эти вопросы можно разными способами, но только чтение исходников гарантированно ответит на них со всеми тонкостями. Все не желающие идти путь самурая по С коду могут смело пролистать вниз ~250 сток до итогового результата и Очень Важной Картинки.</p><p style="text-indent:20px"> Итак скачиваем последнюю версию исходников cpython и будем погружаться. Все начинается в cpython/Python/ceval.c со строки вида <b>TARGET(LOAD_ATTR)</b> (у меня строка 2228). Мы находимся прямо в сердце виртуальной машины сpython, в цикле eval - здесь сpython по очереди исполняет инструкции байтокода. <b>dis.dis</b> говорит нам, что <b>a.b</b> компилируется в две инструкции:</p><span style="font-size:120%"><pre><font face="courier"> >>> import dis
>>> dis.dis(lambda : a.b )
1 0 LOAD_GLOBAL 0 (a)
3 LOAD_ATTR 1 (b)
6 RETURN_VALUE</font></pre></span><p style="text-indent:20px"> Так что инструкция <b>LOAD_ATTR</b> это как раз то, что нам нужно. Тело <b>TARGET(LOAD_ATTR)</b> содержит стандартную возню со стеком, подсчет ссылок и интересующий нас вызов:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ab8b9e7c2e4c11e1b27514feb5b819a0" objtohide2="ab8be7922e4c11e1b27514feb5b819a0" >Hightlited/Raw</a><br><span id="ab8b9e7c2e4c11e1b27514feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">x <span style="color: #666666">=</span> PyObject_GetAttr(v, w); <span style="color: #408080; font-style: italic">//Здесь 'v' - 'a', а 'w' - 'b'.</span>
</pre></div></span><span style="font-size:110%;display:none" id="ab8be7922e4c11e1b27514feb5b819a0"><pre><font face="courier">x = PyObject_GetAttr(v, w); //Здесь 'v' - 'a', а 'w' - 'b'.</font></pre></span><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ab8ccb122e4c11e1b27514feb5b819a0" objtohide2="ab8d20442e4c11e1b27514feb5b819a0" >Hightlited/Raw</a><br><span id="ab8ccb122e4c11e1b27514feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #408080; font-style: italic">//Ф-ция PyObject_GetAttr находится в cpython/objects/object.c</span>
PyObject <span style="color: #666666">*</span>
<span style="color: #0000FF">PyObject_GetAttr</span>(PyObject <span style="color: #666666">*</span>v, PyObject <span style="color: #666666">*</span>name)
{
<span style="color: #408080; font-style: italic">// получаем тип v</span>
PyTypeObject <span style="color: #666666">*</span>tp <span style="color: #666666">=</span> Py_TYPE(v);
<span style="color: #408080; font-style: italic">// бла-бла-бла, проверка типов</span>
<span style="color: #408080; font-style: italic">// получение атрибута</span>
<span style="color: #008000; font-weight: bold">if</span> (tp<span style="color: #666666">-></span>tp_getattro <span style="color: #666666">!=</span> <span style="color: #008000">NULL</span>)
<span style="color: #008000; font-weight: bold">return</span> (<span style="color: #666666">*</span>tp<span style="color: #666666">-></span>tp_getattro)(v, name);
<span style="color: #408080; font-style: italic">// не важный код </span>
}
</pre></div></span><span style="font-size:110%;display:none" id="ab8d20442e4c11e1b27514feb5b819a0"><pre><font face="courier">//Ф-ция PyObject_GetAttr находится в cpython/objects/object.c
PyObject *
PyObject_GetAttr(PyObject *v, PyObject *name)
{
// получаем тип v
PyTypeObject *tp = Py_TYPE(v);
// бла-бла-бла, проверка типов
// получение атрибута
if (tp->tp_getattro != NULL)
return (*tp->tp_getattro)(v, name);
// не важный код
}</font></pre></span><p style="text-indent:20px"> Ок, следующие два участка кода, которые могут ответить на наш вопрос это структура <b>object</b> и метод <b>type.__new__</b>. Первый наследуют почти все классы, а второй может повлиять на структуру новых классов.</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ab8efe5a2e4c11e1b27514feb5b819a0" objtohide2="ab8f04ae2e4c11e1b27514feb5b819a0" >Hightlited/Raw</a><br><span id="ab8efe5a2e4c11e1b27514feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #408080; font-style: italic">//cpython/Objects/typeobject.c:3260</span>
PyTypeObject PyBaseObject_Type <span style="color: #666666">=</span> { <span style="color: #408080; font-style: italic">// эта девушка! (с)</span>
<span style="color: #408080; font-style: italic">// bla-bla-bla</span>
<span style="color: #BA2121">"object"</span>, <span style="color: #408080; font-style: italic">/* tp_name */</span>
<span style="color: #408080; font-style: italic">// bla-bla-bla</span>
<span style="color: #408080; font-style: italic">// __getattribute__</span>
PyObject_GenericGetAttr, <span style="color: #408080; font-style: italic">/* tp_getattro */</span>
<span style="color: #408080; font-style: italic">// ....... </span>
}
<span style="color: #408080; font-style: italic">// PyObject_GenericGetAttr:</span>
<span style="color: #408080; font-style: italic">// cpython/objects/object.c</span>
PyObject <span style="color: #666666">*</span>
PyObject_GenericGetAttr(PyObject <span style="color: #666666">*</span>obj, PyObject <span style="color: #666666">*</span>name)
{
<span style="color: #008000; font-weight: bold">return</span> _PyObject_GenericGetAttrWithDict(obj, name, <span style="color: #008000">NULL</span>);
}
<span style="color: #408080; font-style: italic">// cpython/objects/object.c</span>
<span style="color: #408080; font-style: italic">// первый солидный кусок кода</span>
PyObject <span style="color: #666666">*</span>
_PyObject_GenericGetAttrWithDict(PyObject <span style="color: #666666">*</span>obj, PyObject <span style="color: #666666">*</span>name, PyObject <span style="color: #666666">*</span>dict)
{
PyTypeObject <span style="color: #666666">*</span>tp <span style="color: #666666">=</span> Py_TYPE(obj);
.......
<span style="color: #008000; font-weight: bold">if</span> (<span style="color: #666666">!</span>PyUnicode_Check(name)){
.... <span style="color: #408080; font-style: italic">// исключение</span>
}
<span style="color: #008000; font-weight: bold">else</span>
Py_INCREF(name);
<span style="color: #408080; font-style: italic">// ......</span>
<span style="color: #408080; font-style: italic">// ищем name по mro</span>
descr <span style="color: #666666">=</span> _PyType_Lookup(tp, name);
.....
<span style="color: #008000; font-weight: bold">if</span> (descr <span style="color: #666666">!=</span> <span style="color: #008000">NULL</span>) {
<span style="color: #408080; font-style: italic">// если у descr есть __get__ - перед нами дескриптор </span>
f <span style="color: #666666">=</span> descr<span style="color: #666666">-></span>ob_type<span style="color: #666666">-></span>tp_descr_get;
<span style="color: #408080; font-style: italic">// если дескриптор данных</span>
<span style="color: #008000; font-weight: bold">if</span> (f <span style="color: #666666">!=</span> <span style="color: #008000">NULL</span> <span style="color: #666666">&&</span> PyDescr_IsData(descr)) {
<span style="color: #408080; font-style: italic">// получаем из него значение и выходим</span>
res <span style="color: #666666">=</span> f(descr, obj, (PyObject <span style="color: #666666">*</span>)obj<span style="color: #666666">-></span>ob_type);
Py_DECREF(descr);
<span style="color: #008000; font-weight: bold">goto</span> done;
}
}
<span style="color: #408080; font-style: italic">// dict это параметр для рекурсивного поиска</span>
<span style="color: #408080; font-style: italic">// при вызове из tp_getattro он всегда NULL </span>
<span style="color: #008000; font-weight: bold">if</span> (dict <span style="color: #666666">==</span> <span style="color: #008000">NULL</span>) {
<span style="color: #408080; font-style: italic">/* Inline _PyObject_GetDictPtr */</span>
<span style="color: #408080; font-style: italic">// тут получаем смещение __dict__ внутри объекта</span>
<span style="color: #408080; font-style: italic">// ну и сам __dict__ по нему</span>
dictptr <span style="color: #666666">=</span> (PyObject <span style="color: #666666">**</span>) ((<span style="color: #B00040">char</span> <span style="color: #666666">*</span>)obj <span style="color: #666666">+</span> dictoffset);
dict <span style="color: #666666">=</span> <span style="color: #666666">*</span>dictptr;
}
<span style="color: #408080; font-style: italic">// если dict нашелся</span>
<span style="color: #008000; font-weight: bold">if</span> (dict <span style="color: #666666">!=</span> <span style="color: #008000">NULL</span>) {
Py_INCREF(dict);
<span style="color: #408080; font-style: italic">// получаем a.__dict__['b']</span>
<span style="color: #408080; font-style: italic">// если 'b' not in a.__dict__, то PyDict_GetItem установит</span>
<span style="color: #408080; font-style: italic">// исключение и вернет NULL</span>
<span style="color: #408080; font-style: italic">// PyObject_GetAttr затем заменит это исключение(KeyError) на AttributeError</span>
res <span style="color: #666666">=</span> PyDict_GetItem(dict, name);
<span style="color: #008000; font-weight: bold">if</span> (res <span style="color: #666666">!=</span> <span style="color: #008000">NULL</span>) {
....
<span style="color: #008000; font-weight: bold">goto</span> done;
}
....
}
<span style="color: #408080; font-style: italic">// возвращаемся к дескриптору</span>
<span style="color: #408080; font-style: italic">// если он хоть какой-нить</span>
<span style="color: #408080; font-style: italic">// используем его</span>
<span style="color: #008000; font-weight: bold">if</span> (f <span style="color: #666666">!=</span> <span style="color: #008000">NULL</span>) {
res <span style="color: #666666">=</span> f(descr, obj, (PyObject <span style="color: #666666">*</span>)Py_TYPE(obj));
Py_DECREF(descr);
<span style="color: #008000; font-weight: bold">goto</span> done;
}
<span style="color: #408080; font-style: italic">// если вообще нашли что-то в базовых классах</span>
<span style="color: #008000; font-weight: bold">if</span> (descr <span style="color: #666666">!=</span> <span style="color: #008000">NULL</span>) {
res <span style="color: #666666">=</span> descr;
<span style="color: #408080; font-style: italic">/* descr was already increfed above */</span>
<span style="color: #008000; font-weight: bold">goto</span> done;
}
<span style="color: #408080; font-style: italic">// иначе raise AttributeError</span>
PyErr_Format(PyExc_AttributeError,
<span style="color: #BA2121">"'%.50s' object has no attribute '%U'"</span>,
tp<span style="color: #666666">-></span>tp_name, name);
<span style="color: #A0A000">done:</span>
Py_DECREF(name);
<span style="color: #008000; font-weight: bold">return</span> res;
}
</pre></div></span><span style="font-size:110%;display:none" id="ab8f04ae2e4c11e1b27514feb5b819a0"><pre><font face="courier">//cpython/Objects/typeobject.c:3260
PyTypeObject PyBaseObject_Type = { // эта девушка! (с)
// bla-bla-bla
"object", /* tp_name */
// bla-bla-bla
// __getattribute__
PyObject_GenericGetAttr, /* tp_getattro */
// .......
}
// PyObject_GenericGetAttr:
// cpython/objects/object.c
PyObject *
PyObject_GenericGetAttr(PyObject *obj, PyObject *name)
{
return _PyObject_GenericGetAttrWithDict(obj, name, NULL);
}
// cpython/objects/object.c
// первый солидный кусок кода
PyObject *
_PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, PyObject *dict)
{
PyTypeObject *tp = Py_TYPE(obj);
.......
if (!PyUnicode_Check(name)){
.... // исключение
}
else
Py_INCREF(name);
// ......
// ищем name по mro
descr = _PyType_Lookup(tp, name);
.....
if (descr != NULL) {
// если у descr есть __get__ - перед нами дескриптор
f = descr->ob_type->tp_descr_get;
// если дескриптор данных
if (f != NULL && PyDescr_IsData(descr)) {
// получаем из него значение и выходим
res = f(descr, obj, (PyObject *)obj->ob_type);
Py_DECREF(descr);
goto done;
}
}
// dict это параметр для рекурсивного поиска
// при вызове из tp_getattro он всегда NULL
if (dict == NULL) {
/* Inline _PyObject_GetDictPtr */
// тут получаем смещение __dict__ внутри объекта
// ну и сам __dict__ по нему
dictptr = (PyObject **) ((char *)obj + dictoffset);
dict = *dictptr;
}
// если dict нашелся
if (dict != NULL) {
Py_INCREF(dict);
// получаем a.__dict__['b']
// если 'b' not in a.__dict__, то PyDict_GetItem установит
// исключение и вернет NULL
// PyObject_GetAttr затем заменит это исключение(KeyError) на AttributeError
res = PyDict_GetItem(dict, name);
if (res != NULL) {
....
goto done;
}
....
}
// возвращаемся к дескриптору
// если он хоть какой-нить
// используем его
if (f != NULL) {
res = f(descr, obj, (PyObject *)Py_TYPE(obj));
Py_DECREF(descr);
goto done;
}
// если вообще нашли что-то в базовых классах
if (descr != NULL) {
res = descr;
/* descr was already increfed above */
goto done;
}
// иначе raise AttributeError
PyErr_Format(PyExc_AttributeError,
"'%.50s' object has no attribute '%U'",
tp->tp_name, name);
done:
Py_DECREF(name);
return res;
}</font></pre></span><p style="text-indent:20px">Выясним что такое <b>PyDescr_IsData</b>:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ab8f40542e4c11e1b27514feb5b819a0" objtohide2="ab8f41da2e4c11e1b27514feb5b819a0" >Hightlited/Raw</a><br><span id="ab8f40542e4c11e1b27514feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #BC7A00">//cpython/Include/descrobject.h</span>
<span style="color: #BC7A00">#define PyDescr_IsData(d) (Py_TYPE(d)->tp_descr_set != NULL)</span>
</pre></div></span><span style="font-size:110%;display:none" id="ab8f41da2e4c11e1b27514feb5b819a0"><pre><font face="courier">//cpython/Include/descrobject.h
#define PyDescr_IsData(d) (Py_TYPE(d)->tp_descr_set != NULL)</font></pre></span><p style="text-indent:20px">Переведем на почти python</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ab918f1c2e4c11e1b27514feb5b819a0" objtohide2="ab9190a22e4c11e1b27514feb5b819a0" >Hightlited/Raw</a><br><span id="ab918f1c2e4c11e1b27514feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">PyObject_GenericGetAttr</span>(obj, name):
tp_attr <span style="color: #666666">=</span> <span style="color: #008000">getattr</span>(obj<span style="color: #666666">.</span>__class__, name, NULL)
f <span style="color: #666666">=</span> <span style="color: #008000">getattr</span>(tp_attr, <span style="color: #BA2121">'__get__'</span>, NULL)
<span style="color: #008000; font-weight: bold">if</span> f <span style="color: #666666">!=</span> NULL:
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #008000">hasattr</span>(tp_attr, <span style="color: #BA2121">'__set__'</span>):
<span style="color: #008000; font-weight: bold">return</span> f(obj)
<span style="color: #008000; font-weight: bold">if</span> obj<span style="color: #666666">.</span>have_a_slot(__dict__):
<span style="color: #008000; font-weight: bold">return</span> obj<span style="color: #666666">.</span>__dict__[name]
<span style="color: #008000; font-weight: bold">if</span> f <span style="color: #666666">!=</span> NULL:
<span style="color: #008000; font-weight: bold">return</span> f(obj)
<span style="color: #008000; font-weight: bold">if</span> tp_attr <span style="color: #666666">!=</span> NULL:
<span style="color: #008000; font-weight: bold">return</span> tp_attr
<span style="color: #008000; font-weight: bold">raise</span> <span style="color: #D2413A; font-weight: bold">AttributeError</span>(<span style="color: #BA2121">'......'</span>)
</pre></div></span><span style="font-size:110%;display:none" id="ab9190a22e4c11e1b27514feb5b819a0"><pre><font face="courier">def PyObject_GenericGetAttr(obj, name):
tp_attr = getattr(obj.__class__, name, NULL)
f = getattr(tp_attr, '__get__', NULL)
if f != NULL:
if hasattr(tp_attr, '__set__'):
return f(obj)
if obj.have_a_slot(__dict__):
return obj.__dict__[name]
if f != NULL:
return f(obj)
if tp_attr != NULL:
return tp_attr
raise AttributeError('......')</font></pre></span><p style="text-indent:20px">Ситуация проясняется, переходим к <b>type.__new__</b>.</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ab91d35a2e4c11e1b27514feb5b819a0" objtohide2="ab91d4d62e4c11e1b27514feb5b819a0" >Hightlited/Raw</a><br><span id="ab91d35a2e4c11e1b27514feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #408080; font-style: italic">//cpython/objects/typeobject.c </span>
<span style="color: #008000; font-weight: bold">static</span> PyObject <span style="color: #666666">*</span>
<span style="color: #0000FF">type_new</span>(PyTypeObject <span style="color: #666666">*</span>metatype, PyObject <span style="color: #666666">*</span>args, PyObject <span style="color: #666666">*</span>kwds)
{
<span style="color: #408080; font-style: italic">// ... 453 строки разного, не важного для нас сейчас кода</span>
<span style="color: #408080; font-style: italic">/* Put the proper slots in place */</span>
fixup_slot_dispatchers(type);
<span style="color: #008000; font-weight: bold">return</span> (PyObject <span style="color: #666666">*</span>)type;
}
</pre></div></span><span style="font-size:110%;display:none" id="ab91d4d62e4c11e1b27514feb5b819a0"><pre><font face="courier">//cpython/objects/typeobject.c
static PyObject *
type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
{
// ... 453 строки разного, не важного для нас сейчас кода
/* Put the proper slots in place */
fixup_slot_dispatchers(type);
return (PyObject *)type;
}</font></pre></span><p style="text-indent:20px"> <b>fixup_slot_dispatchers</b> должен настроить слоты создаваемого объекта (в python CAPI слотами называются все поля <b>PyObject</b> <b>tp_getattr</b>/<b>tp_getattro</b> - слоты). <b>fixup_slot_dispatchers</b> использует <b>slotdefs</b> для обновления слотов. Это стуруктура содержит разнообразную информацию о слотах по умолчанию для классов. Cледующая остановка - <b>slot_tp_getattro</b> и <b>slot_tp_getattr_hook</b> - именно они описанны, как значения по умолчанию для <b>tp_getattro</b> в <b>slotdefs</b>.</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ab92f0b42e4c11e1b27514feb5b819a0" objtohide2="ab92f2302e4c11e1b27514feb5b819a0" >Hightlited/Raw</a><br><span id="ab92f0b42e4c11e1b27514feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #408080; font-style: italic">// Используется если __getattribute__ перегружен а __getattr__ - нет.</span>
<span style="color: #408080; font-style: italic">// Изначально fixup_slot_dispatchers всегда помещает в объект</span>
<span style="color: #408080; font-style: italic">// slot_tp_getattr_hook и уже она произведет подмену себя на</span>
<span style="color: #408080; font-style: italic">// slot_tp_getattro, если в объекте нет __getattr__</span>
<span style="color: #408080; font-style: italic">// __getattribute__ есть всегда</span>
<span style="color: #008000; font-weight: bold">static</span> PyObject <span style="color: #666666">*</span>
<span style="color: #0000FF">slot_tp_getattro</span>(PyObject <span style="color: #666666">*</span>self, PyObject <span style="color: #666666">*</span>name)
{
<span style="color: #008000; font-weight: bold">static</span> PyObject <span style="color: #666666">*</span>getattribute_str <span style="color: #666666">=</span> <span style="color: #008000">NULL</span>;
<span style="color: #008000; font-weight: bold">return</span> call_method(self, <span style="color: #BA2121">"__getattribute__"</span>, <span style="color: #666666">&</span>getattribute_str,
<span style="color: #BA2121">"(O)"</span>, name);
}
<span style="color: #008000; font-weight: bold">static</span> PyObject <span style="color: #666666">*</span>
<span style="color: #0000FF">slot_tp_getattr_hook</span>(PyObject <span style="color: #666666">*</span>self, PyObject <span style="color: #666666">*</span>name)
{
PyTypeObject <span style="color: #666666">*</span>tp <span style="color: #666666">=</span> Py_TYPE(self);
.....
<span style="color: #408080; font-style: italic">// всякие проверки</span>
.....
<span style="color: #408080; font-style: italic">// если нет __getattr__ вызываем slot_tp_getattro</span>
<span style="color: #408080; font-style: italic">// и подменяем слот на slot_tp_getattro для будущих вызовов</span>
getattr <span style="color: #666666">=</span> _PyType_Lookup(tp, getattr_str);
<span style="color: #008000; font-weight: bold">if</span> (getattr <span style="color: #666666">==</span> <span style="color: #008000">NULL</span>) {
<span style="color: #408080; font-style: italic">/* No __getattr__ hook: use a simpler dispatcher */</span>
tp<span style="color: #666666">-></span>tp_getattro <span style="color: #666666">=</span> slot_tp_getattro;
<span style="color: #008000; font-weight: bold">return</span> slot_tp_getattro(self, name);
}
getattribute <span style="color: #666666">=</span> _PyType_Lookup(tp, getattribute_str);
<span style="color: #408080; font-style: italic">// если __getattribute__ не определен, или если он</span>
<span style="color: #408080; font-style: italic">// ссылается на PyObject_GenericGetAttr</span>
<span style="color: #408080; font-style: italic">// маленькая оптимизация</span>
<span style="color: #008000; font-weight: bold">if</span> (getattribute <span style="color: #666666">==</span> <span style="color: #008000">NULL</span> <span style="color: #666666">||</span>
(Py_TYPE(getattribute) <span style="color: #666666">==</span> <span style="color: #666666">&</span>PyWrapperDescr_Type <span style="color: #666666">&&</span>
((PyWrapperDescrObject <span style="color: #666666">*</span>)getattribute)<span style="color: #666666">-></span>d_wrapped <span style="color: #666666">==</span>
(<span style="color: #B00040">void</span> <span style="color: #666666">*</span>)PyObject_GenericGetAttr))
<span style="color: #408080; font-style: italic">// ищем атрибут, используя PyObject_GenericGetAttr</span>
res <span style="color: #666666">=</span> PyObject_GenericGetAttr(self, name);
<span style="color: #008000; font-weight: bold">else</span> {
<span style="color: #408080; font-style: italic">// есть __getattribute__, оличный от PyObject_GenericGetAttr</span>
Py_INCREF(getattribute);
res <span style="color: #666666">=</span> call_attribute(self, getattribute, name);
Py_DECREF(getattribute);
}
<span style="color: #408080; font-style: italic">// если ничего не нашли и есть исключение PyExc_AttributeError - очищаем исключение</span>
<span style="color: #408080; font-style: italic">// и вызываем self.__getattr__</span>
<span style="color: #008000; font-weight: bold">if</span> (res <span style="color: #666666">==</span> <span style="color: #008000">NULL</span> <span style="color: #666666">&&</span> PyErr_ExceptionMatches(PyExc_AttributeError)) {
PyErr_Clear();
res <span style="color: #666666">=</span> call_attribute(self, getattr, name);
}
Py_DECREF(getattr);
<span style="color: #008000; font-weight: bold">return</span> res;
}
</pre></div></span><span style="font-size:110%;display:none" id="ab92f2302e4c11e1b27514feb5b819a0"><pre><font face="courier">// Используется если __getattribute__ перегружен а __getattr__ - нет.
// Изначально fixup_slot_dispatchers всегда помещает в объект
// slot_tp_getattr_hook и уже она произведет подмену себя на
// slot_tp_getattro, если в объекте нет __getattr__
// __getattribute__ есть всегда
static PyObject *
slot_tp_getattro(PyObject *self, PyObject *name)
{
static PyObject *getattribute_str = NULL;
return call_method(self, "__getattribute__", &getattribute_str,
"(O)", name);
}
static PyObject *
slot_tp_getattr_hook(PyObject *self, PyObject *name)
{
PyTypeObject *tp = Py_TYPE(self);
.....
// всякие проверки
.....
// если нет __getattr__ вызываем slot_tp_getattro
// и подменяем слот на slot_tp_getattro для будущих вызовов
getattr = _PyType_Lookup(tp, getattr_str);
if (getattr == NULL) {
/* No __getattr__ hook: use a simpler dispatcher */
tp->tp_getattro = slot_tp_getattro;
return slot_tp_getattro(self, name);
}
getattribute = _PyType_Lookup(tp, getattribute_str);
// если __getattribute__ не определен, или если он
// ссылается на PyObject_GenericGetAttr
// маленькая оптимизация
if (getattribute == NULL ||
(Py_TYPE(getattribute) == &PyWrapperDescr_Type &&
((PyWrapperDescrObject *)getattribute)->d_wrapped ==
(void *)PyObject_GenericGetAttr))
// ищем атрибут, используя PyObject_GenericGetAttr
res = PyObject_GenericGetAttr(self, name);
else {
// есть __getattribute__, оличный от PyObject_GenericGetAttr
Py_INCREF(getattribute);
res = call_attribute(self, getattribute, name);
Py_DECREF(getattribute);
}
// если ничего не нашли и есть исключение PyExc_AttributeError - очищаем исключение
// и вызываем self.__getattr__
if (res == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) {
PyErr_Clear();
res = call_attribute(self, getattr, name);
}
Py_DECREF(getattr);
return res;
}</font></pre></span><p style="text-indent:20px">Теперь можно восстановить всю картину: <b>a.b</b> => Если:</p><ul><li><b>__getattribute__</b> был перегружен, то вызывается <b>A.__getattribute__(a, "b")</b>.<li>есть свойство-данное - вызывается оно <b>A.b.__get__(a)</b><li><b>b</b> присутствует в <b>a.__dict__</b>, возвращаем <b>a.__dict__["b"]</b><li>есть какое-нить свойство - вызывается оно <b>A.b.__get__(a)</b><li><b>b</b> найдено в <b>A.__dict__</b> или в <b>__dict__</b> одного из базовых для <b>A</b> классов, => <b>X.__dict__["b"]</b><li>Если не перегружен <b>__getattr__</b> - исключение/результат возвращаются<li>Если перегружен <b>__getattr__</b> и ничего не найдено и тип исключения <b>AttributeError</b> - вызывается <b>A.__getattr__(a, "b")</b><li>исключение/результат возвращаются</ul><p style="text-indent:20px"><a href="https://github.com/koder-ua/python-lectures/raw/master/attribute.jpg">Очень Важная Картинка</a></p><br><img src="https://github.com/koder-ua/python-lectures/raw/master/attribute.jpg" width="700" /><br><p style="text-indent:20px">Нетривиальный результат:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ab9364222e4c11e1b27514feb5b819a0" objtohide2="ab9365bc2e4c11e1b27514feb5b819a0" >Hightlited/Raw</a><br><span id="ab9364222e4c11e1b27514feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">X</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__getattribute__</span>(<span style="color: #008000">self</span>, name):
<span style="color: #008000; font-weight: bold">raise</span> <span style="color: #D2413A; font-weight: bold">AttributeError</span>(name)
f <span style="color: #666666">=</span> <span style="color: #666666">1</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__getattr__</span>(<span style="color: #008000">self</span>, name):
<span style="color: #008000; font-weight: bold">return</span> name
</pre></div></span><span style="font-size:110%;display:none" id="ab9365bc2e4c11e1b27514feb5b819a0"><pre><font face="courier">class X(object):
def __getattribute__(self, name):
raise AttributeError(name)
f = 1
def __getattr__(self, name):
return name</font></pre></span><p style="text-indent:20px"> В таком классе поле <b>f</b> будет всегда игнорироваться, но если <b>__getattribute__</b> убрать - то все вернется на свои места и '<b>X().f</b>' будет равно 1, а '<b>X().d == d</b>'.</p><p style="text-indent:20px"> После просмотра кода возникает вопрос - какой ценой достается эта гибкость? Замеряем скорость доступа к разным вариантам атрибутов:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ab94150c2e4c11e1b27514feb5b819a0" objtohide2="ab9416a62e4c11e1b27514feb5b819a0" >Hightlited/Raw</a><br><span id="ab94150c2e4c11e1b27514feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #408080; font-style: italic"># глобальная переменная</span>
global_var <span style="color: #666666">=</span> <span style="color: #666666">1</span>
<span style="color: #408080; font-style: italic"># пустая функция</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">empty_func</span>():
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #408080; font-style: italic"># доступ к глобальной переменной из функции</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">global_var_from_func</span>():
global_var
<span style="color: #408080; font-style: italic"># доступ к локальной переменной из функции</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">local_var_from_func</span>():
local_var <span style="color: #666666">=</span> <span style="color: #666666">1</span>
local_var
<span style="color: #408080; font-style: italic"># сложение целых</span>
a <span style="color: #666666">=</span> <span style="color: #666666">1</span>
<span style="color: #408080; font-style: italic"># int + int</span>
a <span style="color: #666666">+</span> a
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">A1</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__getattribute__</span>(<span style="color: #008000">self</span>, name):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">None</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">A2</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">get_b</span>(<span style="color: #008000">self</span>):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">None</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">set_b</span>(<span style="color: #008000">self</span>, val):
<span style="color: #008000; font-weight: bold">pass</span>
b <span style="color: #666666">=</span> <span style="color: #008000">property</span>(get_b, set_b)
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">A3</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>):
<span style="color: #008000">self</span><span style="color: #666666">.</span>b <span style="color: #666666">=</span> <span style="color: #008000">None</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">A4</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">get_b</span>(<span style="color: #008000">self</span>):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">None</span>
b <span style="color: #666666">=</span> <span style="color: #008000">property</span>(get_b)
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">A5</span>(<span style="color: #008000">object</span>):
b <span style="color: #666666">=</span> <span style="color: #008000">None</span>
<span style="color: #408080; font-style: italic"># создаем иерархию со 128ю уровнями вложенности наследования</span>
<span style="color: #408080; font-style: italic"># атрибут 'b' будет у "самого базового" класса</span>
<span style="color: #408080; font-style: italic"># в использовать для доступа будет экзампляр "самого дочернего"</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">A6</span>(<span style="color: #008000">object</span>):
b <span style="color: #666666">=</span> <span style="color: #008000">None</span>
<span style="color: #008000; font-weight: bold">for</span> i <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">range</span>(<span style="color: #666666">127</span>):
A6 <span style="color: #666666">=</span> <span style="color: #008000">type</span>(<span style="color: #BA2121">'A6'</span>, (A6,), {})
a6 <span style="color: #666666">=</span> A6()
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">A7</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__getattr__</span>(<span style="color: #008000">self</span>, name):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">None</span>
</pre></div></span><span style="font-size:110%;display:none" id="ab9416a62e4c11e1b27514feb5b819a0"><pre><font face="courier"># глобальная переменная
global_var = 1
# пустая функция
def empty_func():
pass
# доступ к глобальной переменной из функции
def global_var_from_func():
global_var
# доступ к локальной переменной из функции
def local_var_from_func():
local_var = 1
local_var
# сложение целых
a = 1
# int + int
a + a
class A1(object):
def __getattribute__(self, name):
return None
class A2(object):
def get_b(self):
return None
def set_b(self, val):
pass
b = property(get_b, set_b)
class A3(object):
def __init__(self):
self.b = None
class A4(object):
def get_b(self):
return None
b = property(get_b)
class A5(object):
b = None
# создаем иерархию со 128ю уровнями вложенности наследования
# атрибут 'b' будет у "самого базового" класса
# в использовать для доступа будет экзампляр "самого дочернего"
class A6(object):
b = None
for i in range(127):
A6 = type('A6', (A6,), {})
a6 = A6()
class A7(object):
def __getattr__(self, name):
return None</font></pre></span><p style="text-indent:20px">Время замерянно на Core i7-2630QM, 2Ghz. Ubuntu 11.10, python 2.7.2, 64bit</p><pre><font face="courier"> +----------------------------------------------------------------------------------------+
| Операция | время нс | diff % | относительное | такты CPU |
| показывающая где берется | | | время | |
| aX.b | | | time/(a.b time)| |
|----------------------------------------------------------------------------------------|
| Global var access | 7.4 | 0 | 0.2 | 15 |
| Empty func call | 66.2 | 2 | 2.2 | 132 |
| Global var from func | 9.3 | 0 | 0.3 | 19 |
| Local var from func | 7.4 | 0 | 0.3 | 15 |
| int + int | 17.5 | 1 | 0.6 | 35 |
| A1.__getattribute__(a, 'b') | 178.9 | 1 | 6.0 | 358 |
| A2.b.__get__(a) data property | 139.4 | 0 | 4.7 | 279 |
| a3.__dict__['b'] | 30.0 | 1 | 1.0 | 60 |
| A4.b.__get__(a) property | 140.0 | 1 | 4.7 | 280 |
| A5.__dict__['b'] | 21.6 | 0 | 0.7 | 43 |
| A6.__dict__['b'] | 21.6 | 1 | 0.7 | 43 |
| A7.__getattr__(a, 'b') | 517.0 | 4 | 17.2 | 1034 |
| a.b.b | 54.1 | 2 | 1.8 | 108 |
| a.b.b.b.b | 103.5 | 2 | 3.4 | 207 |
| a....b (128) | 3428.5 | 1 | 114.0 | 6857 |
+----------------------------------------------------------------------------------------+</font></pre><p style="text-indent:20px">diff - максимальный разброс времени в % на 10 измерениях, при выброшенных 2 крайних значениях.</p><p style="text-indent:20px"> В принципе все результаты, кроме странных "тормозов" <b>__getattr__</b>, ожидаемы. Одинаковое время для <b>A5.__dict__["b"]</b> и <b>A6.__dict__["b"]</b> связанно с кешированием привязки имени классового атрибута к классу, так что поиск по всей иерархии не производится. Тестирующий скрипт лежит <a href="https://github.com/koder-ua/python-lectures/blob/master/attribute_access.py">тут</a>. Тестирование производительности отдельная достаточно большая тема, которой я собираюсь посвятить отдельный пост в ближайшем будущем. Получить указанные тайминги и погрешность на уровне < 5% простым использованием <b>timeit.timeit</b> не получится.</p>Ссылки:<br> <a name="тут"><a href="https://github.com/koder-ua/python-lectures/blob/master/attribute_access.py">github.com/koder-ua/python-lectures/blob/master/attribute_access.py</a></a><br> <a name="Очень_Важная_Картинка"><a href="https://github.com/koder-ua/python-lectures/raw/master/attribute.jpg">github.com/koder-ua/python-lectures/raw/master/attribute.jpg</a></a><br> <a name="python"><a href="http://docs.python.org/reference/datamodel.html#customizing-attribute-access">docs.python.org/reference/datamodel.html#customizing-attribute-access</a></a><br><p style="text-indent:20px">Исходники этого и других постов со скриптами лежат тут - <a href="https://github.com/koder-ua/python-lectures">github.com/koder-ua</a>. При использовании их, пожалуйста, ссылайтесь на <a href="http://koder-ua.blogspot.com/">koder-ua.blogspot.com</a>.</p>
<script type="text/javascript">
function on_hidabble_click()
{
var me = $(this);
var hide_id = me.attr("objtohide");
var controlled_object = $('#' + hide_id);
controlled_object.toggle();
if ( controlled_object.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".hidder").click(on_hidabble_click);
function on_double_hidabble_click()
{
var me = $(this);
var hide_id1 = me.attr("objtohide1");
var hide_id2 = me.attr("objtohide2");
var controlled_object1 = $('#' + hide_id1);
var controlled_object2 = $('#' + hide_id2);
controlled_object1.toggle();
controlled_object2.toggle();
if ( controlled_object1.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".dhidder").click(on_double_hidabble_click);
</script>Anonymoushttp://www.blogger.com/profile/06645078574760734586noreply@blogger.com0tag:blogger.com,1999:blog-1174489715777430743.post-64822579292544227062011-12-06T00:40:00.000+02:002012-09-28T22:50:35.057+03:00Метаклассы в python 2.X с примерами и полным разоблачением<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<br><h2>Теория, часть 1. Метаклассы</h2><p style="text-indent:20px"> Все начинается с объявления класса:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ea8a9f4e2e4a11e1ab2114feb5b819a0" objtohide2="ea8ad82e2e4a11e1ab2114feb5b819a0" >Hightlited/Raw</a><br><span id="ea8a9f4e2e4a11e1ab2114feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">A</span>(<span style="color: #008000">object</span>):
field <span style="color: #666666">=</span> <span style="color: #666666">12</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">method</span>(<span style="color: #008000">self</span>, param):
<span style="color: #008000; font-weight: bold">return</span> param <span style="color: #666666">+</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>field
</pre></div></span><span style="font-size:110%;display:none" id="ea8ad82e2e4a11e1ab2114feb5b819a0"><pre><font face="courier">class A(object):
field = 12
def method(self, param):
return param + self.field</font></pre></span><p style="text-indent:20px"> Имеющие опыт программирования на компилируемых языках могут увидеть здесь
декларативную конструкцию, но это только обман зрения.
В python всего две декларативные конструкции - объявление кодировки файла и
импорт синтаксических конструкций "из будущего". Все остальное - исполняемое.
Написанное объявление это синтаксический сахар для следующего:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ea8bb1402e4a11e1ab2114feb5b819a0" objtohide2="ea8bf9fc2e4a11e1ab2114feb5b819a0" >Hightlited/Raw</a><br><span id="ea8bb1402e4a11e1ab2114feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">txt_code <span style="color: #666666">=</span> <span style="color: #BA2121">"""</span>
<span style="color: #BA2121">field = 12</span>
<span style="color: #BA2121">def method(self, param):</span>
<span style="color: #BA2121"> return param + self.field</span>
<span style="color: #BA2121">"""</span>
class_body <span style="color: #666666">=</span> {}
compiled_code <span style="color: #666666">=</span> <span style="color: #008000">compile</span>(txt_code, __file__, <span style="color: #BA2121">"exec"</span>)
<span style="color: #008000">eval</span>(compiled_code, <span style="color: #008000">globals</span>(), class_body)
A <span style="color: #666666">=</span> <span style="color: #008000">type</span>(<span style="color: #BA2121">"A"</span>, (<span style="color: #008000">object</span>,), class_body)
</pre></div></span><span style="font-size:110%;display:none" id="ea8bf9fc2e4a11e1ab2114feb5b819a0"><pre><font face="courier">txt_code = """
field = 12
def method(self, param):
return param + self.field
"""
class_body = {}
compiled_code = compile(txt_code, __file__, "exec")
eval(compiled_code, globals(), class_body)
A = type("A", (object,), class_body)</font></pre></span><a name='more'></a><p style="text-indent:20px"> Оба этих способа создать класс <b>A</b> совершенно эквивалентны.
Окончательно убедиться в том, что объявление класса исполнимо
можно посмотрев вот на это:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ea8ccfee2e4a11e1ab2114feb5b819a0" objtohide2="ea8cd6102e4a11e1ab2114feb5b819a0" >Hightlited/Raw</a><br><span id="ea8ccfee2e4a11e1ab2114feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">this_is_not_cplusplus</span>(some_base_class, num_methods):
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Result</span>(some_base_class):
x <span style="color: #666666">=</span> some_base_class()
<span style="color: #008000; font-weight: bold">for</span> pos <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">range</span>(num_methods):
<span style="color: #408080; font-style: italic"># добавим функцию в locals - она попадает в тело </span>
<span style="color: #408080; font-style: italic"># класса Result и станет его методом</span>
<span style="color: #008000">locals</span>()[<span style="color: #BA2121">'method_'</span> <span style="color: #666666">+</span> <span style="color: #008000">str</span>(pos)] <span style="color: #666666">=</span> \
<span style="color: #008000; font-weight: bold">lambda</span> <span style="color: #008000">self</span>, m : m <span style="color: #666666">+</span> num_methods
<span style="color: #008000; font-weight: bold">return</span> Result
class_with_10_methods <span style="color: #666666">=</span> this_is_not_cplusplus(<span style="color: #008000">object</span>, <span style="color: #666666">10</span>)
class_with_20_methods <span style="color: #666666">=</span> this_is_not_cplusplus(
class_with_10_methods, <span style="color: #666666">20</span>)
<span style="color: #008000; font-weight: bold">print</span> class_with_10_methods()<span style="color: #666666">.</span>method_3(<span style="color: #666666">2</span>) <span style="color: #408080; font-style: italic"># напечатает 12</span>
<span style="color: #008000; font-weight: bold">print</span> class_with_20_methods()<span style="color: #666666">.</span>method_13(<span style="color: #666666">2</span>) <span style="color: #408080; font-style: italic"># напечатает 22</span>
</pre></div></span><span style="font-size:110%;display:none" id="ea8cd6102e4a11e1ab2114feb5b819a0"><pre><font face="courier">def this_is_not_cplusplus(some_base_class, num_methods):
class Result(some_base_class):
x = some_base_class()
for pos in range(num_methods):
# добавим функцию в locals - она попадает в тело
# класса Result и станет его методом
locals()['method_' + str(pos)] = \
lambda self, m : m + num_methods
return Result
class_with_10_methods = this_is_not_cplusplus(object, 10)
class_with_20_methods = this_is_not_cplusplus(
class_with_10_methods, 20)
print class_with_10_methods().method_3(2) # напечатает 12
print class_with_20_methods().method_13(2) # напечатает 22</font></pre></span><p style="text-indent:20px"> Функция <b>this_is_not_cplusplus</b> создает новый класс каждый
раз, когда мы ее вызываем, используя переданный тип в качестве базового и
создавая в новом классе такое количество методов, какое мы запросили.</p><p style="text-indent:20px"> Но вернемся ко второму блоку кода. В нем нас особенно интересует последняя
строка:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ea8d199a2e4a11e1ab2114feb5b819a0" objtohide2="ea8d1b2a2e4a11e1ab2114feb5b819a0" >Hightlited/Raw</a><br><span id="ea8d199a2e4a11e1ab2114feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">A <span style="color: #666666">=</span> <span style="color: #008000">type</span>(<span style="color: #BA2121">"A"</span>, (<span style="color: #008000">object</span>,), class_body)
</pre></div></span><span style="font-size:110%;display:none" id="ea8d1b2a2e4a11e1ab2114feb5b819a0"><pre><font face="courier">A = type("A", (object,), class_body)</font></pre></span><p style="text-indent:20px"> Она похожа на инстанцирование типа <b>type</b>, и это на самом деле так.
Т.е. класс <b>A</b> это экзампляр типа <b>type</b>. В python есть некая супер иерархия
типов - снизу находятся экземпляры обычных классов, потом обычные классы
(унаследованные от <b>object</b> или он ничего) и на самом верху <b>type</b> (который,
кстати, экземпляр самого себя) и все, что от него унаследовано - метаклассы.
Инстанцируя метаклассы мы получаем обычные классы - метаклассы являются типами
классов.</p><span style="font-size:140%"><pre><font face="courier"> type --------> MetaB --> MetaC
| | |
object --> A object ---> B ------> C
| | |
a b c</font></pre></span><p style="text-indent:20px"> Здесь вертикальная черта - инстанцирование, а горизинтальная стрелка
- наследование.</p><p style="text-indent:20px"> Как и объекты, классы хранят свои метаклассы в атрибуте
<b>__class__</b>. Так же, как классы управляют жизненным циклом объектов и их
поведением, метаклассы управляют жизненным циклом и поведением классов.</p><p style="text-indent:20px"> Начнем с простого:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ea8d5e6e2e4a11e1ab2114feb5b819a0" objtohide2="ea8d5fe02e4a11e1ab2114feb5b819a0" >Hightlited/Raw</a><br><span id="ea8d5e6e2e4a11e1ab2114feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">MyMeta</span>(<span style="color: #008000">type</span>):
<span style="color: #008000; font-weight: bold">pass</span>
</pre></div></span><span style="font-size:110%;display:none" id="ea8d5fe02e4a11e1ab2114feb5b819a0"><pre><font face="courier">class MyMeta(type):
pass</font></pre></span><p style="text-indent:20px"> <b>MyMeta</b> простейший метакласс. Если в теле класса присвоить его полю
<b>__metaclass__</b> то он будет использован для конструирования класса и станет его
типом:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ea8da4322e4a11e1ab2114feb5b819a0" objtohide2="ea8da6582e4a11e1ab2114feb5b819a0" >Hightlited/Raw</a><br><span id="ea8da4322e4a11e1ab2114feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">A</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">B</span>(A):
__metaclass__ <span style="color: #666666">=</span> MyMeta
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">C</span>(B):
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"type(A) ="</span>, <span style="color: #008000">type</span>(A)
<span style="color: #408080; font-style: italic"># напечатает "type(A) = <type 'type'>"</span>
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"type(B) ="</span>, <span style="color: #008000">type</span>(B)
<span style="color: #408080; font-style: italic"># напечатает "type(B) = <class '__main__.MyMeta'>"</span>
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"type(C) ="</span>, <span style="color: #008000">type</span>(C)
<span style="color: #408080; font-style: italic"># напечатает "type(C) = <class '__main__.MyMeta'>"</span>
</pre></div></span><span style="font-size:110%;display:none" id="ea8da6582e4a11e1ab2114feb5b819a0"><pre><font face="courier">class A(object):
pass
class B(A):
__metaclass__ = MyMeta
class C(B):
pass
print "type(A) =", type(A)
# напечатает "type(A) = <type 'type'>"
print "type(B) =", type(B)
# напечатает "type(B) = <class '__main__.MyMeta'>"
print "type(C) =", type(C)
# напечатает "type(C) = <class '__main__.MyMeta'>"</font></pre></span><p style="text-indent:20px"> В итоге <b>B</b> и <b>C</b> имеют тип <b>MyMeta</b>. Т.е. метаклассы наследуются.
Я напомню, что инстанцирование класса в питоне это тоже синтаксический сахар:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ea8dcf8e2e4a11e1ab2114feb5b819a0" objtohide2="ea8dd10a2e4a11e1ab2114feb5b819a0" >Hightlited/Raw</a><br><span id="ea8dcf8e2e4a11e1ab2114feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">c <span style="color: #666666">=</span> SomeClass(<span style="color: #666666">1</span>, x<span style="color: #666666">=12</span>)
</pre></div></span><span style="font-size:110%;display:none" id="ea8dd10a2e4a11e1ab2114feb5b819a0"><pre><font face="courier">c = SomeClass(1, x=12)</font></pre></span><p style="text-indent:20px">=></p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ea8dfaf42e4a11e1ab2114feb5b819a0" objtohide2="ea8dfc662e4a11e1ab2114feb5b819a0" >Hightlited/Raw</a><br><span id="ea8dfaf42e4a11e1ab2114feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">c <span style="color: #666666">=</span> SomeClass<span style="color: #666666">.</span>__new__(SomeClass, <span style="color: #666666">1</span>, x<span style="color: #666666">=12</span>)
SomeClass<span style="color: #666666">.</span>__init__(c, <span style="color: #666666">1</span>, x<span style="color: #666666">=12</span>)
</pre></div></span><span style="font-size:110%;display:none" id="ea8dfc662e4a11e1ab2114feb5b819a0"><pre><font face="courier">c = SomeClass.__new__(SomeClass, 1, x=12)
SomeClass.__init__(c, 1, x=12)</font></pre></span><p style="text-indent:20px"> <b>__new__</b> создает новый объект класса, а <b>__init__</b> инициализирует его.
Здесь полная аналогия с С++ методами <b>new</b> и конструктором (чаще всего <b>__new__</b>
наследуется от <b>object</b>). Рассмотрим эти методы на примере:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ea8e67be2e4a11e1ab2114feb5b819a0" objtohide2="ea8e69302e4a11e1ab2114feb5b819a0" >Hightlited/Raw</a><br><span id="ea8e67be2e4a11e1ab2114feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">MyMeta2</span>(<span style="color: #008000">type</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__new__</span>(cls, name, bases, class_dict):
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"__new__({0}, {1}, {2}, {3})"</span><span style="color: #666666">.</span>format(
cls, name, bases, class_dict)
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">super</span>(MyMeta2, cls)<span style="color: #666666">.</span>__new__(cls,
name,
bases,
class_dict)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>, name, bases, class_dict):
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"__init__({0}, {1}, {2}, {3})"</span><span style="color: #666666">.</span>format(
cls, name, bases, class_dict)
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">super</span>(MyMeta2, <span style="color: #008000">self</span>)<span style="color: #666666">.</span>__init__(<span style="color: #008000">self</span>,
name,
bases,
class_dict)
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">D</span>(<span style="color: #008000">object</span>):
__metclass__ <span style="color: #666666">=</span> MyMeta2
x <span style="color: #666666">=</span> <span style="color: #666666">12</span>
</pre></div></span><span style="font-size:110%;display:none" id="ea8e69302e4a11e1ab2114feb5b819a0"><pre><font face="courier">class MyMeta2(type):
def __new__(cls, name, bases, class_dict):
print "__new__({0}, {1}, {2}, {3})".format(
cls, name, bases, class_dict)
return super(MyMeta2, cls).__new__(cls,
name,
bases,
class_dict)
def __init__(self, name, bases, class_dict):
print "__init__({0}, {1}, {2}, {3})".format(
cls, name, bases, class_dict)
return super(MyMeta2, self).__init__(self,
name,
bases,
class_dict)
class D(object):
__metclass__ = MyMeta2
x = 12</font></pre></span><p style="text-indent:20px">Эта конструкция напечатает ожидаемые строки:</p><span style="font-size:120%"><pre><font face="courier"> __new__(<class '__main__.MyMeta2'>, D, (<type 'object'>,),
{'x': 12, '__module__': '__main__',
'__metaclass__': <class '__main__.MyMeta2'>})
__init__(<class '__main__.D'>, D, (<type 'object'>,),
{'x': 12, '__module__': '__main__',
'__metaclass__': <class '__main__.MyMeta2'>})</font></pre></span><br><h2>Практика</h2><p style="text-indent:20px"> Что мы получили по итогу - с помощью метаклассов можно:</p><ul><li>Изменять типы создаваемых классов<li>Автоматически вызывать некоторый код при каждом прямом или непрямом наследовании данного класса<li>Менять параметры нового класса - метакласс может подменить в методе <b>__new__</b> переменные <b>name</b>, <b>bases</b>, <b>class_dict</b> до их передачи в
<b>type.__new__</b> или повлиять на полученный класс.</ul><p style="text-indent:20px"> Альтернативные методы некоторых возможностей метаклассов реализуют
декораторы, но:</p><ul><li>декораторы классов исполняются уже после создания класса, и некоторые модификации класса в них более громоздки (аналогично методу
<b>__init__</b> метаклассов). Также метакласс может влиять на список базовых
классов<li>декораторы функций требуют применения к каждому методу в отдельности, в то время как метаклассы позволяют применять функциональность
ко всем методам объекта сразу.</ul><p style="text-indent:20px"> Чаще всего в метаклассе перегружается метод <b>__new__</b> , позволяющий менять
параметры класса до создания. Читая все ниже написаное нужно помнить, что
достичь того-же эффекта часто можно и без метаклассов, но придется писать больше
однообразного кода. Метаклассы, как и многие возможности python, позволяют
писать удобные для использования библиотеки, но менее полезны для написания
конечного продукта.</p><p style="text-indent:20px"> Итак какие практические результаты мы можем извлечь из этого метасчастья?
Разобьем примеры использования по списку возможностей метаклассов:</p><br><h3>Изменять типы создаваемых классов</h3><p style="text-indent:20px"> Большое количество синтаксических конструкций python преобразуются в вызовы
специальных методов класса используемого объекта, с передачей объекта параметром
в метод, например:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ea8eef2c2e4a11e1ab2114feb5b819a0" objtohide2="ea8ef0a82e4a11e1ab2114feb5b819a0" >Hightlited/Raw</a><br><span id="ea8eef2c2e4a11e1ab2114feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">a <span style="color: #666666">+</span> b <span style="color: #408080; font-style: italic"># => a.__class__.__add__(a, b)</span>
a<span style="color: #666666">.</span>c <span style="color: #408080; font-style: italic"># => a.__class__.__getattr__(a, 'c')</span>
<span style="color: #408080; font-style: italic"># кроме этого a.__class__.__dict__ будет использован для </span>
<span style="color: #408080; font-style: italic"># поиска атрибута 'c', если он не будет найден</span>
<span style="color: #408080; font-style: italic"># в a.__dict__. Вообще говоря поиск атрибута в объекте длинная </span>
<span style="color: #408080; font-style: italic"># история - о ней будет своя статья</span>
<span style="color: #008000">str</span>(a) <span style="color: #408080; font-style: italic"># => a.__str__(a)</span>
<span style="color: #008000">iter</span>(a) <span style="color: #408080; font-style: italic"># => a.iter(a)</span>
<span style="color: #408080; font-style: italic"># и т.д.</span>
</pre></div></span><span style="font-size:110%;display:none" id="ea8ef0a82e4a11e1ab2114feb5b819a0"><pre><font face="courier">a + b # => a.__class__.__add__(a, b)
a.c # => a.__class__.__getattr__(a, 'c')
# кроме этого a.__class__.__dict__ будет использован для
# поиска атрибута 'c', если он не будет найден
# в a.__dict__. Вообще говоря поиск атрибута в объекте длинная
# история - о ней будет своя статья
str(a) # => a.__str__(a)
iter(a) # => a.iter(a)
# и т.д.</font></pre></span><p style="text-indent:20px"> Таким образом мы можем перегрузить операторы для класса, перегрузив
соответствующие методы в его метаклассе. Например наследование при помощи
сложения:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ea8f6bfa2e4a11e1ab2114feb5b819a0" objtohide2="ea8f6d802e4a11e1ab2114feb5b819a0" >Hightlited/Raw</a><br><span id="ea8f6bfa2e4a11e1ab2114feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">MixinMeta</span>(<span style="color: #008000">type</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__add__</span>(<span style="color: #008000">self</span>, other_type):
new_name <span style="color: #666666">=</span> <span style="color: #BA2121">"Mixin_{0}_{1}"</span><span style="color: #666666">.</span>format(<span style="color: #008000">self</span><span style="color: #666666">.</span>__name__,
other_type<span style="color: #666666">.</span>__name__)
<span style="color: #408080; font-style: italic"># явное построение нового класса, наследующего текущий и </span>
<span style="color: #408080; font-style: italic"># 'other_type'</span>
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>__class__(new_name,
(<span style="color: #008000">self</span>, other_type),
{})
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Mixin</span>(<span style="color: #008000">object</span>):
__metaclass__ <span style="color: #666666">=</span> MixinMeta
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">A</span>(Mixin):
x <span style="color: #666666">=</span> <span style="color: #BA2121">'A.x'</span>
y <span style="color: #666666">=</span> <span style="color: #BA2121">'A.y'</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">B</span>(<span style="color: #008000">object</span>):
x <span style="color: #666666">=</span> <span style="color: #BA2121">'B.x'</span>
z <span style="color: #666666">=</span> <span style="color: #BA2121">'B.z'</span>
<span style="color: #008000; font-weight: bold">print</span> (A <span style="color: #666666">+</span> B)<span style="color: #666666">.</span>y <span style="color: #408080; font-style: italic"># A.y</span>
<span style="color: #008000; font-weight: bold">print</span> (A <span style="color: #666666">+</span> B)<span style="color: #666666">.</span>x <span style="color: #408080; font-style: italic"># A.x</span>
<span style="color: #008000; font-weight: bold">print</span> (A <span style="color: #666666">+</span> B)<span style="color: #666666">.</span>z <span style="color: #408080; font-style: italic"># B.z</span>
<span style="color: #408080; font-style: italic"># можно и так</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">C</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">pass</span>
tp <span style="color: #666666">=</span> Mixin <span style="color: #666666">+</span> C <span style="color: #666666">+</span> B
</pre></div></span><span style="font-size:110%;display:none" id="ea8f6d802e4a11e1ab2114feb5b819a0"><pre><font face="courier">class MixinMeta(type):
def __add__(self, other_type):
new_name = "Mixin_{0}_{1}".format(self.__name__,
other_type.__name__)
# явное построение нового класса, наследующего текущий и
# 'other_type'
return self.__class__(new_name,
(self, other_type),
{})
class Mixin(object):
__metaclass__ = MixinMeta
class A(Mixin):
x = 'A.x'
y = 'A.y'
class B(object):
x = 'B.x'
z = 'B.z'
print (A + B).y # A.y
print (A + B).x # A.x
print (A + B).z # B.z
# можно и так
class C(object):
pass
tp = Mixin + C + B</font></pre></span><p style="text-indent:20px"> <b>MixinMeta</b> позволяет создавать дочерний класс складывая базовые - вместо
<b>class C(A,B):pass</b> писать <b>A+B</b>. Точно так же можно, например, облагородить
'str(A)' или хранить в классе список ссылок на все его экземпляры и итерировать
по ним (этот пример идет в конце, поскольку использует все возможности
метаклассов).
По поводу доступа к атрибутам нужно помнить, что атрибуты и методы метаклассов
доступны через его классы однако не через экземпляры этих классов.</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ea8fb7682e4a11e1ab2114feb5b819a0" objtohide2="ea8fb8f82e4a11e1ab2114feb5b819a0" >Hightlited/Raw</a><br><span id="ea8fb7682e4a11e1ab2114feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">M</span>(<span style="color: #008000">type</span>):
X <span style="color: #666666">=</span> <span style="color: #666666">1</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">B</span>(<span style="color: #008000">object</span>):
__metaclass__ <span style="color: #666666">=</span> M
b <span style="color: #666666">=</span> B()
<span style="color: #008000; font-weight: bold">print</span> M<span style="color: #666666">.</span>X <span style="color: #408080; font-style: italic"># 1</span>
<span style="color: #008000; font-weight: bold">print</span> B<span style="color: #666666">.</span>X <span style="color: #408080; font-style: italic"># 1</span>
<span style="color: #008000; font-weight: bold">print</span> b<span style="color: #666666">.</span>X <span style="color: #408080; font-style: italic"># => AtrributeError</span>
</pre></div></span><span style="font-size:110%;display:none" id="ea8fb8f82e4a11e1ab2114feb5b819a0"><pre><font face="courier">class M(type):
X = 1
class B(object):
__metaclass__ = M
b = B()
print M.X # 1
print B.X # 1
print b.x # => AtrributeError</font></pre></span><p style="text-indent:20px">Поиск атрибута производится по объекту и его типу, но не по типу его типа.</p><br><h3>Автоматически вызывать некоторый код при каждом прямом или непрямом наследовании данного класса</h3><p style="text-indent:20px"> Это позволяет вести реестр всех классов, унаследовавших данный интерфейс.
Удобно для написания плагинов и других расширяемых архитектур.</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ea9069ba2e4a11e1ab2114feb5b819a0" objtohide2="ea906b362e4a11e1ab2114feb5b819a0" >Hightlited/Raw</a><br><span id="ea9069ba2e4a11e1ab2114feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #408080; font-style: italic"># в файле plugin_api.py</span>
plugin_registry <span style="color: #666666">=</span> {}
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">get_registry</span>():
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">RegMeta</span>(<span style="color: #008000">type</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>):
<span style="color: #008000">self</span><span style="color: #666666">.</span>interface_name <span style="color: #666666">=</span> <span style="color: #008000">None</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__new__</span>(cls, name, bases, cls_dict):
new_cls <span style="color: #666666">=</span> <span style="color: #008000">super</span>(RegMeta, cls)<span style="color: #666666">.</span>__new__(name,
bases,
cls_dict)
<span style="color: #408080; font-style: italic"># первый раз этот вызов произойдет из тела интерфейса</span>
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>interface_name <span style="color: #AA22FF; font-weight: bold">is</span> <span style="color: #008000">None</span>:
<span style="color: #008000">self</span><span style="color: #666666">.</span>interface_name <span style="color: #666666">=</span> name
<span style="color: #008000; font-weight: bold">else</span>:
plugin_registry<span style="color: #666666">.</span>setdefault(<span style="color: #008000">self</span><span style="color: #666666">.</span>interface_name, [])<span style="color: #666666">.</span>append(new_cls)
<span style="color: #008000; font-weight: bold">return</span> new_cls
<span style="color: #008000; font-weight: bold">return</span> RegMeta
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">get_all_implementations</span>(interface):
<span style="color: #008000; font-weight: bold">return</span> plugin_registry[interface<span style="color: #666666">.</span>__name__]
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">IDataGridUI</span>(<span style="color: #008000">object</span>):
__metaclass__ <span style="color: #666666">=</span> get_registry()
provides_ui <span style="color: #666666">=</span> <span style="color: #008000">None</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">display_my_data</span>(<span style="color: #008000">self</span>, data):
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #408080; font-style: italic"># в файле data_display_html_qt.py</span>
<span style="color: #008000; font-weight: bold">from</span> <span style="color: #0000FF; font-weight: bold">plugin_api</span> <span style="color: #008000; font-weight: bold">import</span> IDataGridUI
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">QtDataDisplayer</span>(IDataGridUI):
provides_ui <span style="color: #666666">=</span> <span style="color: #BA2121">'qt'</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">display_my_data</span>(<span style="color: #008000">self</span>, data):
<span style="color: #408080; font-style: italic">#some qt code</span>
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">HTMLDataDisplayer</span>(IDataGridUI):
provides_ui <span style="color: #666666">=</span> <span style="color: #BA2121">'html'</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">display_my_data</span>(<span style="color: #008000">self</span>, data):
<span style="color: #408080; font-style: italic">#some template code</span>
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #408080; font-style: italic"># в файле build_ui.py</span>
<span style="color: #008000; font-weight: bold">from</span> <span style="color: #0000FF; font-weight: bold">plugin_api</span> <span style="color: #008000; font-weight: bold">import</span> get_all_implementations
<span style="color: #008000; font-weight: bold">print</span> get_all_implementations(IDataGridUI)
<span style="color: #408080; font-style: italic"># [..., QtDataDisplayer, HTMLDataDisplayer, ....]</span>
</pre></div></span><span style="font-size:110%;display:none" id="ea906b362e4a11e1ab2114feb5b819a0"><pre><font face="courier"># в файле plugin_api.py
plugin_registry = {}
def get_registry():
class RegMeta(type):
def __init__(self):
self.interface_name = None
def __new__(cls, name, bases, cls_dict):
new_cls = super(RegMeta, cls).__new__(name,
bases,
cls_dict)
# первый раз этот вызов произойдет из тела интерфейса
if self.interface_name is None:
self.interface_name = name
else:
plugin_registry.setdefault(self.interface_name, []).append(new_cls)
return new_cls
return RegMeta
def get_all_implementations(interface):
return plugin_registry[interface.__name__]
class IDataGridUI(object):
__metaclass__ = get_registry()
provides_ui = None
def display_my_data(self, data):
pass
# в файле data_display_html_qt.py
from plugin_api import IDataGridUI
class QtDataDisplayer(IDataGridUI):
provides_ui = 'qt'
def display_my_data(self, data):
#some qt code
pass
class HTMLDataDisplayer(IDataGridUI):
provides_ui = 'html'
def display_my_data(self, data):
#some template code
pass
# в файле build_ui.py
from plugin_api import get_all_implementations
print get_all_implementations(IDataGridUI)
# [..., QtDataDisplayer, HTMLDataDisplayer, ....]</font></pre></span><p style="text-indent:20px"> Нужно учесть что для того, чтобы реализация попала в реестр файл реализации
должен быть импортирован где нибудь до вызова <b>get_all_implementations</b>.
Имея реестр всех реализаций можно, например,
автоматически расширять API программы (Rpc/REST/командная строка).</p><br><h3>Изменять параметры создаваемого дочернего класса</h3><p style="text-indent:20px">Свойство с наиболее обширным спектром применений. Варианты его применения:
Реализовать возможности аспектно-ориентированного подхода - автоматически
модифицировать поля и методы класса, основываясь на их имени:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ea90fe982e4a11e1ab2114feb5b819a0" objtohide2="ea9100142e4a11e1ab2114feb5b819a0" >Hightlited/Raw</a><br><span id="ea90fe982e4a11e1ab2114feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">PropertyAutoMakerMeta</span>(<span style="color: #008000">type</span>):
<span style="color: #BA2121; font-style: italic">"""автоматически делает property из всех </span>
<span style="color: #BA2121; font-style: italic"> всех методов вида get_Something"""</span>
get_prefix <span style="color: #666666">=</span> <span style="color: #BA2121">'get_'</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__new__</span>(cls, name, bases, cls_dict):
add_props <span style="color: #666666">=</span> {}
<span style="color: #008000; font-weight: bold">for</span> fname, val <span style="color: #AA22FF; font-weight: bold">in</span> cls_dict<span style="color: #666666">.</span>items():
<span style="color: #008000; font-weight: bold">if</span> fname<span style="color: #666666">.</span>startswith(cls<span style="color: #666666">.</span>get_prefix) <span style="color: #AA22FF; font-weight: bold">and</span> <span style="color: #008000">callable</span>(val):
prop_name <span style="color: #666666">=</span> fname[<span style="color: #008000">len</span>(cls<span style="color: #666666">.</span>get_prefix):]
add_props[prop_name] <span style="color: #666666">=</span> <span style="color: #008000">property</span>(fget<span style="color: #666666">=</span>val,
doc<span style="color: #666666">=</span>val<span style="color: #666666">.</span>__doc__)
cls_dict<span style="color: #666666">.</span>update(add_props)
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">super</span>(PropertyAutoMakerMeta,
cls)<span style="color: #666666">.</span>__new__(cls, name, bases, cls_dict)
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">PropertyAutoMaker</span>(<span style="color: #008000">object</span>):
__metaclass__ <span style="color: #666666">=</span> PropertyAutoMakerMeta
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">A</span>(PropertyAutoMaker):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">get_X</span>(<span style="color: #008000">self</span>):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #666666">1</span>
<span style="color: #008000; font-weight: bold">print</span> A()<span style="color: #666666">.</span>X <span style="color: #408080; font-style: italic"># 1</span>
</pre></div></span><span style="font-size:110%;display:none" id="ea9100142e4a11e1ab2114feb5b819a0"><pre><font face="courier">class PropertyAutoMakerMeta(type):
"""автоматически делает property из всех
всех методов вида get_Something"""
get_prefix = 'get_'
def __new__(cls, name, bases, cls_dict):
add_props = {}
for fname, val in cls_dict.items():
if fname.startswith(cls.get_prefix) and callable(val):
prop_name = fname[len(cls.get_prefix):]
add_props[prop_name] = property(fget=val,
doc=val.__doc__)
cls_dict.update(add_props)
return super(PropertyAutoMakerMeta,
cls).__new__(cls, name, bases, cls_dict)
class PropertyAutoMaker(object):
__metaclass__ = PropertyAutoMakerMeta
class A(PropertyAutoMaker):
def get_X(self):
return 1
print A().X # 1</font></pre></span><p style="text-indent:20px"> Изменять наборы параметров методов, применять к ним декораторы, менять
документацию, байтокод, добавлять/убирать базовые классы, etc.
Например - проверка соответствия объекта заявленному интерфейсу:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ea9153a22e4a11e1ab2114feb5b819a0" objtohide2="ea91564a2e4a11e1ab2114feb5b819a0" >Hightlited/Raw</a><br><span id="ea9153a22e4a11e1ab2114feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">from</span> <span style="color: #0000FF; font-weight: bold">interface</span> <span style="color: #008000; font-weight: bold">import</span> Interface, ImplementsBase
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">MyInterface</span>(Interface):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">func</span>(x, y, z):
ok(x)<span style="color: #666666">.</span>is_a(<span style="color: #008000">int</span>)
ok(y)<span style="color: #666666">.</span>in_((<span style="color: #666666">1</span>,<span style="color: #666666">2</span>,<span style="color: #666666">3</span>))
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Impl</span>(ImplementsBase):
__implements__ <span style="color: #666666">=</span> [MyInterface]
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">func</span>(<span style="color: #008000">self</span>, x, y, z<span style="color: #666666">=13</span>):
<span style="color: #008000; font-weight: bold">pass</span>
</pre></div></span><span style="font-size:110%;display:none" id="ea91564a2e4a11e1ab2114feb5b819a0"><pre><font face="courier">from interface import Interface, ImplementsBase
class MyInterface(Interface):
def func(x, y, z):
ok(x).is_a(int)
ok(y).in_((1,2,3))
class Impl(ImplementsBase):
__implements__ = [MyInterface]
def func(self, x, y, z=13):
pass</font></pre></span><p style="text-indent:20px"> Код модуля interface слишком большой для этой стать и находится здесь:
<a href="https://github.com/koder-ua/Interface_example">github.com/koder-ua/Interface_example</a> .</p><p style="text-indent:20px"> При конструировании <b>Impl</b> будет проверенно что он предоставляет все
методы, требуемые от <b>MyInterface</b> и совместимость сигнатур методов (т.е. что любой набор
параметров, который синтаксически подходит для <b>MyInterface.func</b> синтаксически
подходит и для <b>Impl.func</b>). Также при отладочных настройках перед каждым вызовом
<b>Impl.func</b> будет вызываться <b>MyInterface.func</b>, которая проверит ограничения
на входные параметры. Т.е. система с одной стороны проверяет, что класс
предоставляет необходимые методы, а с другой проверяет что при вызове
в методы передаются правильные параметры. Эту функциональность можно расширить
разными способами - извлекать ограничения на типы из строк документации,
вести реестр всех реализаций интерфейса, добавить автоматическую проверку пост
и пред условий на объект, проверять поля класса и т.д.</p><p style="text-indent:20px">Простой ORM:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ea9243e82e4a11e1ab2114feb5b819a0" objtohide2="ea92456e2e4a11e1ab2114feb5b819a0" >Hightlited/Raw</a><br><span id="ea9243e82e4a11e1ab2114feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Field</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Base</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Int</span>(Base):
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">String</span>(Base):
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">LittleORMMeta</span>(<span style="color: #008000">type</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__new__</span>(cls, name, bases, cls_dict):
fields <span style="color: #666666">=</span> []
<span style="color: #008000; font-weight: bold">for</span> fname, tp <span style="color: #AA22FF; font-weight: bold">in</span> cls_dict<span style="color: #666666">.</span>items():
<span style="color: #008000; font-weight: bold">try</span>:
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #008000">issubclass</span>(tp, Field<span style="color: #666666">.</span>Base):
fields<span style="color: #666666">.</span>append(fname)
<span style="color: #008000; font-weight: bold">except</span> <span style="color: #D2413A; font-weight: bold">TypeError</span>:
<span style="color: #008000; font-weight: bold">pass</span>
cls_dict[<span style="color: #BA2121">'_fields'</span>] <span style="color: #666666">=</span> fields
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">super</span>(LittleORMMeta, cls)<span style="color: #666666">.</span>__new__(cls, name,
bases, cls_dict)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__lshift__</span>(<span style="color: #008000">self</span>, fields):
<span style="color: #008000">self</span><span style="color: #666666">.</span>insert(<span style="color: #666666">**</span>fields)
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Table</span>(<span style="color: #008000">object</span>):
__metaclass__ <span style="color: #666666">=</span> LittleORMMeta
<span style="color: #AA22FF">@classmethod</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">execute</span>(cls, request):
<span style="color: #008000; font-weight: bold">print</span> request
<span style="color: #AA22FF">@classmethod</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">insert</span>(cls, <span style="color: #666666">**</span>fields):
insert_request <span style="color: #666666">=</span> <span style="color: #BA2121">"insert into {0} ({1}) values ({2})"</span>
values <span style="color: #666666">=</span> <span style="color: #BA2121">','</span><span style="color: #666666">.</span>join(
<span style="color: #008000">repr</span>(fields[fname])
<span style="color: #008000; font-weight: bold">for</span> fname <span style="color: #AA22FF; font-weight: bold">in</span> cls<span style="color: #666666">.</span>_fields)
field_names <span style="color: #666666">=</span> <span style="color: #BA2121">','</span><span style="color: #666666">.</span>join(cls<span style="color: #666666">.</span>_fields)
cls<span style="color: #666666">.</span>execute(
insert_request<span style="color: #666666">.</span>format( cls<span style="color: #666666">.</span>__name__, field_names, values)
)
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">MyTable</span>(Table):
rec_id <span style="color: #666666">=</span> Field<span style="color: #666666">.</span>Int
name <span style="color: #666666">=</span> Field<span style="color: #666666">.</span>String
<span style="color: #408080; font-style: italic">#Table.connect(conn_str)</span>
MyTable <span style="color: #666666"><<</span> <span style="color: #008000">dict</span>(rec_id<span style="color: #666666">=1</span>, name<span style="color: #666666">=</span><span style="color: #BA2121">'a'</span>)
MyTable <span style="color: #666666"><<</span> <span style="color: #008000">dict</span>(rec_id<span style="color: #666666">=2</span>, name<span style="color: #666666">=</span><span style="color: #BA2121">'b'</span>)
MyTable <span style="color: #666666"><<</span> <span style="color: #008000">dict</span>(rec_id<span style="color: #666666">=3</span>, name<span style="color: #666666">=</span><span style="color: #BA2121">'c'</span>)
</pre></div></span><span style="font-size:110%;display:none" id="ea92456e2e4a11e1ab2114feb5b819a0"><pre><font face="courier">class Field(object):
class Base(object):
pass
class Int(Base):
pass
class String(Base):
pass
class LittleORMMeta(type):
def __new__(cls, name, bases, cls_dict):
fields = []
for fname, tp in cls_dict.items():
try:
if issubclass(tp, Field.Base):
fields.append(fname)
except TypeError:
pass
cls_dict['_fields'] = fields
return super(LittleORMMeta, cls).__new__(cls, name,
bases, cls_dict)
def __lshift__(self, fields):
self.insert(**fields)
class Table(object):
__metaclass__ = LittleORMMeta
@classmethod
def execute(cls, request):
print request
@classmethod
def insert(cls, **fields):
insert_request = "insert into {0} ({1}) values ({2})"
values = ','.join(
repr(fields[fname])
for fname in cls._fields)
field_names = ','.join(cls._fields)
cls.execute(
insert_request.format( cls.__name__, field_names, values)
)
class MyTable(Table):
rec_id = Field.Int
name = Field.String
#Table.connect(conn_str)
MyTable << dict(rec_id=1, name='a')
MyTable << dict(rec_id=2, name='b')
MyTable << dict(rec_id=3, name='c')</font></pre></span><p style="text-indent:20px"> Система автоматического логирования всех вызовов. В функции <b>log_call</b>
можно реализовать расширенную фильтрацию логов:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ea92be4a2e4a11e1ab2114feb5b819a0" objtohide2="ea92c05c2e4a11e1ab2114feb5b819a0" >Hightlited/Raw</a><br><span id="ea92be4a2e4a11e1ab2114feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">logme</span>(class_name, func):
name <span style="color: #666666">=</span> <span style="color: #BA2121">"{0}.{1}"</span><span style="color: #666666">.</span>format(class_name, func)
<span style="color: #AA22FF">@functools.wraps</span>(func)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">closure</span>(<span style="color: #008000">self</span>, <span style="color: #666666">*</span>dt, <span style="color: #666666">**</span>mp):
log_call(name, dt, mp)
<span style="color: #008000; font-weight: bold">return</span> func(<span style="color: #008000">self</span>, <span style="color: #666666">*</span>dt, <span style="color: #666666">**</span>mp)
<span style="color: #008000; font-weight: bold">return</span> closure
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">CallLoggerMeta</span>(<span style="color: #008000">type</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__new__</span>(cls, name, bases, cdict):
<span style="color: #008000; font-weight: bold">for</span> fname, val <span style="color: #AA22FF; font-weight: bold">in</span> cdict:
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #008000">isinstance</span>(val, types<span style="color: #666666">.</span>FunctionType):
cdict[fname] <span style="color: #666666">=</span> logme(name, val)
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">super</span>(CallLoggerMeta, cls)<span style="color: #666666">.</span>\
__new__(cls, name, bases, cdict)
</pre></div></span><span style="font-size:110%;display:none" id="ea92c05c2e4a11e1ab2114feb5b819a0"><pre><font face="courier">def logme(class_name, func):
name = "{0}.{1}".format(class_name, func)
@functools.wraps(func)
def closure(self, *dt, **mp):
log_call(name, dt, mp)
return func(self, *dt, **mp)
return closure
class CallLoggerMeta(type):
def __new__(cls, name, bases, cdict):
for fname, val in cdict:
if isinstance(val, types.FunctionType):
cdict[fname] = logme(name, val)
return super(CallLoggerMeta, cls).\
__new__(cls, name, bases, cdict)</font></pre></span><p style="text-indent:20px"> В общем можно слепить из входящего в полях <b>name</b>, <b>bases</b> и <b>cls_dict</b>
пластилина то, что нам нужно. Обещанный пример класса, ведущего список всех
своих экземпляров и позволяющего по ним итерировать:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ea93c7ae2e4a11e1ab2114feb5b819a0" objtohide2="ea93ca242e4a11e1ab2114feb5b819a0" >Hightlited/Raw</a><br><span id="ea93c7ae2e4a11e1ab2114feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">IterOverChildsMeta</span>(<span style="color: #008000">type</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__new__</span>(cls, name, bases, cls_dict):
fname <span style="color: #666666">=</span> <span style="color: #BA2121">'_{0}__all_childs'</span><span style="color: #666666">.</span>format(name)
cls_dict[fname] <span style="color: #666666">=</span> []
ntype <span style="color: #666666">=</span> <span style="color: #008000">super</span>(IterOverChildsMeta,
cls)<span style="color: #666666">.</span>__new__(cls, name, bases, cls_dict)
old_new <span style="color: #666666">=</span> ntype<span style="color: #666666">.</span>__new__
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">closure</span>(cls1, <span style="color: #666666">*</span>args, <span style="color: #666666">**</span>kwargs):
<span style="color: #408080; font-style: italic"># workaroud for warning</span>
<span style="color: #008000; font-weight: bold">if</span> old_new <span style="color: #AA22FF; font-weight: bold">is</span> <span style="color: #008000">object</span><span style="color: #666666">.</span>__new__:
obj <span style="color: #666666">=</span> old_new(cls1)
<span style="color: #008000; font-weight: bold">else</span>:
obj <span style="color: #666666">=</span> old_new(cls1, <span style="color: #666666">*</span>args, <span style="color: #666666">**</span>kwargs)
<span style="color: #408080; font-style: italic"># skip objects of a child classes</span>
<span style="color: #008000; font-weight: bold">if</span> cls1<span style="color: #666666">.</span>__name__ <span style="color: #666666">==</span> name:
<span style="color: #408080; font-style: italic"># real code should use weakref.ref here</span>
ntype<span style="color: #666666">.</span>__dict__[fname]<span style="color: #666666">.</span>append(obj)
<span style="color: #008000; font-weight: bold">return</span> obj
ntype<span style="color: #666666">.</span>__new__ <span style="color: #666666">=</span> <span style="color: #008000">classmethod</span>(closure)
<span style="color: #008000; font-weight: bold">return</span> ntype
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__iter__</span>(<span style="color: #008000">self</span>):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">iter</span>(<span style="color: #008000">getattr</span>(<span style="color: #008000">self</span>,
<span style="color: #BA2121">'_{0}__all_childs'</span><span style="color: #666666">.</span>format(<span style="color: #008000">self</span><span style="color: #666666">.</span>__name__)))
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">IterOverChilds</span>(<span style="color: #008000">object</span>):
__metaclass__ <span style="color: #666666">=</span> IterOverChildsMeta
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">A</span>(IterOverChilds):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>, name):
<span style="color: #008000">self</span><span style="color: #666666">.</span>name <span style="color: #666666">=</span> name
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__str__</span>(<span style="color: #008000">self</span>):
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #BA2121">"<A name={0!r}>"</span><span style="color: #666666">.</span>format(<span style="color: #008000">self</span><span style="color: #666666">.</span>name)
a1 <span style="color: #666666">=</span> A(<span style="color: #BA2121">'a1'</span>)
a2 <span style="color: #666666">=</span> A(<span style="color: #BA2121">'a2'</span>)
<span style="color: #008000; font-weight: bold">for</span> obj <span style="color: #AA22FF; font-weight: bold">in</span> A:
<span style="color: #008000; font-weight: bold">print</span> obj
</pre></div></span><span style="font-size:110%;display:none" id="ea93ca242e4a11e1ab2114feb5b819a0"><pre><font face="courier">class IterOverChildsMeta(type):
def __new__(cls, name, bases, cls_dict):
fname = '_{0}__all_childs'.format(name)
cls_dict[fname] = []
ntype = super(IterOverChildsMeta,
cls).__new__(cls, name, bases, cls_dict)
old_new = ntype.__new__
def closure(cls1, *args, **kwargs):
# workaroud for warning
if old_new is object.__new__:
obj = old_new(cls1)
else:
obj = old_new(cls1, *args, **kwargs)
# skip objects of a child classes
if cls1.__name__ == name:
# real code should use weakref.ref here
ntype.__dict__[fname].append(obj)
return obj
ntype.__new__ = classmethod(closure)
return ntype
def __iter__(self):
return iter(getattr(self,
'_{0}__all_childs'.format(self.__name__)))
class IterOverChilds(object):
__metaclass__ = IterOverChildsMeta
class A(IterOverChilds):
def __init__(self, name):
self.name = name
def __str__(self):
return "<A name={0!r}>".format(self.name)
a1 = A('a1')
a2 = A('a2')
for obj in A:
print obj</font></pre></span><br><h2>Теория, часть 2 - наследование метаклассов и метаклассы-функции</h2><p style="text-indent:20px"> Python позволяет присвоить полю <b>__metaclass__</b> функцию - она будет
вызвана вместо методов <b>__new__</b> и <b>__init__</b> метакласса.</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ea941d122e4a11e1ab2114feb5b819a0" objtohide2="ea941eca2e4a11e1ab2114feb5b819a0" >Hightlited/Raw</a><br><span id="ea941d122e4a11e1ab2114feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">meta_func</span>(name, bases, dct):
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"meta_func called"</span>
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">type</span>(name, bases, dct)
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">F</span>(<span style="color: #008000">object</span>):
__metaclass__ <span style="color: #666666">=</span> meta_func
<span style="color: #408080; font-style: italic"># здесь напечатается 'meta_func called'</span>
</pre></div></span><span style="font-size:110%;display:none" id="ea941eca2e4a11e1ab2114feb5b819a0"><pre><font face="courier">def meta_func(name, bases, dct):
print "meta_func called"
return type(name, bases, dct)
class F(object):
__metaclass__ = meta_func
# здесь напечатается 'meta_func called'</font></pre></span><p style="text-indent:20px"> Если у одного из родительских классов тип отличается от <b>type</b>,
то для создания дочернего класса будет использован он и он же станет типом дочернего класса -
это позволяет метаклассам наследоваться. Функция-метакласс не может стать типом создаваемого класса и,
соответственно, не наследуется. Итого - пусть у нас есть такая иерархия классов:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ea94d02c2e4a11e1ab2114feb5b819a0" objtohide2="ea94d4142e4a11e1ab2114feb5b819a0" >Hightlited/Raw</a><br><span id="ea94d02c2e4a11e1ab2114feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">MetaClass</span>(<span style="color: #008000">type</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__new__</span>(cls, name, bases, dct):
<span style="color: #008000; font-weight: bold">print</span> name <span style="color: #666666">+</span> <span style="color: #BA2121">":metaclass == MetaClass"</span>
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">super</span>(MetaClass, cls)<span style="color: #666666">.</span>__new__(cls, name, bases, dct)
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">meta_func</span>(name, bases, dct):
<span style="color: #008000; font-weight: bold">print</span> name <span style="color: #666666">+</span> <span style="color: #BA2121">":metaclass == meta_func"</span>
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">type</span>(name, bases, dct)
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">A</span>(<span style="color: #008000">object</span>):
__metaclass__ <span style="color: #666666">=</span> MetaClass
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">B</span>(A):
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">C</span>(<span style="color: #008000">object</span>):
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">D</span>(A, C):
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">E</span>(C, A):
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">F</span>(<span style="color: #008000">object</span>):
__metaclass__ <span style="color: #666666">=</span> meta_func
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">G</span>(F):
<span style="color: #008000; font-weight: bold">pass</span>
<span style="color: #008000; font-weight: bold">for</span> cls <span style="color: #AA22FF; font-weight: bold">in</span> [A, B, C, D, E, F, G]:
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">"{0}.__class__ == {1}"</span><span style="color: #666666">.</span>format(cls<span style="color: #666666">.</span>__name__,
cls<span style="color: #666666">.</span>__class__<span style="color: #666666">.</span>__name__)
</pre></div></span><span style="font-size:110%;display:none" id="ea94d4142e4a11e1ab2114feb5b819a0"><pre><font face="courier">class MetaClass(type):
def __new__(cls, name, bases, dct):
print name + ":metaclass == MetaClass"
return super(MetaClass, cls).__new__(cls, name, bases, dct)
def meta_func(name, bases, dct):
print name + ":metaclass == meta_func"
return type(name, bases, dct)
class A(object):
__metaclass__ = MetaClass
class B(A):
pass
class C(object):
pass
class D(A, C):
pass
class E(C, A):
pass
class F(object):
__metaclass__ = meta_func
class G(F):
pass
for cls in [A, B, C, D, E, F, G]:
print "{0}.__class__ == {1}".format(cls.__name__,
cls.__class__.__name__)</font></pre></span><p style="text-indent:20px"> Вопрос - что будет напечатанное при ее конструировании и какие метаклассы
будут у полученных классов? Ответ: для конструирования <b>A</b>, <b>B</b>, <b>D</b> и <b>E</b> будет
использован <b>MetaClass</b>, для конструирования <b>F</b> будет использована
<b>meta_func</b>. Для конструирования <b>C</b> и <b>G</b> будет использован <b>type</b>. Для <b>A</b>,
<b>B</b>, <b>D</b> и <b>E</b> <b>__class__</b> будет <b>MetaClass</b>, для <b>F</b>, <b>G</b> и <b>C</b> будет <b>type</b>.</p><p style="text-indent:20px"> А как все обстоит с множественным наследованием, если у двух или более
базовых классов есть метакласс? Python не может самостоятельно разобраться в
этой ситуации и заставит нас сделать все руками:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ea958e682e4a11e1ab2114feb5b819a0" objtohide2="ea9590982e4a11e1ab2114feb5b819a0" >Hightlited/Raw</a><br><span id="ea958e682e4a11e1ab2114feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">MetaClass</span>(<span style="color: #008000">type</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__new__</span>(cls, name, bases, dct):
<span style="color: #008000; font-weight: bold">print</span> name <span style="color: #666666">+</span> <span style="color: #BA2121">":metaclass == MetaClass"</span>
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">super</span>(MetaClass, cls)<span style="color: #666666">.</span>__new__(cls, name, bases, dct)
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">A</span>(<span style="color: #008000">object</span>):
__metaclass__ <span style="color: #666666">=</span> MetaClass
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">SecondMetaClass</span>(<span style="color: #008000">type</span>):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__new__</span>(cls, name, bases, dct):
<span style="color: #008000; font-weight: bold">print</span> name <span style="color: #666666">+</span> <span style="color: #BA2121">":metaclass == SecondMetaClass"</span>
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">super</span>(SecondMetaClass,
cls)<span style="color: #666666">.</span>__new__(cls, name, bases, dct)
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">H</span>(<span style="color: #008000">object</span>):
__metaclass__ <span style="color: #666666">=</span> SecondMetaClass
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">J</span>(H, A):
<span style="color: #008000; font-weight: bold">pass</span>
</pre></div></span><span style="font-size:110%;display:none" id="ea9590982e4a11e1ab2114feb5b819a0"><pre><font face="courier">class MetaClass(type):
def __new__(cls, name, bases, dct):
print name + ":metaclass == MetaClass"
return super(MetaClass, cls).__new__(cls, name, bases, dct)
class A(object):
__metaclass__ = MetaClass
class SecondMetaClass(type):
def __new__(cls, name, bases, dct):
print name + ":metaclass == SecondMetaClass"
return super(SecondMetaClass,
cls).__new__(cls, name, bases, dct)
class H(object):
__metaclass__ = SecondMetaClass
class J(H, A):
pass</font></pre></span><p style="text-indent:20px">Это приведет к:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ea9615d62e4a11e1ab2114feb5b819a0" objtohide2="ea96178e2e4a11e1ab2114feb5b819a0" >Hightlited/Raw</a><br><span id="ea9615d62e4a11e1ab2114feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #0040D0">Traceback (most recent call last):</span>
File <span style="color: #008000">"test.py"</span>, line <span style="color: #666666">19</span>, in <module>
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">J</span>(H, A):
File <span style="color: #008000">"test.py"</span>, line <span style="color: #666666">14</span>, in __new__
<span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">super</span>(SecondMetaClass, cls)<span style="color: #666666">.</span>__new__(cls, name, bases, dct)
<span style="color: #FF0000">TypeError</span>: Error when calling the metaclass bases
metaclass conflict: the metaclass of a derived class must be a
(non-strict) subclass of the metaclasses of all its bases
</pre></div></span><span style="font-size:110%;display:none" id="ea96178e2e4a11e1ab2114feb5b819a0"><pre><font face="courier">Traceback (most recent call last):
File "test.py", line 19, in <module>
class J(H, A):
File "test.py", line 14, in __new__
return super(SecondMetaClass, cls).__new__(cls, name, bases, dct)
TypeError: Error when calling the metaclass bases
metaclass conflict: the metaclass of a derived class must be a
(non-strict) subclass of the metaclasses of all its bases</font></pre></span><p style="text-indent:20px"> Чтобы унаследовать классы <b>A</b> и <b>H</b> в указанном примере, нужно создать
класс, наследующий <b>MetaClass</b> и <b>SecondMetaClass</b> и определить его, как
метакласс для <b>J</b>. Впрочем создание такого класса можно автоматизировать:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ea96a58c2e4a11e1ab2114feb5b819a0" objtohide2="ea96a9382e4a11e1ab2114feb5b819a0" >Hightlited/Raw</a><br><span id="ea96a58c2e4a11e1ab2114feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">meta_base</span>(name, bases, cdict, add_meta <span style="color: #666666">=</span> <span style="color: #008000">tuple</span>()):
meta_set <span style="color: #666666">=</span> <span style="color: #008000">set</span>(cls<span style="color: #666666">.</span>__class__
<span style="color: #008000; font-weight: bold">for</span> cls <span style="color: #AA22FF; font-weight: bold">in</span> ( bases <span style="color: #666666">+</span> add_meta )
<span style="color: #008000; font-weight: bold">if</span> cls<span style="color: #666666">.</span>__class__ <span style="color: #AA22FF; font-weight: bold">is</span> <span style="color: #AA22FF; font-weight: bold">not</span> <span style="color: #008000">type</span>)
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #008000">len</span>(meta_set) <span style="color: #666666">!=</span> <span style="color: #666666">0</span>:
new_meta <span style="color: #666666">=</span> <span style="color: #008000">type</span>(<span style="color: #BA2121">"tempo_meta"</span>, <span style="color: #008000">tuple</span>(meta_set), {})
<span style="color: #008000; font-weight: bold">else</span>:
new_meta <span style="color: #666666">=</span> <span style="color: #008000">type</span>
<span style="color: #008000; font-weight: bold">return</span> new_meta(name, bases, cdict)
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">J</span>(H, A):
__metaclass__ <span style="color: #666666">=</span> meta_base
</pre></div></span><span style="font-size:110%;display:none" id="ea96a9382e4a11e1ab2114feb5b819a0"><pre><font face="courier">def meta_base(name, bases, cdict, add_meta = tuple()):
meta_set = set(cls.__class__
for cls in ( bases + add_meta )
if cls.__class__ is not type)
if len(meta_set) != 0:
new_meta = type("tempo_meta", tuple(meta_set), {})
else:
new_meta = type
return new_meta(name, bases, cdict)
class J(H, A):
__metaclass__ = meta_base</font></pre></span><p style="text-indent:20px">Если нужно добавить еще метаклассов, кроме базовых:</p><a hided_text="Hightlited/Raw" visible_text="Hightlited/Raw" style="border-bottom: 2px dotted #2020B0; color: #2020B0; font-style:italic; font-size: 90%" class="dhidder" objtohide1="ea96f7762e4a11e1ab2114feb5b819a0" objtohide2="ea96f9382e4a11e1ab2114feb5b819a0" >Hightlited/Raw</a><br><span id="ea96f7762e4a11e1ab2114feb5b819a0"><div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">meta_base_plus</span>(<span style="color: #666666">**</span>metas):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">closure</span>(name, bases, cdict) :
<span style="color: #008000; font-weight: bold">return</span> meta_base(name, bases, cdict,
add_meta<span style="color: #666666">=</span>metas)
<span style="color: #008000; font-weight: bold">return</span> closure
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Jplus</span>(H, A):
__metaclass__ <span style="color: #666666">=</span> meta_base_plus(SomeAdditionalMeta1,
SomeAdditionalMeta2)
</pre></div></span><span style="font-size:110%;display:none" id="ea96f9382e4a11e1ab2114feb5b819a0"><pre><font face="courier">def meta_base_plus(**metas):
def closure(name, bases, cdict) :
return meta_base(name, bases, cdict,
add_meta=metas)
return closure
class Jplus(H, A):
__metaclass__ = meta_base_plus(SomeAdditionalMeta1,
SomeAdditionalMeta2)</font></pre></span><p style="text-indent:20px"> Метаклассы представляют достаточно мощный инструмент для создания повторно
используемого кода, но результат их деятельности может стать большой
неожиданностью для тех, кто будет их использовать. Это подчеркивает важность
документирования всей нетривиальной функциональности, реализованной с их
помощью.</p>Ссылки:<br> <a href="http://www.python.org/download/releases/2.2/descrintro/">www.python.org/download/releases/2.2/descrintro</a><br> <a href="http://gnosis.cx/publish/programming/metaclass_1.html">gnosis.cx/publish/programming/metaclass_1.html</a><br> <a href="http://gnosis.cx/publish/programming/metaclass_2.html">gnosis.cx/publish/programming/metaclass_2.html</a><br> <a href="http://gnosis.cx/publish/programming/metaclass_3.html">gnosis.cx/publish/programming/metaclass_3.html</a><br> <a href="http://www.ibm.com/developerworks/linux/library/l-pymeta/index.html">www.ibm.com/developerworks/linux/library/l-pymeta/index.html</a><br> <a href="http://www.voidspace.org.uk/python/articles/metaclasses.shtml">www.voidspace.org.uk/python/articles/metaclasses.shtml</a><br> <a href="http://peak.telecommunity.com/PyProtocols.html">peak.telecommunity.com/PyProtocols.html</a><br> <a href="http://pypi.python.org/pypi/zope.interface/3.8.0">pypi.python.org/pypi/zope.interface/3.8.0</a><br> <a href="http://www.python.org/dev/peps/pep-0246/">www.python.org/dev/peps/pep-0246</a><br><br><h3>Disclamer</h3><ul><li>Приведенный код сознательно сокращен для упрощения понимания. Для реального использования его нужно дорабатывать, впрочем не сильно.<li>Все описанное относится к "новым классам", т.е. прямо или косвенно унаследованным от <b>object</b>. Все кто во втором десятилетии 21го века
не наследуют свои классы от <b>object</b> создают себе лишние проблемы.
В python3 все объекты по умолчанию "новые".<li>Про метаклассы в python написано достаточно много (см. ссылки). Эта статья основывается на моем опыте преподавания python, описывает некоторые
темы, которые сложно найти (например почему метаклассы-функции не наследуются)
и включает большое количество примеров практического применения метаклассов.</ul><p style="text-indent:20px">P.S. Спасибо всем за комментарии и правки.</p><p style="text-indent:20px">Исходники этого и других постов со скриптами лежат тут - <a href="https://github.com/koder-ua/python-lectures">github.com/koder-ua</a>. При использовании их, пожалуйста, ссылайтесь на <a href="http://koder-ua.blogspot.com/">koder-ua.blogspot.com</a>.</p>
<script type="text/javascript">
function on_hidabble_click()
{
var me = $(this);
var hide_id = me.attr("objtohide");
var controlled_object = $('#' + hide_id);
controlled_object.toggle();
if ( controlled_object.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".hidder").click(on_hidabble_click);
function on_double_hidabble_click()
{
var me = $(this);
var hide_id1 = me.attr("objtohide1");
var hide_id2 = me.attr("objtohide2");
var controlled_object1 = $('#' + hide_id1);
var controlled_object2 = $('#' + hide_id2);
controlled_object1.toggle();
controlled_object2.toggle();
if ( controlled_object1.is(":visible") )
me.html(me.attr("visible_text"));
else
me.html(me.attr("hided_text"));
return false;
}
$(".dhidder").click(on_double_hidabble_click);
</script>Anonymoushttp://www.blogger.com/profile/06645078574760734586noreply@blogger.com2