Журнал LinuxFormat - перейти на главную

LXF133:Python

Материал из Linuxformat
Перейти к: навигация, поиск

Python Ре­аль­ные про­ек­ты, от­та­чи­ваю­щие на­вы­ки ха­ке­ра

Содержание

Python: Кох и его снежинка

Ник Вейч уп­раж­ня­ет­ся в ма­те­ма­ти­ке, со­че­тая тео­ре­му Пи­фа­го­ра, Python, Clutter и Cogls и по­лу­чая на вы­хо­де кра­си­вые пу­ши­стые сне­жин­ки Ко­ха.


Python
Python для профессионалов
Python + PyGame
Python + Web
Python + Clutter

В про­шлых вы­пусках этой се­рии мы раз­влека­лись с ак­те­ра­ми [actor] и сце­на­ми [scene], под­клю­чив мощь до­полнитель­ных биб­лио­тек ти­па GStreamer и Cairo, что­бы соз­дать по­боль­ше объ­ек­тов и дви­гать ими, по­ка они не запро­сят по­ща­ды. Те­перь по­ра опять за­нять­ся ак­те­ра­ми, но на сей раз мы не ог­раничим­ся нуд­ны­ми пря­мо­угольника­ми и тек­стом, пре­достав­ляе­мы­ми Clutter – нет, мы соз­да­дим свои. Для это­го по­тре­бу­ет­ся при­влечь неко­то­рые из при­ми­тив­ных ме­то­дов для управ­ления ле­жа­щи­ми в их осно­ве GL-объ­ек­та­ми – по­рез­вим­ся с Cogls.

Пре­ж­де чем на­чать, оп­ре­де­лим­ся с фор­мой, ко­то­рую примет наш ак­тер. Че­ст­но го­во­ря, основ­ные евк­ли­до­вы фор­мы хоть и по­лез­ны, но уны­лы – да­вай­те соз­да­дим нечто по­ин­те­реснее: снежин­ку Ко­ха!

Что нам на­до

Оче­вид­но, пре­ж­де чем на­чать, на­до об­за­вес­тись Python’ом и его моду­лем Clutter. Оба они дос­туп­ны в ре­по­зи­то­ри­ях ва­ше­го ди­ст­ри­бути­ва, ес­ли ваш ди­ст­ри­бу­тив об­нов­лял­ся хоть раз за по­след­ний год. Обыч­но безо­пас­нее взять па­кет от­ту­да; ну, а са­мый но­вый ис­ход­ный код для Clutter – на сай­те http://www.clutter-project.org.

Да бу­дет снег!

LXF133 80 1.jpg Здесь по­ка­за­ны пер­вая, вто­рая и де­вя­тая ите­ра­ции сне­жин­ки Ко­ха. Да­лее раз­ни­цу уже не раз­гля­деть.

Снежин­ка (или кри­вая) Ко­ха – это вид фрак­та­ла. Стро­ят­ся фракта­лы, как пра­ви­ло, про­це­дур­ным спо­со­бом, ко­то­рый на-ура адапти­ру­ет­ся ком­пь­ю­те­ра­ми. Уве­рен, что по­пыт­ки по­стро­ить по­добные шту­ки несколь­ки­ми стро­ка­ми на язы­ке Basic, Pascal или че­му там сей­час де­тей учат (в мое вре­мя это бы­ли Algol и Fortran) за­няли нема­ло уро­ков ин­фор­ма­ти­ки.

Основ­ная кон­цеп­ция про­ста. Бе­рет­ся рав­но­сто­ронний треугольник. За­тем по­сре­ди ка­ж­дой сто­ро­ны стро­ит­ся еще по одно­му рав­но­сто­роннему тре­угольнику, со сто­ро­ной втрое мень­ше, чем у ис­ход­но­го. Этот шаг по­вто­ря­ет­ся до тех пор, по­ка не на­доест. На ри­сун­ке по­ка­за­но, что по­лу­ча­ет­ся по­сле нескольких ите­ра­ций.

По некой при­чине эта за­да­ча обыч­но ре­ша­ет­ся при­менением ре­кур­сив­но­го ал­го­рит­ма, вы­зы­ваю­ще­го са­мо­го се­бя. Оно, конечно, ум­но и изящ­но, но да­ле­ко от идеа­ла. Ма­ло то­го, что ал­горитм тру­ден в понимании; он еще и ужас­но про­жор­лив. К то­му же мож­но на­ткнуть­ся на ли­мит ко­ли­че­ства ре­кур­сий: Python ко­режит при мыс­ли о до­бав­лении в стек лишнего об­ра­щения к функции. По умол­чанию Python раз­ре­ша­ет толь­ко 1000 уровней ре­курсии, и хо­тя уста­нов­кой зна­чений систем­ных пе­ре­мен­ных пре­дел мож­но рас­ши­рить, от­дель­ные плат­фор­мы за­да­ют же­ст­кое ог­раничение.

Для на­ше­го ал­го­рит­ма Ко­ха мы возь­мем менее эф­фектный, но бо­лее эф­фек­тив­ный под­ход – на­деж­ный и дру­же­любный к про­цес­со­ру (важ­ность это­го про­явит­ся поз­же). По­про­сту, мы начнем со спи­ска из трех то­чек, ко­то­рые об­ра­зу­ют бо­лее-менее пра­виль­ный тре­угольник. В цик­ле бу­дем про­смат­ри­вать спи­сок и встав­лять три точ­ки ме­ж­ду ка­ж­дой па­рой то­чек (не забы­вая про па­ру, со­стоя­щую из пер­вой и по­следней то­чек). Та­ким об­ра­зом, ка­ж­дый про­ход цик­ла фор­ми­ру­ет но­вый уро­вень фракта­ла. Это про­сто, и при­мер­но на­по­ло­ви­ну бы­ст­рее ре­кур­сив­ного ре­шения:

LXF133 81 1.jpg Вот так мы вы­числя­ем, ку­да пе­ре­меща­ют­ся точ­ки.

 def generatekoch(depth=4):
    sqrtof3=1.7320508075688772
    pointlist=[(0,50),(75,180),(150,50)] # более-менее равносторонний треугольник
    for i in range(depth):
       newlist=[]
       for p in range(len(pointlist)):
         x1=pointlist[p][0]
         y1=pointlist[p][1]
         if p==len(pointlist)-1:
            x5=pointlist[0][0]
            y5=pointlist[0][1]
         else:
            x5=pointlist[p+1][0]
            y5=pointlist[p+1][1]
            dx=x5-x1
            dy=y5-y1
            x2=x1+(dx/3.0)
            y2=y1+(dy/3.0)
            x4=x1+(2*dx/3.0)
            y4=y1+(2*dy/3.0)
            x3=(x1+x5)/2 + (sqrtof3 * (y1-y5))/6 #см. диаграмму
            y3=(y1+y5)/2 + (sqrtof3 * (x5-x1))/6
            newlist.append((x1,y1))
            newlist.append((x2,y2))
            newlist.append((x3,y3))
            newlist.append((x4,y4))
            #точка 5 уже в списке
        pointlist = newlist
    return pointlist
 if __name__ == “__main__”:
    list=generatekoch(3)
    print list

Пе­ре­мен­ные внут­ри цик­ла со­от­вет­ству­ют пя­ти точ­кам на новых от­рез­ках. Пер­вая и по­след­няя – это те, что бе­рут­ся из ис­ходно­го спи­ска. Осталь­ные три на­до вы­чис­лить. Вто­рая и чет­вер­тая ле­жат на од­ной тре­ти и двух тре­тях от­рез­ка, со­еди­няю­ще­го ис­ходные точ­ки, и их ко­ор­ди­на­ты най­ти неслож­но. Тре­тья точ­ка – верши­на но­во­го тре­угольника, ко­то­рую на­до вы­чис­лить. К сча­стью, тео­ре­ма Пи­фа­го­ра для рав­но­сто­ронних тре­угольников весь­ма упро­ща­ет­ся, и все, что нуж­но вы­чис­лить – это квад­рат­ный ко­рень из 3; его мож­но стя­нуть из биб­лио­те­ки math (мо­же­те и са­ми написать под­хо­дя­щее при­бли­жение). Ес­ли вас ин­те­ре­су­ет ма­те­ма­ти­ка, вспомните, что пра­виль­ный тре­угольник – это два пря­мо­уголь­ных тре­угольника, при­став­лен­ных друг к дру­гу. За де­таль­ной кар­тиной об­ра­ти­тесь к диа­грам­ме.

Здесь мы ис­поль­зо­ва­ли в ка­че­стве ите­ра­то­ра цикл for, а не список: так про­ще ра­бо­тать с па­рой эле­мен­тов. Мы фор­ми­ру­ем но­вый спи­сок то­чек, а не встав­ля­ем точ­ки в ста­рый, по­сколь­ку при этом, кро­ме все­го про­че­го, пор­тит­ся счет­чик цик­ла.

При та­кой генера­ции снежин­ки есть неболь­шой под­вод­ный ка­мень: фор­му­лы пред­по­ла­га­ют, что точ­ки ис­ход­но­го цик­ла распо­ло­же­ны по ча­со­вой стрел­ке, ина­че кри­вая бу­дет во­гну­той (при этом то­же, кста­ти, по­лу­ча­ет­ся кра­си­вый ри­су­нок).

Соз­дание ак­те­ров

Те­перь мы зна­ем, что со­бра­лись ри­со­вать, и мож­но на­чи­нать строить ак­те­ра. В Clutter есть ме­та­класс для ак­те­ров, и мы ис­поль­зуем его как шаб­лон. То есть, ниче­го не за­пол­няя, мож­но сде­лать новый класс на осно­ве clutter.Actor, и он уже унас­ле­ду­ет мно­же­ство ме­то­дов и свойств.

Объ­ек­ты Clutter и са­ми унас­ле­до­ва­ны от GObject, ко­то­рый явля­ет­ся ча­стью GLib от Gnome Foundation, об­шир­ной биб­лио­те­ки кросс-плат­фор­мен­ных струк­тур дан­ных (не пу­тай­те ее с Glibc!). Это по­на­до­бит­ся нам в дальней­шем – мы прихватим несколько кусков из GLib, что­бы наш код за­ра­бо­тал. А по­ка про­сто постро­им ак­те­ра-тре­угольника. От­крой­те тер­ми­нал, вве­ди­те python для за­пуска Python в ин­те­рак­тив­ном ре­жи­ме, и вве­ди­те сле­дующее (ес­ли вам лень, ско­пи­руй­те и вставь­те со­дер­жи­мое листинга с LXFDVD):

 >>> import gobject
 >>> import clutter
 >>> from clutter import cogl
 >>> class Triangle (clutter.Actor):
 ... def __init__ (self):
 ... clutter.Actor.__init__(self)
 ... self._color = clutter.Color(255,255,255,255)
 ... def do_paint (self):
 ... (x1, y1, x2, y2) = self.get_allocation_box()
 ... width=x2-x1
 ... height=y2-y1
 ... cogl.path_move_to(width / 2, 0)
 ... cogl.path_line_to(width, height)
 ... cogl.path_line_to(0, height)
 ... cogl.path_line_to(width / 2, 0)
 ... cogl.path_close()
 ... cogl.set_source_color(self._color)
 ... cogl.path_fill()
 ...
 >>> gobject.type_register(Triangle)
 <class ‘__main__.Triangle>
 >>>

Вме­сте с биб­лио­те­кой Clutter мы так­же им­пор­ти­ро­ва­ли gobject и, осо­бен­но, биб­лио­те­ку cogl. По­след­няя про­сто уко­ротит про­стран­ство имен (вме­сто то­го, что­бы пи­сать clutter.cogl.path_move_to, мож­но опустить пер­вый clutter). GObject необхо­дим не толь­ко для до­бав­ления свойств и сиг­на­лов, но так­же для ре­ги­ст­ра­ции ти­па объ­ек­та, ко­то­рая тре­бу­ет­ся в Clutter. Можно ви­деть, что мы сде­ла­ли это сра­зу по­сле соз­дания клас­са – это необ­хо­ди­мо про­из­ве­сти до соз­дания ка­ких-ли­бо эле­мен­тов Triangle.

В са­мом клас­се мы оп­ре­де­ли­ли, как обыч­но, ме­тод __init__. Ме­та­класс Actor име­ет свой ме­тод init, но мы пе­ре­запи­са­ли его, что­бы до­ба­вить но­вую функ­цио­наль­ность (в на­шем слу­чае это про­сто уста­нов­ка пе­ре­мен­ной цве­та). Од­на­ко мы все-та­ки мо­жем вы­звать имев­ший­ся по умол­чанию ме­тод __init__, яв­ным об­разом; так мы сва­лим на Clutter ряд обыч­ных для него ве­щей, с ко­торы­ми нам неохо­та во­зить­ся са­мим.

Ме­тод paint очень ва­жен: он-то и ис­поль­зу­ет функ­ции из Cogl. Ка­ж­дый ак­тер име­ет ме­тод paint, ко­то­рый вы­зы­ва­ет­ся вся­кий раз, когда нуж­но от­ри­со­вать объ­ект. Этот ме­тод вы­зы­вает­ся са­мим Clutter, и мо­жет вы­зы­вать­ся мно­же­ство раз, на­при­мер, при анима­ции.

Коман­ды ри­со­вания про­зрач­ны для понимания. Пред­ставь­те, что у вас есть пе­ро – его нуж­но по­местить в на­чаль­ную по­зи­цию, а за­тем вести линии к раз­ным точ­кам. Ме­тод path_close объ­е­диня­ет пер­вую и по­след­нюю точ­ки для за­мы­кания кон­ту­ра, что обяза­тель­но при за­лив­ке. До­полнитель­ных команд ри­со­вания пол­но (у мно­гих есть аб­со­лют­ные и от­но­си­тель­ные вер­сии), и до­ку­мента­ция для при­ми­ти­вов доступ­на на сай­те Clutter: http://clutter-project.org/docs/cogl/stable/cogl-Primitives.html. Конеч­но, это доку­мен­та­ция для C, но ра­зо­брать­ся в ра­бо­те ме­то­дов там со­всем неслож­но.

Чу­де­са ри­со­вания

И по­следний ма­ги­че­ский трюк в этом ко­де – в на­ча­ле ме­то­да paint. Вы­зов get_allocation_box ис­поль­зу­ет один из унас­ле­дован­ных ме­то­дов Actor, что­бы уз­нать раз­мер от­ри­со­ванн­но­го акте­ра, ко­то­рый воз­вра­ща­ет две точ­ки, за­даю­щие пре­де­лы об­ласти ри­со­вания. На дан­ный мо­мент не сто­ит бес­по­ко­ить­ся о раз­ме­ре объ­ек­та – при ка­ж­дом вы­зо­ве ак­тер­ско­го ме­то­да set_size(), разно­об­раз­ные внут­ренние про­це­ду­ры Clutter по­за­бо­тят­ся об изменении раз­ме­ра ак­те­ра, а раз­мер об­ласти для ри­со­вания по­меня­ет­ся в со­от­вет­ствии с ним.

Те­перь про­тес­ти­ру­ем на­ши тре­уголь­ни­ки, вы­пол­нив обыч­ную ус­та­нов­ку сце­ны и до­ба­вив объ­ек­ты:

 >>> stage=clutter.Stage()
 >>> stage.set_size(400,400)
 >>> t=Triangle()
 >>> t.set_size(50,50)
 >>> stage.add(t)
 >>> stage.set_color(clutter.Color(0,0,0,255))
 >>> stage.show_all()
 >>> tt=Triangle()
 >>> tt.set_size(100,100)
 >>> tt.set_position(200,200)
 >>> stage.add(tt)

Те­перь мож­но соз­да­вать тре­уголь­ни­ки и да­же ани­ми­ро­вать их:

>>> tt.animate(clutter.EASE_IN_QUAD,2000,’y’,0)
<clutter.Animation object at 0x97b334c (ClutterAnimation at 0x98a1990)>

Но не все так про­сто, как ка­жет­ся. По­про­бу­ем по­ме­нять цвет на­ше­го тре­уголь­ни­ка, та­ким спо­со­бом:

>>> tt.set_color(clutter.Color(255,255,0,255))
Traceback (most recent call last):
File “<stdin>”, line 1, in <module>
AttributeError: ‘Triangle’ object has no attribute ‘set_color’

На­лог на на­след­ст­во

Функ­цио­наль­ность стан­дарт­но­го ак­те­ра на­сле­ду­ет­ся не пол­ностью. В от­ли­чие от встро­ен­но­го объ­ек­та rectangle, у нас нет ме­то­да для ус­та­нов­ки цве­та соз­дан­но­го на­ми объ­ек­та Triangle. Для это­го нуж­но до­ба­вить в класс:

def set_color (self, color):
self._color = color

Этот ку­сок ко­да, оче­вид­но, дол­жен быть ча­стью глав­но­го класса, и в этом при­ме­ре он при­ни­ма­ет стан­дарт­ный объ­ект clutter.Color(), хо­тя это мож­но из­ме­нить. Так,при­ме­няя это к на­шей снежин­ке Ко­ха, мы по­лу­чим не­что вро­де сле­дую­ще­го (за­меть­те, что для крат­ко­сти здесь ге­не­ра­тор Ко­ха не по­ка­зан):

 import gobject
 import clutter
 from clutter import cogl
 class Koch (clutter.Actor):
    “””
    Актер снежинки Коха
    имеет дополнительное свойство ‘_iterations’, управляющее глубиной фрактала
    “””
   __gtype_name__ = ‘Koch’
   def __init__ (self):
    clutter.Actor.__init__(self)
    self._color = clutter.Color(255,255,255,255)
    self._iterations = 2
    self._points=[(0,0),(0,0),(0,0)]
   def generatekoch(self,dimension):
    ### см. выше
    return pointlist
 
   def set_color (self, color):
    self._color = color
   def __paint_shape (self, paint_color):
    pointlist=self._points
    cogl.path_move_to(pointlist[0][0], pointlist[0][1])
    for point in pointlist:
     cogl.path_line_to(point[0], point[1])
    cogl.path_close()
    cogl.set_source_color(paint_color)
    cogl.path_fill()
   def do_paint (self):
    paint_color = self._color
    real_alpha = self.get_paint_opacity() * paint_color.alpha / 255
    paint_color.alpha = real_alpha
    self.__paint_shape(paint_color)
 
   def set_size (self,width,height):
    clutter.Actor.set_size(self,width,height)
    dimension=float(min(width,height))
    self._points=self.generatekoch(dimension)
 
   def set_iterations (self,number):
    self._iterations=number
    (x,y) = self.get_size()
    dimension = min(x,y)
    self._points=self.generatekoch(dimension)
    self.do_paint()
 gobject.type_register(Koch)

Здесь мож­но ви­деть, что мы храним спи­сок то­чек в ви­де свойства – пе­ре­ри­со­вы­вать кри­вую глу­би­ны во­семь и вы­ше с ну­ля, да еще и несколь­ко раз в се­кун­ду, бы­ло бы очень за­трат­но! Генерация спи­ска вы­зы­ва­ет­ся вся­кий раз, когда из­ме­ня­ет­ся чис­ло ите­раций. Это зна­чит, что обыч­но при уста­нов­ке объ­ек­та точ­ки фор­миру­ют­ся два­ж­ды, пред­по­ла­гая, что ко­ли­че­ство ите­ра­ций, стоя­щее по умол­чанию, из­менено. К со­жа­лению, это не уст­ранить, ес­ли толь­ко вы не хо­ти­те, что­бы «глу­би­на» фрак­та­ла име­ла дей­ствие толь­ко при из­менении раз­ме­ра.

Мы пе­ре­запи­са­ли ме­тод set_size клас­са Actor, что­бы убе­диться, что в сфор­ми­ро­ван­ных на­ми точ­ках от­ра­жа­ет­ся раз­мер объ­екта, но, тем не менее, все еще необ­хо­ди­мо вы­зы­вать ме­тод set_size ро­ди­тель­ско­го клас­са, что­бы удо­сто­ве­рить­ся, что бу­фе­ры и прочее об­но­ви­лись.

Что­бы про­де­мон­ст­ри­ро­вать на­ши но­вые фи­гу­ры, вот вам простой генера­тор для тести­ро­вания:

LXF133 83 1.jpg Вот и сно­ва вы­пал снег — ваш воз­вы­шен­ный код от Linux Format мо­жет соз­да­вать снежин­ки при лю­бой по­го­де.

 import clutter, random
 from clutterKoch import Koch
 stage = clutter.Stage()
 stage.set_size(640, 480)
 stage.set_color(clutter.Color(0,0,0,255))
 stage.connect(‘destroy’, clutter.main_quit)
 for i in range(10):
    s = Koch()
    x=random.randint(20,90)
    s.set_size(x, x)
    s.set_iterations(6)
    s.set_color(clutter.Color(200,200,random.randint(200,255),255))
    z=random.randint(0+x,640-x)
    zz=random.randint(x,x+200)
    s.set_position(z,-zz)
    stage.add(s)
    s.animate(clutter.EASE_IN_QUAD, 5000,’y’,x+random.randint(480,550),’rotation-angle-y’,random.randint(180,720))
    stage.show()
  clutter.main()

При усло­вии, что файл Actor (в на­шем слу­чае это clutterKoch.py) на­хо­дит­ся в том же ка­та­ло­ге, это мож­но за­пускать и ри­со­вать слу­чай­ные кри­вые. Как вы уви­ди­те, на­ши тво­рения мож­но дви­гать и вра­щать. Для это­го не нуж­но за­но­во фор­ми­ро­вать все точки: на дан­ный мо­мент они яв­ля­ют­ся GL-точ­ка­ми, и об их пра­вильном по­ло­жении по­за­бо­тит­ся ви­део­кар­та.

Что даль­ше

Cogls по­ле­зен не толь­ко при ри­со­вании раз­ных форм. Ис­поль­зуя этот ин­тер­фейс к OpenGL, мож­но по­ме­нять мно­же­ство ас­пек­тов ото­бра­жения, вплоть до соз­дания соб­ствен­но­го шей­де­ра. На сайте Clutter есть мно­го до­ку­мен­та­ции по раз­ным воз­мож­но­стям Cogls. Од­на­ко, как мы отметили ранее, она пред­на­зна­че­на для про­грам­ми­стов на C, и вам при­дет­ся по­тра­тить неко­то­рое вре­мя, чтобы это за­ра­бо­та­ло в Python. Взгляните на http://clutter-project.org/docs/cogl/stable.

Мы так­же небреж­но отнеслись к на­строй­ке на­ше­го gobject. В сущ­но­сти, все что мы сде­ла­ли – это са­мый минимум, что­бы Actor за­ра­бо­тал. Тот, кто пра­виль­но об­ра­ща­ет­ся с систе­мой, должен за­ре­ги­ст­ри­ро­вать GObject’ов­ские свой­ства на­ше­го объ­ек­та, и мож­но да­же до­ба­вить для него соб­ствен­ные сиг­на­лы. GObject пре­кра­сен, хотя и сло­жноват и мно­го­сло­вен; увы, здесь ма­ло места, что­бы пол­но­стью его раскрыть. Но до­ку­мен­тация PyGTK со­дер­жит мно­го по­лез­ной ин­фор­ма­ции о GObject, и если вам ин­те­рес­но, зай­ди­те на http://www.pygtk.org/docs/pygobject.

Персональные инструменты
купить
подписаться
Яндекс.Метрика