сряда, 28 януари 2009 г.

Изпълняване на Shell команди от Java код.

String cmd = "ls -al";
Runtime run = Runtime.getRuntime();
Process pr = run.exec(cmd);
pr.waitFor();
BufferedReader buf = new BufferedReader(new InputStreamReader(pr.getInputStream()));
String line = "";
while ((line=buf.readLine())!=null) {
System.out.println(line);
}

понеделник, 19 януари 2009 г.

Deploying на Web приложение на WebSphere

За да deploy-нем приложение на IBM WebSphere трябва да направим .war файл. Ако нашето приложение е работило на Tomcat или JBOSS, то ние можем да го преместим на WebSphere като направим .war файл с помоща на jar.

1) Отваряме терминала с текуща директория директорията на приложението ни.
2) За да направим .war файл изпълняваме следната команда "jar - cvf schoolsystem.war * "
(jar e tool от стандартния пакет на JRE)


Нашия .war файл се появява в текущата ни директория.

3) Стартираме WebSphere:
Start menu > All Program > IBM WebSphere > Application Server v6 > Profiles > AppSrv01 > Start the server

4) Отваряте Administration Console пак от същото място в StartMenu и се логваме с произволно име.




5) Избираме Applications > Install New Application за да инсталираме нашия .war файл

6) Избираме файла от диска и в
Context Root задаваме име под което ще се отваря приложението приметно my_app означава http://localhost:9081/my_app

7) Даваме Next и довършваме инсталацията

8) Избираме Save to Master Configuration и избираме Save за да запазим промените







9) Избираме
Applications > Enterprise Application и стартираме нашето приложение


10) Отваряме http://localhost:9081/my_app, (в някой случаи може порта да е 9080) къhttp://1.bp.blogspot.com/_DoTb4t-DV0c/SXSN3ES2T0I/AAAAAAAAATU/YA-pUHGhW7M/s1600-h/p8.pngдето трябва да видим нашето приложение



събота, 17 януари 2009 г.

Създаване на прозорчета в Python

„ Програмиране с Python“, ФМИ
Нено Ганчев
04.06.2007г.
Как да си създадем прозорци с Python?

* За щастие не се налага да си рисуваме сами прозорците по екрана, за това си има специализирани инструменти
* Има няколко инструмента за Python които ни позволяват да си правим шарени програмки
* Днес ще се запознаем с wxPython - порт на C++ библиотеката wxWidgets за Python

Откъде да започнем?

1. Първо да инсталираме магическата пръчица за прозорци - питонския модул wx
* сваляме си инсталацията, демотата и документацията от http://wxpython.org/
* инсталацията е в готови изпълними файлове, но за ентусиастите има и source-ове
2. Не е лоша идея преди да почнем да си правим собствено приложение първо да погледнем примерните програми и overview частта в документацията
3. Има и страхотно демо в което можете да видите примери за всички възможности които wxPython ви предоставя както в код, така и нагледно(незаменим помощник по време на разработка:)
4. Недостатък на документацията - има я само в C++ вариант със забележки за wxPython

Основни концепции в wxPython

1. Йерархия на класовете, представляващи прозорчетата по екрана
* Основни класове: wxApp, wxWindow, wxFrame, wxDialog, wxPanel
* Класове-контроли: всевъзможни бутончета, текстови полета, менюта и т.н.
2. Обработка на събития(event handling) и main loop

Основните класове отблизо: wxApp

wxApp има няколко метода които трябва да се дефинират или извикат:

1. OnInit()
* инициализира графичното ни приложение(играе ролята на конструктор)
* трябва да върне True, иначе wxPython няма да покаже приложението
2. SetTopWindow(window)
* Казва на програмата кой от всичките прозорци е главен
* Програмата може да има няколко главни прозореца
* Главните прозорци са специални
3. MainLoop()
* стартира главния цикъл за прехващане на събития
* без него главния прозорец може и да се покаже но ще стои "замръзнал" и няма да иска нито да си играе с нас, нито да си ходи
4. OnExit()
* правилното място за деинициализация на потребителски данни
* вика се след като потребителя е казал че ще затваря програмата, но преди вътрешните структури на wxPython да се деинициализират и да не можете да ги ползвате

Основните класове отблизо: wxWindow

* wxWindow e базовия клас на всички прозорци по екрана
* под прозорци се разбира всеки видим обект на екрана(менюта, бутони, диалогови прозорци, текстови полета, статични надписи, вдлъбнати линийки и т.н.)
* поема memory management`а на своите деца
* сам по себе си не предлага никаква полезна за диалог с потребителя функционалност
* не работите директно с обекти от този тип а с производните му wxFrame и wxDialog

Основните класове отблизо: wxFrame

* прозорец, чиито размери и положение могат да бъдат променяни от потребителя
* притежава "бордюр" и лента за заглавието(titlebar)
* може да съдържа всякакви прозорци с изключение на wxFrame и wxDialog(например лента с менюта, лента с инструменти(toolbar) и лента за състоянието(statusbar))

Основните класове отблизо: wxDialog

* прозорец с лента за заглавието и евентуално системно меню в нея
* може да бъде местен из екрана от потребителя
* може да съдържа всякакви контроли вътре в себе си
* обикновено се използва за предоставяне на възможност за избор на потребителя
* има едно специално свойство: може да блокира изпълнението на програмата и възможността на потребителя да взаимодейства с други прозорци докато не направи необходимия избор в диалоговия прозорец

Основните класове отблизо: wxPanel

* Прозорец в който могат да се слагат контроли
* обикновено се съдържа в wxFrame за да не се поставят контролите директно в него
* подобен по външен вид и функционалност на wxDialog, но с удобството че може да се съдържа във всякакъв вид прозорец

Малко практика

Да видим как изглежда казаното досега като код:

* Първо наследяваме wxApp:


class MyApp(wx.App):
def OnInit(self):
frame = MyFrame(parent = None, title = "My own frame")
frame.Show()
self.SetTopWindow(frame)
return True


* След това си дефинираме MyFrame като наследник на wxFrame:


class MyFrame(wx.Frame):
def __init__(self, parent, title, id = -1):
wx.Frame.__init__(self, parent, id, title)
panel = wx.Panel(parent = self)
self.greeting = wx.StaticText(parent = panel, pos = (20, 20), label = "Hello pythoneers!")


* И накрая създаваме обект от MyApp:


app = MyApp()
app.MainLoop()


* Готово!

Малки по-надълбоко: обработване на събития

За да реагира на действията на потребителя, wxPython използва предварително зададени възможни събития( например натискане на бутон или преместване на прозореца) с които можете да асоциирате ваши функции които да обработват по желания от вас начин събитието. Това става като "свържете" някой прозорец с дадено събитие, което е възникнало в него, и с вашата функция която ще го обработи

За удобство събитията не се виждат само от прозореца в който са възникнали, а и от всички прозорци нагоре в родителското дърво. Това означава че ако в панел имате няколко бутона, не е нужно събитието "натискане на бутон" да се прехваща във всеки бутон, може директо да ги прехванете в панела.

Това става като извикате метода Bind на прозореца в който искате да прехванете събитието и му подадете типа събитие, функцията която ще се извика, и източника на събитието
Да си добавим меню в примера

В конструктора на MyFrame добавяме:


menuBar = wx.MenuBar()
menu = wx.Menu()
menu.Append(self.ID_Exit, "E&xit")
menuBar.Append(menu, "&File")
menu = wx.Menu()
menu.Append(self.ID_About, "&About")
menuBar.Append(menu, "&Help")
self.SetMenuBar(menuBar)


Свързваме менютата с наши функции които искаме да се изпълнят когато менюте се избере:


self.Bind(wx.EVT_MENU, self.OnExit, id = self.ID_Exit)
self.Bind(wx.EVT_MENU, self.OnAbout, id = self.ID_About)


И добавяме handler`ите ни:


def OnExit(self, event):
answer = wx.MessageBox("Do you really want to quit?", "Confirm", style = wx.YES_NO | wx.ICON_QUESTION)
if answer == wx.YES: self.Close()

def OnAbout(self, event):
wx.MessageBox("wxPython greeter program", "About", style = wx.OK)


Как да си подредим бутончетата в прозореца

При слагането на повече контроли в един прозорец става малко трудно да ги наредите всички така че да не си пречат. Указването на точна позиция(в координати) на всеки е дейност, достойна за наказание в програмисткия ад. Едно решение на проблема са така наречените sizers

Sizer-ите разчитат на това че в повечето случаи разположението на обектите в прозореца следва прости геометрични правила като например в колона, редица или таблица. Използването им е съвсем просто - при създаване на прозорец създавате и sizer който ще отговаря за него, добавяте всички деца на прозореца в sizer-а и накрая не забравяте да кажете на прозореца кой му е sizer-a. След това sizer-а автоматично поема отговорността за разположението на обектите в прозореца

При добавяне на обекти в sizer-а можете да укажете дали обекта ще се разширява при разширяване на целия прозорец и в какво съотношение в сравнение с останалите обекти в прозореца
Да позапълним прозореца

Засега прозореца в приложението ни е много пуст и на самотния низ му е много скучно, а и му е писнало да поздравява само едни и същи хора. Нека му разнообразим съществуването: ще добавим поле в което да се казва кого ще поздравява програмата ни и бутон с който да поздравим въведеното същество

За целта създаваме BoxSizer в конструктора на MyFrame и добавяме текста и новите контроли в него


self.textField = wx.TextCtrl(panel, -1)
self.greetButton = wx.Button(panel, ID_Greet, "Greet")
buttonSizer = wx.BoxSizer(wx.HORIZONTAL)
buttonSizer.Add(wx.StaticText(parent = panel, label = "Enter name:"), 0, wx.ALL, 10)
buttonSizer.Add(self.textField, 1, wx.ALL | wx.EXPAND, 10)
buttonSizer.Add(self.greetButton, 0, wx.ALL, 10)
panelSizer = wx.BoxSizer(wx.VERTICAL)
panelSizer.Add(self.greeting, 1, wx.ALL | wx.EXPAND | wx.CENTRE, 10)
panelSizer.Add(buttonSizer, 0, wx.ALL | wx.EXPAND, 10)

panel.SetSizer(panelSizer)
panel.Fit()

self.Bind(wx.EVT_BUTTON, self.OnGreet, self.greetButton)

frameSizer = wx.BoxSizer(wx.VERTICAL)
frameSizer.Add(panel, 1, wx.EXPAND)
self.SetSizerAndFit(frameSizer)


Да позапълним прозореца 2

Остана да добавим handler-а за новия бутон:


def OnGreet(self, event):
name = self.textField.GetValue()
if len(name) == 0:
wx.MessageBox("Enter a name to greet first", "Error", style = wx.OK | wx.ICON_EXCLAMATION)
return
self.greeting.SetLabel("Hello %s!" % name)
self.Refresh()
self.Update()

Някои добри практики в Python

„ Програмиране с Python“, ФМИ
Стефан Кънев & Николай Бачийски
30.05.2007г.
Какво прави следния код?


for x in range(0, 12):
print arr[x]

А този?


fox index in range(0, NUMBER_OF_MONTHS):
print monthNames[index]

Писането на код като работа

Когато пишете код, рядко го правите самоцелно. Вместо това, вие опитвате се да решите някакъв реален проблем със собствена логика, терминология и особености. Винаги когато пишете код трябва да се стараете той да отразява много добре този проблем - това го прави много по-четим, по-лесен за поддръжка и по-разбираем от външни хора. Още повече, така вие ясно показвате намерението което вашия код има, вместо да карате читателя да задълбава в особенностите на вашата реализация.
Първо правило: Добри имена на променливи

Променливите обикновенно отговарят за съществуващи обекти/концепции в реалния проблем, който решавате. Това ги прави идеални за комуникиране на идеята на кода. За целта, обаче, се налага да избирате смислени имена.

* Използвайте смислени имена, които да показват ясно и недвусмислено за какво служи променливата
* Спазвайте конвенция в именуването на нещата
* Избягвайте думи, в които лесно се допускат правописни грешки
* Избягвайте криптични съкращения или дълги имена - numberOfPeopleOnTheUsOlympicTeam, npot, teamMemberCount
* Избягвайте като цяло безсмислени имена - thing, stuff, foo
* Бъдете консистенти в наименоването на променливите - без shipsCount и numDocks в една програма.
* Не използвайте една променлива два пъти за едни и същи неща.

Типична грешка


# Грешно
temp = sqrt(b ** 2 - 4 * a * c)
x1 = (-b + temp) / (2 * a)
x2 = (-b - temp) / (2 * a)

# По-правилно
discriminant = sqrt(b ** 2 - 4 * a * c)
x1 = (-b + discriminant) / (2 * a)
x2 = (-b - discriminant) / (2 * a)

Лоши имена...


old = readOld()
tupple = getValues(r'c:\')
tup = {}
for t in tupple:
if old[t] != tupple[t]: continue
tup.update({t:tupple[t]})

show(tup)
save(tupple)

...и добри имена


oldHashsums = readCachedHashsums()
newHashsums = findHashsums(r'c:\')

changedFiles = {}
for filename in oldHashsums:
if oldHashsums[filename] != newHashsums[filename]:
changedFiles[filename] = newHashsums[filename]

reportChanges(changedFiles)
saveHashsums(newHashsums)

Функции / методи / рутини

Рутините са едно от най-често използваните средства в програмирането. И все пак, причините за които има смисъл да създавате рутина са.

* Опростяване на кода.
* Избягване на повтаряне на код.
* Скриване на последователни действия.
* Разширяемост.
* Подобряване на производителността.
* За по-гъвкаво наследяване.
* Изолиране на сложността.
* Скриване на имплементационни детайли.
* Като цяло: за създаване на абстракция.

Именуване на рутини

При именуване на рутини се съобразявайте внимателно със следните неща.

* Да обяснява всичко което рутината прави
* Избягвайте безсмислени и размити имена - doStuff(), generateData(), processInput().
* Не различавайте две рутини само по имена - wait2() и wait3().
* Ако рутината връща стойност, кръстете я така че да описва връщаната стойност
* Ако рутината е "процедура", използвайте глагол в името й, който да описва действието й.
* Използвайте противоположни имена - add/remove, open/close, get/set - консистентно.

Кохезия

"Кохезията" на една рутина смътно описва действието й. Като говорим за "добра кохезия" имаме предвид, че една рутина прави едно единствено нещо и го прави добре. Най-силния вид "кохезия" е функционалната. Други видове са:

* Последователна кохезия - рутината капсулира няколко действия, които трябва да се направят последователно.
* Комуникационна кохезия - рутината извършва няколко различни операции над едни и същи данни
* Времева козехия - рутината извършва няколко действия, които трябва да станат едновременно - Startup(), Shutdown()

Неприемлив вид кохезия

* Процедурна - когато рутината е създадена само защото това отговаря на последователността в която потребителя извършва действията.
* Логическа - поведението на рутината зависи силно от стойността на някой параметър.
* Случайна - когато действията в рутината не са особено свързани.

Аргументи на рутините

* Действието на една рутина не трябва да зависи от стойностите на неин аргумент.
* Старайте се да не ползвате повече от 7 (седем) аргумента в една рутина
* Когато извиквате рутина с много аргументи, хубаво е да ползвате възможността на Python да предава аргументите наименовано.
* Не променяйте състоянието на параметрите на функциите, ако може да го избегнете.
* Ако ползвате параметри за изход, тогава избягвайте да ги ползвате и като входни.
* Ако евентуално имате нужда от параметри за вход, вход-изход и изход, подреждайте ги консистентно в програмата си.

Реакция при грешни параметри на рутината

Лош подход.


def storePerson(name, age, address):
if not isinsntace(name, str) or len(name) < 6:
raise ValueError("Illegal name")
if not isinstance(age, int, long) or age < 0:
raise ArithmeticError("Illegal age")
if not hasattr(address, "street") or not hassattr(address, "number"):
raise ValueError("Illegal address")

storeData(name, age, address.street, address.number)

Реакция при грешни параметри на рутината (2)

По-добър подход.


def storePerson(name, age, address):
assert long(age) >= 6
assert isinstance(name, str) and len(name) >= 6

storeData(name, age, address.street, address.number)

Не ползвайте глобални променливи

Когато пиеше функции, не ползвайте глобални променливи. Ама въобще. Най-честия случай на неправилно ползване на глобавни променливи е когато се употребяват за комуникация между функции. Никога не правете това. В рядките случаи, в които имате нужда от "глобални" обекти правете Singleton-и или thread.local-и.
Не ползвайте goto

В Python няма goto. Ако случайно пишете на друг език, в който има goto, това правило остава - не ползвайте goto.
Не използвайте глупави низове
в съобщенията за грешка
Основната задача на програмиста: да намалява сложността

Подходи за намаляване на сложността:

* Модуларизация
* Абстракия
* Енкапсулация

Нишки в Python

„ Програмиране с Python“, ФМИ
Стефан Кънев & Николай Бачийски
28.05.2007г.
Многозадачност

Класически проблем е да накараме една програма да върши няколко неща едновременно. Има два стандартни подхода в повечето езици - fork-ове и нишки. Както можете да очаквате, те се срещат в Python в модулите os и threading.
fork

fork е функция, чието изпълнение създава ново копие на програмата, което продължава от същото място където е извикана функцията. Досега заделените ресурси са общи за двата процеса, докато новите са локални. Създава се нов процес на ниво операционна система. В родителския процес функцията връща id-то на дъщерния, докато във дъщерния връща 0.


import os

print "pre-forking"
pid = os.fork()
print "post-forking", pid

pre-forking
post-forking 0
post-forking 6450

fork (2)

Отделни процеси могат да си комуникират по-успешно с други функции в модула os. Като цяло fork е твърде примитивен подход когато имате нужда от конкурентност в приложението и в почти всички случаи е много по-добре да използвате по-високо ниво на абстракция - нишки.
thread и threading

Python предлага два модула за работа с нишки - thread и threading. Втория предлага по-високо ниво на абстракция и по-удобен интерфейс. Съответно е по-логичния избор от двата модула, ако нямате смислена причина да предпочетете thread.
threading.Thread

Класът Thread ви дава прост механизъм да изпълнявате код в нова нишка. Нужно е да предоставите метод run, който да съдържа кода на нишката. След това я инстанцирате и викате методът и start.


import threading

class MyThread(threading.Thread):
def run(self):
for i in range(0, 10000): print i

thread = MyThread()
thread.start()
for i in range(0, 10000): print -i

theading.Lock

Стандартен проблем при конкуренти задачи са "критични секции" от програмата - такива, които трябва да бъдат изпълнявани само веднъж в даден момент. Това може да се постигне с класът Lock. Неговите инстанции имат два метода - acquire() и block(). Класът вътре пази флаг дали lock-а е свободен или зает. Ако acquire() се извика на свободен lock, флага се вдига и lock-а става зает. Ако acquire() се извика на зает lock, метода блокира докато lock-а не се освободи. Извикването на release() освобождава lock-а.
theading.Lock (2)


import threading

lock = threading.Lock()

class Gatherer(threading.Thread):
def run(self):
while True:
foundStuff = gather()
lock.acquire()
for thing in foundStuff: report(thing)
lock.release()


g1, g2 = Gatherer(), Gatherer()
g1.start()
g2.start()

threading.Lock (3)

Можете да използвате with с Lock.


from __future__ import with_statement
import threading

lock = threading.Lock()
with lock:
print "Do stuff"

threading.Semaphore

Semaphore представлява механизъм за разрешаване на конкурентен достъп, много подобен на Lock разликата е там, че вместо флаг, вътрешно се пази брояч. acquire() го намалява, докато release() го увеличава. acquire() се разблокира, когато брояча е по-голям от нула и блокира, ако той е нула.
threading.Event

Event е проста комуникация между нишки, в която няколко нишки изчакват една да завърши с някакъв резултат. Нишките които изчакват викат метода wait() и изпълнението им блокира. Нишката, която продуцира резултата извиква метода set(). В този момент всички нишки които чакат продължават изпълнението си.
threading.Condition

Condition е много сходен с Lock, но дава няколко нови метода. wait() освобождава lock-а, след което блокира докато не се събуди с извикване на notify() или notifyAll(). Като се "събуди", отново взема lock-а. notify() събужда една нишка, заспала с wait(). notifyAll() събуджа всички.


# Consume one item
cv.acquire()
while not an_item_is_available():
cv.wait()
get_an_available_item()
cv.release()

# Produce one item
cv.acquire()
make_an_item_available()
cv.notify()
cv.release()


threading.local

Инстанциите на local представляват обекти, които са "локални" за дадена нишка. Тяхните атрибути са уникални за всяка нишка. Например:


import threading
foo = threading.local()

foo.blah = 1

class MyThread(threading.Thread):
def run(self):
if (hasattr(foo, 'blah')): print "MyThread sees foo.blah"
foo.blah = 4
print "MyThread(foo.blah) = ", foo.blah

MyThread().start()
print "BaseThread(foo.blah) = ", foo.blah

Метакласове в Python

„ Програмиране с Python“, ФМИ
Стефан Кънев & Николай Бачийски
11.04.2007г.
Метакласове

Метакласовете са инструмент, който ще ползвате в много малко случаи и клиента на вашия код винаги ще е друг програмист. Те помагат за създаване на по-устойчиви и използваеми абстракции, а не решават проблеми директно. Въпреки това, те имат големи възможности и можете да постигнете с тях много неща по елегантен начин. Имайте предвид, че всичко което можете да постигнете с метакласове може да стане и със стандартни способи.
Концепцията

Метакласовете представляват "типове" на класовете. Тези типове определят механиката и правилата на които класът се подчинява. Пример биха могли да бъдат класовете нов и стар стил, които работят по различни правила. Но като нищо може да си измислим и наши типове класове. Например:

* singleton-и
* класове, чиито методи при грешка просто връщат None
* класове, чиито методи получават self неявно

Singleton


class DatabaseConnector(object):
self = None
def __new__(cls):
if not DatabaseConnector.self:
DatabaseConnector.self = object.__new__(cls)
return DatabaseConnector.self

class Mailer(object):
self = None
def __new__(cls):
if not Mailer.self:
Mailer.self = object.__new__(cls)
return Mailer.self

mailer1 = Mailer()
mailer2 = Mailer()
print mailer1 is mailer2 # True

Същото, но с метакласове


class singleton(type):
def __new__(cls, name, bases, classDict):

def getInstance(klass):
if not klass.self:
klass.self = object.__new__(klass)
return klass.self

classDict['self'] = None
classDict['__new__'] = getInstance

return type.__new__(cls, name, bases, classDict)

class Mailer(object):
__metaclass__ = singleton

mailer1 = Mailer()
mailer2 = Mailer()
print mailer1 is mailer2

Класове, чиито методи не хвърлят грешки


def silent(func):
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except:
return None
return wrapper

class Something(object):
@silent
def foo(self): assert false

@silent
def bar(self): print belch

@silent
def omgTooMuchDecorators(self): pass

something = Something()
something.foo()

Отново, с метакласове


def silent(func):
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except:
return None
return wrapper

class silentType(type):
def __new__(cls, name, bases, classDict):
for name, attr in classDict.items():
if callable(attr):
classDict[name] = silent(attr)

return type.__new__(cls, name, bases, classDict)

class Something(object):
__metaclass__ = silentType
def foo(self): assert false
def bar(self): print belch
def omgTooMuchDecorators(self): pass

something = Something()
something.foo()
something.bar()

Класове без self


class Person(object):
__metaclass__ = selfless
def __init__(name, age):
self.name = name
self.age = age

def __repr__():
return "Person(%s, %d)" % (repr(self.name), self.age)

def sayHi():
print "Hello, I'm %s and I'm %d years old" % (self.name, self.age)

def rename(newName):
self.name = newName

Класове без self (2)


from types import FunctionType

class selfless(type):
def __new__(cls, name, bases, classDict):
for attr in classDict:
if not isinstance(classDict[attr], FunctionType): continue
classDict[attr] = selflessWrapper(classDict[attr])
return type.__new__(cls, name, bases, classDict)

def selflessWrapper(func):
def wrapper(self, *args, **kwargs):
hadSelf, oldSelf = func.func_globals.has_key('self'),
func.func_globals.get('self')
func.func_globals['self'] = self
returnValue = func(*args, **kwargs)
if hadSelf:
func.func_globals['self'] = oldSelf
else:
del func.func_globals['self']
return returnValue
wrapper.func_name = func.func_name + "_wrapper"
return wrapper

Пак Метакласове в Python

„ Програмиране с Python“, ФМИ
Стефан Кънев & Николай Бачийски
16.05.2007г.

[Metaclasses] are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don't (the people who actually need them know with certainty that they need them, and don't need an explanation about why).

Tim Peters

Какво е метаклас?


Метаклас — клас, чийто инстанции са класове.


Забравете предишнoto твърдение.
Метапрограмиране

* Meta (from Greek: μετά = "after", "beyond", "with"), is a prefix used in English in order to indicate a concept which is an abstraction from another concept, used to complete or add to the latter.
* Програмиране за програмирането.
* Програмиране, при което клиентите са програмисти.
* При ООП:
o разширяване на езика с нови ”типове класове“
o промяна на природата на обектите
+ добавяне на функционалност (методи от метакласа например)
+ създаване на обекти, които приличат на класове (от които могат да се правят нови инстанции)
+ налагане на ограничения върху класовете
o автоматизиране на сложни задачи
+ регистриране на новосъздадени класове в някаква система

Метакласовете като инструмент за метапрограмиране

* Почти всичко може да се направи и без метакласове:
o factory класове
o функции, които обработват класа след създаването му
o функции, които регистрират новосъздадения клас в системата
* С метакласове просто е по-лесно и по-елегантно
* Използват се вътрешно в типовата система на Python

Употреба: регистрация/търсене

Искаме да видим всички класове, които са се регистрирали в нашата система.

Пример: имаме зоологическа градина с цяла йерархия от класове, като позволяваме на програмистите да дефинират нови класове от животни. Как да видим всички класове животни? Как да видим всичко животни, които могат да плуват? Как да намерим всички животни наследник на класа Риба?
Употреба: проверки при създаване на класове

* Гаранция, че класът удовлетворява определен интерфсейс
* Проверка за стил на кода:
o наличие на docstring-ове
o имена на методи — getName срещу get_name
o дълбочина на наследяването — ≤ 5
* Всякакви други проверки, които ще гарантират, че нмовосъздадения клас отговаря на изискванията на вашата система

Внимавайте да не сте много строги.
Употреба: създаване на специални класове

* обвиване на методи (pre/post conditions)
* добавяне, преименуване на методи
* зареждане на методи/родители от някъде:
o зависещи от системата: разширения (plugins)
o XML схеми и DTD

В Python

* при създаване на тялото на клас се гледа стойността на __metaclass__
* по подразбиране метакласа е type или types.ClassType (стар стил)
* метаклас може да се наследи и от родителски клас

Какво може/трябва да има един метаклас?

* (Мета)класовете са частен случай на обикновени обекти — с техните методи, свойства
* Всичко основно може да се наследи от type и за нас не остава кой знае колко за добавим
* Най-често се променят:
o Инициализатори: __new__, __init__
o Представяне на класовете като низове
o Атрибути и свойства на новия клас (включително методи:)
* Не могат да се ползват за промяна на логиката на:
o търсене в пространсвата от имена
o MRO (Method Resolution Order)
o Дескрипторите (за тях по-нататък)

Регулярни изрази в Python


„ Програмиране с Python“, ФМИ

28.02.2007г.

Идеята

Регулярните изрази се използват за търсене и заместване в текст. Те предлагат апарат, с който дефинирате шаблони, описващи последователност от символи, след което да проверявате дали даден текст съдържа съответния шаблон. Основават се на идея от дискретната математика, макар че реализацията им в езиците за програмиране е доста по-сложна.

Прост пример

  • на pam съответстват spam и pamela
  • на h(ot|ound) dog съответстват hot dog и hound dog
  • на fn\d+ съответстват fn1, fn44444, fn420169 и т.н.
  • на .at съответстват hat, cat, rat и т.н.
  • на [a-zA-Z0-9._]+@[a-aA-Z0-9._]+\.[a-z]+ съответстват повечете email адреси

В детайли

  • Регулярните изрази представляват текстови низове
  • Буквите и цифрите съответстват на себе си
  • Точката съответства на всеки символ, без новия ред
  • \d съответства на цифра
  • \w съответства на буква
  • \s съответства на бяло пространство
  • \D, \W и \S са инверсии на съответните символи

В детайли (2)

  • \d\d\d\d\d\d\d съответства на ЕГН
  • \w\w\w\d\w\w\d\d съответства на три букви, цифра, две букви и после две цифри (например ani4ka92)
  • fn\d\d\d\d\d съответства на част от факултетните номера
  • M\w\w\w\w\sT\w\w\sP\w\w\w\w\w би хванало Mityo The Python
  • s..m съответства на spam и sram

В Python

В Python се ползва модулът re

import re

>>> import re
>>> re.search('spam', 'gmail')
>>> re.search('s..m', 'spam')
<_sre.sre_match>
>>> re.search('\\d\\d', 'asnwer: 42')
<_sre.sre_match>

Още детайли

  • Операторът | в регулярните изрази означава това отляво или това отдясно
  • (c|h)at намира cat и hat
  • ^ съвпада с началото на низа. ^oo ще намери oop, но не и foo.
  • $ съвпада с края на низа. ar$ ще намери bar, но не и argh.

Повторения

  • ? означава, че да го има 0 или 1 пъти
  • + означава, че нещо може да го има един или повече пъти
  • * означава, че нещо може да го има нула или много пъти
  • {x, y} означава, че нещо може да го има от x до y пъти
Примери:
\d{8}

fn\d+
\d{1,2}\.\d{1,2}.\d{4}
route\d*

Класове

  • Класовете съответстват на някакво множество от символи.
  • Бележат се с [].
  • [chr]at съответства на cat, hat и rat.
  • Могат да включват и последователности от символи - [a-z0-8] съответства на малка буква или цифра, без 9
  • Ако започва с ^, класът се интерпретира като отрицание на символите вътре - [^A-Z] съответства на нещо, което не е голяма буква

Област

Върнатия от search обект има методи start, end и span.



>>> import re
>>> text = 'Bla bla bla mityo blabla bla'
>>> match = re.search(r'mityo', text)
>>> match.span()
(12, 17)
>>> match.start(), match.end()
12, 17
>>> text[match.start(), match.end()]
'mityo'

Групи

Всички изрази оградени със скоби се запазват в обекта върнат от re.search. Те се наричат групи



>>> import re
>>> match = re.search(r'\w+ (\w+), fn(\d+)',
'Nickolay Bachiiski, fn43600')
>>> match.group(1)
'Bachiiski'
>>> match.group(2)
'43600'

Субституции

Всички изрази оградени със скоби се запазват в обекта върнат от re.search. Те се наричат групи



>>> import re
>>> re.sub(r':(.*)!', r'!\1?', "I'm a :very big! hacker!")
"I'm a !very big hacker?"

Алчни "квантификатори"

+, * и {x,y} намират максималния брой символи, които могат. Това се нарича "алчно поведение" (greedy). Ако искате да намирате минималния брой, добавете едно ? към края



>>> import re
>>> re.sub(r'(.*)', r'\1',
'My name is Mityo the Python')
'My name is Mityo the Python'

>>> re.sub(r'(.*?)', r'\1',
'My name is Mityo the Python')
'My name is Mityo the Python'

Още за типовете и обектите в Python


„ Програмиране с Python“, ФМИ

Стефан Кънев & Николай Бачийски

14.05.2007г.

За какво ще става дума

  • Разпознаване на типове
  • Копиране на обекти
  • Наследяване на вградени типове

type

  • typeвръща специален обект от тип тип, стойност годна само за директни проверки
  • def update(val):
    if type(val) == type(0):
    _update(val)
    else:
    raise ValueError("Cannot update non-integer!")
  • >>> type('baba')
  • >>> type([])
  • >>> type(type('sugrug'))

Модулът types

  • Ако става дума за вграден тип, type връща обект, който може да бъде намерен в модула types

  • from types import *
    def update(val):
    if type(val) == IntType:
    _update(val)
    else:
    raise ValueError("Cannot update non-integer!")
  • В този модул може да намерите повечето вградени типове, като имената им са с главни букви и имат Type отзад: DictType, ListType, TupleType, BuiltinFunctionType и т.н.

Копиране на обекти

  • a = b не копира стойностите, а насочва a и b към един и същи обект!
  • Идиом за плитко копиране на списъци: newlist = l[:]
  • Още един идиом за плитко копиране: newlist = [x for x in l], прави същото като горния
  • names = 'a b c'.split()
    grades = [1, 2, 3]
    both = [names, grades]
    shallowCopy = both[:]
    names.append('d')
    print shallowCopy[0]
  • Така копираме елементите само на едно ниво.

Плитко копиране на произволни обекти

  • Не винаги си имаме работа със писъци, понякога не е толкова лесно да копираме един обект.
  • На помощ ни идва вградения модул copy
  • import copy
    class C:
    def __init__(self, l): self.l = l
    def __getitem__(self, i): return self.l[i]
    items = [1, 2, 3]
    c = C(items)
    shallow = copy.copy(c)
    print c[2], shallow[2]
    (3, 3)
    items.append(4)
    print c[3], shallow[3]
    (4, 4)
    shallow.l = [42]
    print c.l, shallow.l
    ([1, 2, 3, 4], [42])

Дълбоко копиране

  • Защо е необходимо?
  • import copy
    items = [1, 2, 3]
    d = dict(items=items)
    shallow = copy.copy(d)
    deep = copy.deepcopy(d)
    items.append(4)
    print shallow['items'], deep['items']

В картинки (1)

items = [1, 2, 3]
d = dict(items=items)
shallow = copy.copy(d)
deep = copy.deepcopy(d)

state and references before the items modification

В картинки (2)

items.append(4)

state and references after the items modification

Наследяване на вградени типове

  • Цел: създаване на обекти, които приличат на такива от някой вграден тип (речник, списък, низ)
  • Разширяване функционалността на вградени типове — речник, чийто ключове могат да се достъпват и като атрибути
  • Удобство — ако обектът ни има поведение на списък, по-добре да ползваме познатия писъчен протокол, вместо да измисляме нови методи

Без наследяване

  • Можем да постигнем целта си и без наследяване
  • Ако имате желание да дефинирате сами всички методи на list, огромна част, от които се припокриват като функционалност с тези от вградените списъци
  • Понякога е удачно: трябва ни само индексиране([], __getitem__) и не искаме да си замърсяваме класа с всички методи на list

Старият начин

Преди 2.3 не е било възможно стандартните класове като list, dict, str да се наследяват. Използвали са се няколко стандартни модула, които са предоставяли специални класове, които да бъдат наследявани:

  • UserDict.UserDict — речник, който не поддържа итерация (за съвместимост със стари версии)
  • UserDict.IterableUserDict — речник, който си поддържа и итерация
  • UserDict.DictMixin — добавя стандартните методи на класове, които вече поддържат стандартния интерфейс: __getitem__(), __setitem__(), __delitem__(), keys().
  • UserList.UserList
  • UserString.UserString
  • UserString.MutableString — малко по-различен низ, може да бъде променян в движение

Всички си имат по един атрибут data, в който се пазят реално данните.

Пример: речник с атрибути

class AttrDict(dict):
def __getattr__(self, attr):
if attr in self:
return self[attr]
else:
raise AttributeError("exists neither an attribute nor a key called '%s'." % attr)

if __name__ == "__main__":
d = AttrDict(a = 5, get = 6)
print d.a
print d.get
print d.baba

Частичен пример: низове с case-insensitive сравнения

def lowerFuncs(*names):
def lowerFunc(clsname, bases, classDict):
for name in names:
def comparator(self, other, *args, **kwargs):
return getattr(str, name)(self._lower, other.lower(), *args, **kwargs)
classDict[name] = comparator
return type(clsname, bases, classDict)
return lowerFunc

class CIStr(str):
__metaclass__ = lowerFuncs('__eq__', '__le__', '__lt__', '__ge__', '__gt__')

def __init__(self, *args, **kwargs):
str.__init__(self, *args, **kwargs)
# string will never change, so calc once
self._lower = self.lower()
def startswith(self, prefix, *args, **kwargs):
return str.startswith(self.__lower, prefix.lower(), *args, **kwargs)
def index(sub, *args, **kwargs):
return str.index(self.__lower, sub.lower(), *args, **kwargs)
… # и така нататък...

if __name__ == "__main__":
s = CIStr("Baba")
print s == "baba"

Сериализация в Python


„ Програмиране с Python“, ФМИ

Стефан Кънев & Николай Бачийски

11.04.2007г.

Проблем

Имаме нужда да пазим информация между две изпълнения на едно и също приложение. Искаме достъпа до тази информация да е лесен и да добавя минимално код в самото приложение. Нуждите на приложението са прости и няма да имаме нужда от конкурентен достъп или заключване на ресурси.

Решение

Сериализация се нарича възможността да представяме обекти като поредица от байтове. По този начин те могат да бъдат записвани във файловата система или изпращани през TCP/IP и реконструирани в последствие.

Pickle

pickle е модул, който може да сериализира прости Python обекти.

pickle.dump(object, file)
Приема отворен за писане файл file и Python обект object. Записва обекта в файла.
pickle.load(object)
Приема отворен за четене файл и прочита един обект, който е и резултат от функцията

Pickle (2)


from __future__ import with_statement
import pickle

with open('/home/aquarius/foo.txt', 'w') as file:
pickle.dump("The answer", file)
pickle.dump(["spam", "eggs", "ham"], file)

with open('/home/aquarius/foo.txt', 'r') as file:
print pickle.load(file)
print pickle.load(file)

Какво представлява сериализацията?

Pickle сериализира обектите с един от три различни протокола. Протокола по подразбиране представя обектите като ASCII низове. Функциите dumps и loads връщат сериализирана форма вместо да я записват във файл.


import pickle

serializedList = pickle.dumps(["spam", "eggs", "ham"])
print repr(serializedList)
"(lp0\nS'spam'\np1\naS'eggs'\np2\naS'ham'\np3\na."

Какво може да се сериализира?

  • True, False и None
  • int, long, float и complex
  • str и unicode
  • Списъци, речници, множества и n-орки, които съдържат само обекти, които могат да се сериализират
  • Функции и класове, дефинирани в корена на модула
  • Вградени функции, дефинирани в корена на модула

Сериализиране на един обект два пъти

Ако сериализирате сложна структура, в която един обект присъства два пъти (по адрес), pickle ще го сериализира еднократно.


import pickle

primes = [2, 3, 5, 7, 11, 13]
answer = 42
things = [primes, answer, primes]
print things[0] is things[2] # True

serialized = pickle.dumps(things)
newThings = pickle.loads(serialized)
print newThings[0] is newThings[2] # True

print newThings[0] is things[0] # False

Pickler и Unpickler

Имате достъп до същата функционалност под формата на класове.


from __future__ import with_statement
import pickle

with open('/home/aquarius/foo.txt', 'w') as file:
pickler = pickle.Pickler(file)
pickler.dump('Under the Violet Moon')
pickler.dump('Soldier of Fortune')
pickler.dump('Colubrid on the Tree')

with open('/home/aquarius/foo.txt') as file:
unpickler = pickle.Unpickler(file)
print unpickler.load()
print unpickler.load()
print unpickler.load()

cPickle

cPickle e реализация на pickle на C, което я прави доста по-бърза.


from __future__ import with_statement
import cPickle

with open('/home/aquarius/foo.txt', 'w') as file:
cPickle.dump("The answer", file)
cPickle.dump(["spam", "eggs", "ham"], file)

with open('/home/aquarius/foo.txt', 'r') as file:
print cPickle.load(file)
print cPickle.load(file)

cPickle (2)

За сравнение

import pickle
import cPickle
from timeit import Timer

fancy = [[x ** 2 for x in range(0, 1000)], ["spam", "ham", "eggs"],
["-" * i for i in range(0, 1000)],]

def pickleWith(pickler):
serialized = pickler.dumps(fancy)
restored = pickler.loads(serialized)

print "pickle: ", Timer("pickleWith(pickle)", "from __main__ import *").timeit(50)
print "cPickle: ", Timer("pickleWith(cPickle)", "from __main__ import *").timeit(50)

# pickle: 3.28504800797
# cPickle: 0.722439050674

shelve

shelve е сравнително прост модул, който позволява да записвате данни във файл под формата на речник. Можете да ги достъпвате в последствие по ключовете, с които сте ги записали. Всички обекти се сериализират посредством pickle, докато организацията им във файла става посредством dbm, gdbm или berkeley db.

shelve (2)


import shelve

db = shelve.open('/home/aquarius/foo.db')
db['name'] = 'Mityo the Python'
db['age'] = 33
db['favouriteBands'] = ["Blackmore's Night", "Deep Purple", "Rainbow"]
db['favouriteSong'] = "Colubrid on the Tree"
db.close()

db = shelve.open('/home/aquarius/foo.db')
print db.keys()
print db['name']
print db['favouriteBands']
print db['favouriteSong']
db.close()

Още тестване и документация - Python


„ Програмиране с Python“, ФМИ

Стефан Кънев & Николай Бачийски

16.02.2007г.

Примерно пакетче с модули

Нека разгледаме модула world

  • __init__.py
  • destroyer.py
  • god.py

Прости тестове: test_world.py

  • клас наследник на unittest.TestCase
  • всеки негов метод започващ с test е отделен сценарий
  • unittest.main се грижи да намери подходящите класове и да извика подходящите им методи
  • просто!

setUp и tearDown

  • понякога се налага да правим едно и също преди и след някой сценарий (метод на TestCase)
  • методът setUp на TestCase ще се извика преди всеки сценарий
  • методът tearDownще се извика след всеки сценарий
  • def setUp(self):
    self.w = world.World()
    def tearDown(self):
    self.w = None

Специален TestCase

  • world.destroyer.do отпечатва разни неща, които не искаме да виждаме по време на тестовете
  • можем да си направим наследник на unittest.TestCase, който има желаната функционалност
  • и ползваме негов наследник в случаите, в които ни трябва тази функционалност
  • Пример: world.test.testcases

Под капака на unittest

  • TestLoader
  • TestSuite
  • TestResult
  • TextTestRunner
  • Пример: world.test

Документация—Интернет

  • http://docs.python.org/ — обновена, удобна, ако знаете къде да търсите, това което ви трябва
    • Library Reference—систематизирано описание на функции, модули
    • Global Module Index—азбучен списък с подробни описания на модулите
    • Language Reference—синтактични правила, твърдения, оператори
  • често най-бързия начин да намерите, каквото ви е нужно е търсене в Google за python <функция/метод/клас/…>

Документация—на вашия компютър

  • Под Windows—Python Manuals в chm формат. Може да се търси, на практика в него е цялата HTML документация.
  • HTML документация—почти същата като на сайта (малко по-стара вероятно) и й лиспва търсене. Ще я намерите на собствения си компютър някъде, освен ако за вашата ОС не се разпространява отделно от самия Python.

pydoc

  • Под Windows: python.exe pydoc.py Трябва да сложите в пътя директориите PythonX.Y и PythonX.Y/Lib.
  • На другите места: pydoc
  • pydoc <име>—търси документация за тази функция, тема, модул ключова дума…
  • pydoc -k <ключова дума>—търси тази дума в описанията на всички модули
  • pydoc -p <порт>—пуска HTTP сървър на вашия компютър и можете да разглеждате HTML документацията като отворите с браузъра си http://localhost:<порт>/
  • pydoc -g—отваря графична програма, в която може да търсите по ключова дума

Документация на наши модули

  • Помните ли docstring-овете?
  • def f(x, y):
    """f makes you happy putting a x into your y."""
  • Стига вашия модул да се намира някъде в пътя за търсене на Pythоn
    • можете да погледнете в sys.path
    • PYTHONPATH
    • модулът site
  • при разглеждане с pydoc ще се вижда и документацията на вашите модули
  • pydoc -w —записва документация на диска в HTML формат

epydoc

  • гъвкава система за генериране на документация
  • поддържа много формати
  • не е в стандартната Python дистрибуция

Автоматизирано тестване в Python


„ Програмиране с Python“, ФМИ

Стефан Кънев & Николай Бачийски

11.04.2007г.

Disclaimer

Днес няма да си говорим за acceptance testing, quality assurance или нещо което се прави от "по-низшия" отдел във фирмата.

Митът

Проектът идва с готово задание, подробно задание. Прави се дизайн, с помоща на който работата се разбива на малки задачи. Те се извършват последователно, като не се налага да се връщате към някоя след като сте я приключили. Изискванията не се променят и не се налага добавянето на нова функционалност.

Митът v1.1

Щом съм написал един код, значи ми остава единствено да го разцъкам, да го пробвам в main метода/функцията и толкова. Няма да се променя така или иначе, а ако пък случайно ми се наложи да го пипам - аз съм го писал, знам го, няма как да допусна грешка. Най-много да го поразцъкам още малко.

Тежката действителност

  • Заданията винаги се променят.
  • Често се налага един код да се преработва.
  • Писането на код е сложна задача - могат да се направят много грешки.
  • Програмистите са хора - допускат грешки.
  • Или губите ужасно много време в разцъкване след всяка промяна, или рискувате да вкарате бъгове в приложението, които ще бъдат открити твърде късно.
  • Промяна в един модул в единия край на системата като нищо може да причини грешка в другия, без въобще да усетите.

Занятието


class Programmer(object):
...
def makeChange(self, project, change):
self.openAssociatedFiles(project, change)
while True:
self.attemptChange(change)
project.start()
result = self.clickAroundAndTest(project)
project.stop()
if result.sucessful(): break
self.commitCode(project)
self.hopeEverythingWentOK()

Идея

Това е автоматизирана задача. Всеки път повтаряме едно и също, отнема ни много време, уморява ни и има голям шанс да пропуснем нещо или да допуснем грешка. Защо вместо това не напишем малка програмка, която проверява дали кодът ни работи като хората? Така не просто ще имаме хубави критерии за кода, но и всеки друг ще може да проверява дали неговата промяна в модул А не е довела до грешка в нашия модул Б. Вместо да губим време да цъкаме всеки път, ще пускаме всички тестове автоматично и една команда, няма да въвеждаме никакви параметри и просто ще чакаме един от двата отгорова - "Всички тестове минаха усешно" или "Имаше грешка в тест Х".

Кодът който ще тестваме


class Interval(object):

def __init__(self, left, right):
self.left, self.right = left, right

def leftOpen(self): return self.left == None
def rightOpen(self): return self.right == None

def containsNumber(self, number):
if self.leftOpen() and self.rightOpen(): return true
if self.leftOpen(): return number <= self.right
if self.rightOpen(): return self.left <= number
return self.left < number < self.right

def __max(self, a, b): max(a, b) if not None in [a, b] else None
def __min(self, a, b): min(a, b) if not None in [a, b] else None

def insersect(self, other):
left = self.__max(self.left, other.left)
right = self.__max(self.right, other.right)
return Interval(left, right)

Идеята...


class IntervalTest:
def testContainsNumber(self):
interval = Interval(None, 0)
test("interval съдържа -3")
test("interval съдържа 0")
test("interval не съдържа 9")
test("interval.leftOpen() е истина")
test("interval.rightOpen() е лъжа")

def testIntersects(self):
test("сечението на [0, 10] със [5, None] е [5, 10]")
test("сечението на [None, 0] със [None, 42] е [None, 0]")
test("сечението на [None, 20] със [-20, None] е [-20, 20]")

...реализацията...

    class IntervalTest(unittest.TestCase):
def testContainsNumber(self):
interval = Interval(None, 0)
self.assertTrue(interval.containsNumber(-3))
self.assertTrue(interval.containsNumber(0))
self.failIf(interval.containsNumber(9))
self.assertTrue(interval.leftOpen())
self.failIf(interval.rightOpen())

def testIntersects(self):
self.assertEquals(
Interval(5, 10),
Interval(0, 10).intersect(Interval(5, None)))
self.assertEquals(
Interval(None, 0),
Interval(None, 42).intersect(Interval(None, 0)))
self.assertEquals(
Interval(-20, 20),
Interval(None, 20).intersect(Interval(-20, None)))

if __name__ == "__main__":
unittest.main()

...и резултата

.F
======================================================================
FAIL: testIntersects (__main__.GoodieListTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "./interval_test.py", line 16, in testCompact
self.assertEquals(
Interval(None, 0),
Interval(None, 42).intersects(Interval(None, 0)))
AssertionError: Interval(None, 0) != Interval(0, 0)

----------------------------------------------------------------------
Ran 2 tests in 0.000s

FAILED (failures=1)

Структура на тестовете

  • test case - група сценарии които споделят нещо общо помежду си.
  • test method - единичен сценарий, който проверява нещо конкретно.
  • assertation - единична проверка по време на изпълнение на сценария

Структура на тестовете в Python

  • test case - клас, наследяващ unittest.TestCase
  • test method - метод във въпросния клас, чието име започва с test и изпълнява поне една асертация.
  • assertation - извикване на self.assert* в тестовия метод.

Видове тестове

  • Unit tests - проверяват дали дадено парче код (клас) работи правилно в изолация.
  • Integration tests - проверяват дали няколко модула работят добре заедно.
  • Functional tests - проверяват дали крайната функционалност на продукта работи както се очаква.

За какво ни помагат тестовете

  • Откриват грешки скоро след като са въведени в системата.
  • Позволяват ви уверено да правите промени в самата система
  • Дават сигурност на клиенти, шефове и програмисти
  • Представляват пример как се работи с кода.
  • Помага разделянето на интерфейс от имплементация.

Декоратори и with в python


„ Програмиране с Python“, ФМИ

Стефан Кънев & Николай Бачийски

04.04.2007г.

Декоратори

  • Припомнете си как правихме статични методи:
  • class Mityo:
    # не зависи от инстанцията -- винаги пасуват
    def work(): pass
    work = staticmethod(work)
  • Горното се нарича „декориране“ на метода work
  • Подходът за смилане на метод или функция за да бъдат по-различни или по-полезни е често срещан в Python

Синтаксис

def add(a, b):
return a+b
add = accepts2ints(add)

Поради честотата на използване и поради неудобността на горния синтаксис, в Python си има отделен синтаксис за декорирането.

@accepts2ints
def add(a, b):
return a+b

В този случай, accepts2ints трябва да бъде функция, която приема функция и връща нейната нова версия.

Пример за прост декоратор

def notifyme(f):
def logged(*args, **kwargs):
print f.__name__, 'was called with', args, 'and', kwargs
return f(*args, **kwargs)
return logged

@notifyme
def square(x): return x*x

res = square(25)
square was called.

Няколко декоратора на една функция

class Mityo:
@staticmethod
@notifyme
def work(): pass

Mityo.work()
work was called with () and {}

Горният метод е еквивалентен на:

def work(): pass
work = notifyme(work)
work = classmethod(work)

Или:

work = classmethod(notifyme(work))

Параметри на декораторите

Ако се върнем към примера с accepts2int, сигурно ще ни се прииска да си напишем малко по-общ декоратор, на когото да казваме типовете, които искаме функцията ни искаме да приема а не да пишем нов декоратор за всяка комбинация, която ни хрумне.

@accepts(int, int)
def add(a, b): return a+b

Това се превежда до следното:

add = accepts(int, int)(add)

Другояче казано accepts трябва да бъде функция, която приема като аргументи типовете и връща друга фунцкия. Тя пък трябва да приема вече фунцкията, която ще декорираме и да върне декорираната.

Код срещу думи

def accepts(*types):
def accepter(f):
def decorated(*args):
for (i, (arg, t)) in enumerate(zip(args, types)):
if not isinstance(arg, t):
raise TypeError("Argument #%d of '%s' should have been of type %s" % (i, f.__name__, t.__name__))
#TODO: more complex checks: tuple of a type, list of type
return f(*args)
return decorated
return accepter

Още код


instances = {}

def singleton(cls):
def getinstance():
if cls not in instances:
instances[cls] = cls()
return instances[cls]
return getinstance

@singleton
class MyClass:

Вградени декоратори

  • staticmethod — прави един метод статичен
  • classmethod — прави класов метод (като първи аргумент приема класа)

Работа с файлове


src = raw_input()
target = raw_input()

try:
sourceFile = open(src, 'r')
buffer = []
try:
buffer = sourceFile.readlines()
finally:
sourceFile.close()

targetFile = open(target, 'w')
try:
for line in reversed(buffer):
targetFile.write(line)
finally:
targetFile.close()
except IOError:
print "Tough luck, junior"

With

  • Твърдението with позволява да преизползвате сходна try/catch логика (и не само)
  • Синтаксис:
    with [израз]:
    [блок]

    или

    with [израз] as [име]:
    [блок]

Втори опит


src = raw_input()
target = raw_input()

buffer = []
try:
with open(src) as sourceFile:
buffer = sourceFile.readlines()
with open(target) as targetFile:
for line in reversed(buffer):
targetFile.write(line)
except IOError:
print "Much better, now, ain't it?"

Как работи with?

  • Изразът който получава се изпълнява и се взема върнатия обект. Той се нарича context manager
  • Методът __enter__() на content manager-а се изпълнява. Получената от него стойност се записва в името след as.
  • Изпълнява се блокът подаден на with
  • Ако се получи изключение в блока, се извиква метода __exit__(type, value, traceback) на context manager-а.
  • Ако не се получи изключение, пак се вика __exit__(None, None, None).

Как се ползва with?

Понеже with е нова ключова дума, въведена в Python 2.5, се налага да добавите малко код към вашето приложение за да я ползвате:

from __future__ import with_statement

Ако не сте импортирали with и го ползвате като име, Python ще даде грешка. В Python 2.6 with ще го има по подразбиране и няма да се налага да го импортвате. Това е стандартния подход за приемане на нови feature-и в езика.

Малък пример


from __future__ import with_statement

class Manager:
def __enter__(self):
print "I've been entered!"
return "ticket"
def __exit__(self, type, value, traceback):
print "I've been exited!"

with Manager() as something:
print "Am I inside?"
print something
I've been entered!
Am I inside?
ticket
I've been exited!

contextlib

Модула contextlib предлага следните неща:

  • closing
  • nested
  • contextmanager

closing

contextlib.closing е нещо подобно на...

class closing(object):
def __init__(self, thing): self.thing = thing
def __enter__(self): return thing
def __exit__(self, type, value, traceback): self.thing.close()
...и ви позволява да пишете така...

from __future__ import with_statement
from contextlib import closing
import codecs

with closing(urllib.urlopen('http://www.python.org')) as page:
for line in page:
print line

nested

Долния код...


from contextlib import nested

with nested(A, B, C) as (X, Y, Z):
do_something()
...е еквивалентен на...

with A as X:
with B as Y:
with C as Z:
do_something()

contextmanager

contextmanager е декоратор, който превръща генератор функция в context manager:


from __future__ import with_statement
from contextlib import contextmanager

@contextmanager
def entering(whom):
print "I've been entered by %s" % whom
yield "ticket"
print "I've been exited!"

with entering("someone") as something:
print "Am I inside?"
print something
I've been entered by someone
Am I inside?
ticket
I've been exited!

Примерна задачка на Python


„ Програмиране с Python“, ФМИ

Стефан Кънев & Николай Бачийски

02.04.2007г.

Какво ще правим?

  • Ще си припомним условието
  • Ще ви предложим примерно решение
  • Ще видим дали може да се направи по-хубаво

Условие—начало

Основната ви цел е да напишете функция със следните име и аргументи: module_game(m1, m2).

Входни данни: m1 и m2 са модули.

Резултат: n-торка с 2 елемента, с броя на точките събрали първия и втория модул при играта, която е описана по-долу в условието.

Предварително известни факти:

  • всички речници в модулите са или празни или само от цели числа. Ключовете им са винаги низове. Примери: {} {'baba': 5} {'a': 42, 'b': -69, 'x': 4360000634}
  • всички списъци в модулите са или празни или са списъци от n-торки от числа. n-торките също могат да бъдат празни. Примери: [] [()] [(1023, 4095, 8191), (), (), (0, 17), (3,)]
  • всички функции в модулите приемат два аргумента цели числа и връщат цяло число
  • може да разчитате, че тези условия ще бъдат изпълнени и няма нужда да ги проверявате

Правила на играта:

За атрибути по-надолу ще смятаме само тези реални атрибути на модулите, които не започват с _ (долна черта, подчертавка)

Условие—правило 0

Всеки модул получава 1 точка за всяка функция в него, чието име започва с последните 3 символа на друга функция в същия модул. Ето пример за част от модул, която ни гарантира две точки по този параграф:

s = "baba"
def destroy(x, y):
print "Destroyed."
return 0
def roy_keen(x, y):
print "Babyboy."
return 0
def roy_boss(x, y):
print "Holy-shmolly, that is tea!"
return 0

Условие—правило 1

Всеки модул получава 1 точка, за всеки списък в него, за който средното-аритметично на сумите от квадратите на n-орките в него е по-голямо от средно-аритметичното на всички цели числа в другия модул. Ако някоя n-торка е празна, нейната сума може да се смята за 0. Ако един списък е празен, неговото аритметично също може да се смята за 0.


Пример: имаме списъка [(1, 2), (), (8, -2), (-1, 5, -1)]. Сумите на квадратите на n-торките са съответно: 5, 0, 68, 27. Средното им аритметично е точно 25.

Условие—правило 2

По 1 точка получава модул, за всяка функция в него, която удовлетворява следните условия:

  • извикана с аргументи 0 и 0 връща 0
  • комутативна e за всички двойки числа от 0 до 99 включително

Пример за такава функция: def f(x, y): return x*y

Условие—правило 2

3 точки носи на модул всеки речник, за който:

  1. нека сортираме стойностите му по брой на цифрите в низходящ ред
  2. ако две стойности имат равен брой цифри, приемаме, че те са ъгли в градуси и сравняваме техните тангенси (отново низходящо)
  3. взимаме ключовете на първите 6 елемента. Ако елементите на речника са по-малко от 6, спираме дотук
  4. ако тези 6 ключа са ‘Chapman’, ‘Cleese’, ‘Gilliam’, ‘Idle’, ‘Jones’, ‘Palin’ независимо в какъв ред или с малки или големи букви, модулът получва трите точки

Пример за такъв речник: {'Chapman': 11, 'Charlie': 99, 'GILLIAM': 102, 'Idle': 666, 'Jones': 883, 'Palin': 55, 'Cleese': 1101}

Код—начало

def module_game(m1, m2):
return (assess(m1, m2), assess(m2, m1))

def assess(mod, mod_other):
return sum((
end3_funcs(mod),
avg_lists(mod, mod_other),
commutatives(mod),
3*monty_python_dicts(mod),
))

def valid_attributes(mod):s
return (attr for attr in dir(mod) if not attr.startswith('_'))
  • sum приема последователност и му даваме n-торка: sum((
  • valid_attributes няма нужда да връща списък, може да ползваме generator expression

Код—правило 0

def end3_funcs(mod):
func_names = [attr for attr in valid_attributes(mod) if callable(getattr(mod, attr))]
count = 0
for func in func_names:
if len(func) < 3: continue
start = func[:3]
if any((otherfunc.endswith(start) for otherfunc in func_names if func != otherfunc)):
count += 1
return count
  • Защо func_names е списък (или „за влагането на генератори“)?
  • Какво е any?

Влагане на генератори

g = (x*x for x in xrange(4))
for a in g:
for b in g:
print (a, b)
(0, 1)
(0, 4)
(0, 9)
  • Ред на извикване:
    1. g.next() във външния цикъл, a, става 0
    2. g.next() във вътрешния циъкл, b, става 1
    3. g.next() във вътрешния циъкл, b, става 4
    4. g.next() във вътрешния циъкл, b, става 9
  • генераторът не се копира
  • … и за съжаление не може лесно да се копира
  • генераторът може да се ползва само веднъж

Ами тогава?

  1. Списъци: g = [x for x in xrange(4)]
  2. Създаване на 2:
    g = (x*x for x in xrange(4))
    g_inner = (x*x for x in xrange(4))
  3. Създаване направо вътре:
    for a in (x*x for x in xrange(4)):
    for b in (x*x for x in xrange(4)):
    print (a, b)
  4. Функция генератор:
    def gen_sq(n):
    for x in xrange(n):
    yield x*x
  5. Още една функция:
    def gen_sq2(n):
    return (x*x for x in xrange(n))

Код—правило 1

def avg_lists(mod, other):
other_ints = [getattr(other, attr) for attr in valid_attributes(other)
if isinstance(getattr(other, attr), (int, long))]
other_sum, other_len = sum(other_ints), len(other_ints)
mod_lists = (getattr(mod, attr) for attr in valid_attributes(mod)
if isinstance(getattr(mod, attr), list))
count = 0
square = lambda x: x*x
for l in mod_lists:
jingled_items = [sum(imap(square, tup)) for tup in l]
jingled_sum, jingled_len = sum(jingled_items), len(jingled_items)
# don't use floats
# we know that always both the sum and the len are non-negative
# except that other_sum could be negative
if jingled_sum > 0 and 0 == other_len:
count += 1
elif jingled_len == 0 and other_sum < 0:
count += 1
elif jingled_sum*other_len > jingled_len*other_sum:
count += 1
return count

Код—правило 2

def commutatives(mod):
# този път ф-ии--не имена
funcs = [getattr(mod, attr) for attr in valid_attributes(mod)
if callable(getattr(mod, attr))]
count = 0
for func in funcs:
if func(0,0) == 0 and\
all((func(i, j) == func(j, i)
for i in xrange(0, 99) for j in xrange(i+1, 100))):
count += 1
return count

Код—правило 2

def commutatives(mod):
# този път ф-ии--не имена
funcs = [getattr(mod, attr) for attr in valid_attributes(mod)
if callable(getattr(mod, attr))]
count = 0
for func in funcs:
point_commutative = ( func(i, j) == func(j, i)
for i in xrange(0, 99) for j in xrange(i+1, 100))
if func(0,0) == 0 and\
all(point_commutative):
count += 1
return count

any и all

  • any(iterable) — връща истина, ако поне един от елементите на iterable е истина.
  • all(iterable) — връща истина, ако всички елементи на iterable са истина.
>>>people = ('God', 'Monty', 'Pinkie', 'Mityo')
>>>wannaplay = lambda name: 'x' in name
>>>friends = lambda x,y: x[0] == y[0] and x != y
>>>capitalised = lambda x: x[0].isupper()
>>>print any(map(wannaplay, people))
False # Nobody wants to play
>>>print any([friends(name, 'Mityo') for name in people])
True # Monty
>>>print all(imap(capitalised, people))
True
>>>print all((friends(name, 'Mityo') for name in people))
False

Код—правило 3

def monty_python_dicts(mod):
WANTED = set(['chapman', 'cleese', 'gilliam', 'idle', 'jones', 'palin'])
dicts = [getattr(mod, attr) for attr in valid_attributes(mod) if isinstance(getattr(mod, attr), dict)]
count = 0

def make_strange_cmp(dict_):
def strange_cmp(x, y):
x, y = dict_[x], dict_[y]
sx, sy = str(sx).lstrip('-'), str(sx).lstrip('-')
if (len(sx) != len(sy)):
return cmp(len(sx), len(sy))
return cmp(math.tan(math.radians(x)), math.tan(math.radians(y)))

for d in dicts:
ordered = sorted(d, cmp=make_strange_cmp(d), reverse=True)
if set(imap(string.lower, ordered[:6])) == WANTED:
count += 1
return count

Код—правило 3

def monty_python_dicts(mod):
WANTED = set(['chapman', 'cleese', 'gilliam', 'idle', 'jones', 'palin'])
dicts = [getattr(mod, attr) for attr in valid_attributes(mod)
if isinstance(getattr(mod, attr), dict)]
count = 0
# DSU, DSU
def make_decorate(dict_):
def decorate(key):
value = dict_[key]
svalue = str(value).lstrip('-')
return (len(svalue), math.tan(math.radians(value)))
return decorate

for d in dicts:
ordered = sorted(d, key=make_decorate(d), reverse=True)
if set(imap(string.lower, ordered[:6])) == WANTED:
count += 1
return count

DSU

  • DSU = Decorate, Sort, Undecorate
  • Трансформацията на Шварц (Schwartzian transform)
  • Искаме да сортираме списък от имена първо по второто име, после по пъврото
  • names = ['Monty Python', 'Bilbo Baggins', 'Mityo Python']
    # 2 пъти split
    # цялото се вика n*log(n) пъти
    def names_cmp(n1, n2):
    first1, last1 = n1.split()
    first2, last2 = n2.split()
    if last1 != last2:
    return cmp(last1, last2)
    else:
    return cmp(first1, first2)

    names.sort(names_cmp)

DSU (2)


# (('Python', 'Monty'), 'Monty Python')
decorated = zip([tuple(reversed(name.split())) for name in names], names)
decorated.sort()
undecorated = [value for (decor, value) in decorated]
print undecorated
['Bilbo Baggins', 'Mityo Python', 'Monty Python']
  • списъци и n-орки се сравняват първо по първи елемент, ако са равни—по-втори и т.н.
  • zip взима няколко списъка и връща списък от n-орки с поредните елементи на списъците
  • обработката на един елемент се вика веднъж и се пише веднъж

DSU (3)

ready = sorted(names, key = lambda name: tuple(reversed(name.split())))

или:


def decorate(name):
first, last = name.split()
return last, first
ready = sorted(names, key = decorate)
  • В Python 2.4 има доста нововъведения: sorted, key параметъра на sort и sorted
  • Параметърът key очаква функция с един аргумент и сортира по нейната стойност, а не по стойността на елементите на поредицата
  • Стойностите на поредицата не се променят от викането на функцията, дадена на key
  • DSU се прилага вече много по-лесно и кратко :)

Итератори и генератори в Python


„ Програмиране с Python“, ФМИ

Стефан Кънев & Николай Бачийски

28.03.2007г.

За приятелите

  • Митьо Питона не е много паметлив.
  • Пази телефонните номера на приятелите си в прост текстов файл:

    062600400
    +1800500
    061830647
    062633733
  • Една вечер Митьо решил да организира джакузи-парти и трябвало да се обади на всичките си приятели
  • За целта трябвало да прочете всички номера от файла и да им се обади един по един
  • Не забравяйте: Митьо не е много паметлив — може да помни само един номер в главата си

Първа реализация

numbers = open('friends', 'r').readlines()
for number in numbers:
mityo.invite(number.strip())
  • open отваря файл и връща файлов обект
  • readlines прочита целия файл и ни връща списък от низове (заедно с новите редове)
  • strip маха белите полета от двата края на низа (+ новите редове)
  • Виждате ли проблем с тази реализация?
  • Памет: четем целия файл в на Митьо паметта

Втора реализация

numbers = open('friends')
while (True):
number = numbers.readline()
if not number:
break
mityo.invite(number.strip())
  • readline чете един ред от файла, ако няма — връща празен низ
  • Виждате ли проблем с тази реализация?
  • Простота: то не са while-ове, то не са if-ове

Трета реализация (любима)

for number in open('friends'):
mityo.invite(number.strip())
  • Виждате ли проблем с тази?
  • Функционално е еквивалентна на предишната!
  • Памет: ок
  • Четимост: да, да
  • Магия: да

Каква е магията?

итератор — обект, с метод next, който при всяко извикване или връща пореден елемент или вдига StopIteration, ако няма повече


генератор — обект, за който вградената функция iter връща итератор

Как работи for

for target in sequence:
блок
  1. Опитва се да се добере то итератор
    1. извиква iter(sequence) — на практика проверява дали обекта ни има метод __iter__ и взима неговия резултат. Ако няма такова животно — отива на по-следващия слайд
    2. на всяка стъпка от цикъла в target слага резултата от next метода на получения по-горе обект
    3. … и така докато не прихване StopIteration

Пример

class MityoIter:
def __init__(self, filename = 'friends'):
self.numbers = open(filename)
def next(self):
number = self.numbers.readline()
if not number:
raise StopIteration
return number
def __iter__(self):
return self

for number in MityoIter():
mityo.invite(number.strip())

Как работи for (2)

for target in sequence:
блок
  1. Цикли по стандартния начин:
    1. Проверява дали sequence поддържа оператора [] (__getitem__)
    2. Започвайки от индекс 0 се опитва да достъпи елементите един по един, като увеличава на всяка стъпка индекса с 1
    3. Свършва, когато прихване IndexError

Пример

class MityoBeard:
def __init__(self, step = 1, maxage = 33):
self.step = step
self.maxage = maxage

def immortalise(self):
self.maxage = None

def __getitem__(self, day):
if self.maxage != None and day > self.maxage:
raise IndexError("Mityo's beard is already gone.")
return self.step*day

beard = MityoBeard(2)
for l in beard:
print "Mityo's beard is %d cm. long." % l
beard.immortalise()
for l in beard:
print "Mityo's beard is %d cm. long." % l

Обратно към итераторите

  • итерацията е нещо съвсем отнесено
  • не винаги можем да разберем лесно дали използваме списък или итератор
  • в повечето случаи не ни е нужно да знаем
  • от многото знания боли глава

Генераторите обичат питоните

  • range vs. xrange
  • dict.keys vs. dict.iterkeys vs. dict
  • os.walk
  • enumerate
  • sorted
  • list(генератор)

Примери

>>> list(enumerate(xrange(37, 43)))
[(0, 37), (1, 38), (2, 39), (3, 40), (4, 41), (5, 42)]
>>> map(len, MityoIter()) # map и подобните са умни и разбират
>>> partners = {'Pena': 'Mityo', 'Kuna': 'Mityo', 'Boca': 'Mityo'}
>>> partners.keys()
['Kuna', 'Boca', 'Pena']
>>> partners.iterkeys()

>>> iter(partners)

>>> for woman in partners:
print "%s is with %s." % (woman, partners[woman])

Само вградените ли са лесни?

  • вградените генератори се ползват лесно
  • досега не видяхме лесен начин да си правим сами — само с класове и __iter__
  • е да, ама не

ша та yield-на

Магията не спира дотук:

def mityo_iter(filename='friends'):
numbers = open(filename)
while(True):
number = numbers.readline()
if number:
yield number
else:
return

>>> mityo_iter # най-обикновена ф-я

>>> mityo_iter() # обикновена-чушки

>>> for number in mityo_iter():
mityo.invite(number.strip())

Под капака

Функциите, които съдържат yield връщат генератори.


yield връща стойността на поредната стъпка, а итерацията завършва когато функцията свърши


yield приспива изпълнението на функцията и след това продължава от същото място


Подробен пример

def mityo_rabbits(n):
x, y = 0, 1
for i in xrange(n):
yield y
x, y = y, x + y

>>> list(mityo_rabits(7))
[1, 1, 2, 3, 5, 8, 13]

Изключения в Python


„ Програмиране с Python“, ФМИ

Стефан Кънев & Николай Бачийски

21.03.2007г.

Традицията повелява

Ето как най-често се справяме грешките в нашите програми:

"""Модул за зимнината на Митьо Питона"""
import jars

ERROR = -1
SUCCESS = 0

def prepare_for_winter():
jar = jars.Jar()
if jar.clean() == jars.ERROR:
print "Couldn't clean Mityo's jar!"
return ERROR
if jar.fill('python juice') == jars.ERROR:
print "Couldn't fill Mityo's jar!"
return ERROR
if jar.close() == jars.ERROR:
print "Couldn't close Mityo's jar!"
return ERROR
return SUCCESS

Традицията не са това…

Сега да опитаме с изключения:

"""Модул за зимнината на Митьо Питона"""
import jars

class MityoWinterError: pass

def prepare_for_winter():
try:
jar = jars.Jar()
jar.clean()
jar.fill('python juice')
jar.close()
except jars.Error:
raise MityoWinterError

Синтаксис и семантика

try:

блок

except изключения:

блок ако се случи някое от описаните изключения



except още изключения:

блок ако се случи някое от описаните изключения

except:

блок ако изключението не е хванато по-горе

finally:

блок изпълнява се винаги

else:

блок ако не е възникнала изключителна ситуация

Аз не (при|с)хващам

По подразбиране при неприхванато изключение Python спира изпълнението на програмата и отпечатва на стандартната грешка описание и реда на извикване на функциите до момента на грешката.

bad.py:

l = [1, 2, 3]
def bad(): print l[3]
bad()
След изпълнение получаваме:
$ python bad.py
Traceback (most recent call last):
File "bad.py", line 3, in
bad()
File "bad.py", line 2, in badfunc
def badfunc(): print l[3]
IndexError: list index out of range

Изключенията се използват активно от вградените средства в езика.

Започвам да (при|с)хващам

def distribute_over(beers):
try:
return 333/beers
except ZeroDivisionError:
return 0

Изключенията са класове, инстанции или низове, като последните не е препоръчително да се ползват.

>>> ZeroDivisionError

Можем да прихванем и по-общо тип изключение (родителски клас):

def distribute_over2(beers):
try:
return 333/beers
except ArithmeticError:
return 0

Ето и доказателство:

>>> issubclass(ZeroDivisionError, ArithmeticError)
True

Тази практика е много логична, тъй като делението на нула е и аритметична грешка и когато прихващаме аритметичните грешки би трябвало да хванем и делението на нула.

По-гъвкаво прихващане

  • Можем да вземем и повече информация за изключението:
    try:
    x = [] / 4
    except TypeError, data:
    print data
    Какво ще има в data, зависи от самото изключение, но е прието всички да връщат годна за отпечатване стойност, ако се дадат като аргументи на str или repr.
  • Ако за няколко изключения имаме една и съща реакция, можем да ги прихванем накуп:
    try:
    doomed()
    except (NameError, TypeError), data:
    print data
    except (MyError, YourError):
    print "Opps! This shouldn't've hapenned..."
    except:
    print "Unknown exception."
    else:
    print "It's my happy day!"
  • с празен except прихващаме изключения, които не са били хванати до момента. Трябва да бъде поставен след всички други except

finally

file = open('data.txt')
try:
mymodule.load_info(file)
except IOError, data:
print "Couldn't read from file: %s" % data
except (mymodule.BadDataError, mymodule.InternalError), data:
print "Loading failed: %s" % data
else:
print "Data loaded successfully from file."
finally:
file.close()

Ако присъства, finally стои винаги най-отдолу.

Пораждане на изключителни ситуации — низове (1)

LonelyError = "Mityo is lonely!"
def make_lonely(): raise LonelyError
try:
make_lonely()
except LonelyError:
print LonelyError

Можем към низа да дадем и допълнителна информация:

LonelyError = "Mityo is lonely!"
def make_lonely(level): raise MyError, level
try:
make_lonely(666)
except LonelyError, level:
print "Mityo's loneliness is at level %d" % level

Пораждане на изключителни ситуации — низове (2)

За да се определи дали едно изключение съвпада с конкретно except твърдение двете изключение се сравняват с is, а не с ==:

LonelyError0 = "Mityo is lonely!"
LonelyError1 = "Mityo is lonely!"

def make_lonely(): raise LonelyError0
try:
make_lonely()
except LonelyError1:
print LonelyError1

води до:

Traceback (most recent call last):
File "", line 2, in
File "", line 1, in make_lonely
Mityo is lonely!

Пораждане на изключения — класове (1)

class MityoError:
def __init__(self, msg = "There is something wrong with Mityo!"):
self.msg = msg
def __repr__(self): return self.msg
def __str__(self): return self.__repr__()

class LonelyMityoError(MityoError):
def __init__(self):
MityoError.__init__(self, "Mityo is Lonely!")

class StupidMityoError(MityoError): pass

def make_lonely(): raise LonelyMityoError()

def eat_a_cockroach(): raise StupidMityoError

try:
make_lonely()
except StupidMityoError:
print "Mityo has been somehow stupid!"
except MityoError, data:
print data

Пораждане на изключения — класове (2)

2 начина за пораждане:

  • raise клас, инстанция # инстанцията е от точно този клас
  • raise инстанция # примерът със самотния Митьо, същото като
    raise инстанция.__class__, инстанция

За обратна съвместимост с низовите изключения могат да се използват и следните начини:

  • raise клас # примерът с тъпия Митью,
    същото като raise клас()
  • raise клас, arg # същото като raise клас(arg)
  • raise клас, (arg0, arg1) # същото като raise клас(arg0, arg1)

Пример:

class MyError():
def __init__(self, msg, num):
print "%s (%d)" % (msg, num)

try:
raise MyError, ("Initialized", 99)
except MyError: pass

Initialized (99)

Далаверата от изключенията с класове

  • Прихващат се всички наследници — така лесно можем да си структурираме типовете грешки.
    class EmotionalMityoError(MityoError): pass
    class LonelyMityoError(EmotionalMityoError): pass
    class ConfusedMityoError(EmotionalMityoError): pass
    class MoneyMityoError(MityoError): pass

    mityo = Mityo()
    try:
    mityo.live_a_day()
    except MoneyMityoError:
    mityo.rob_a_bank()

    # прихващаме по-общия проблем, а не по-частните Lonely и Confused
    # от инстанцията на проблема психоаналитика може да извлече ценна информация
    except EmotionalMityoError, problem:
    mityo.go_to_shrink(problem)

    except MityoError:
    mityo.call_911()
    else:
    mityo.set_happy_bit()
    finally:
    mityo.play_with(Girl(beauty=99, hair='blonde')*3)

Същото ама с низове

EmotionalMityoError = "Mityo has an emotional problem!"
LonelyMityoError = "Mityo is lonely!"
ConfusedMityoError = "Mityo is confused!"
MoneyMityoError = "Mityo is out of money!"

mityo = Mityo()
try:
mityo.live_a_day()
except MoneyMityoError:
mityo.rob_a_bank()

# налага се да опишем всички наследници на EmotionalError на ръка
# при всяко добавяне на грешка, трябва да я добавяме и тук
except (EmotionalMityoError, LonelyMityoError, ConfusedMityoError), problem:
mityo.go_to_shrink(problem)

# тук пък трябва да опишем всички други грешки...
# което си е ад за поддръжка, почти като москвич осмак
except MityoError:
mityo.call_911()
else:
mityo.set_happy_bit()
finally:
mityo.play_with(Girl(beauty=99, hair='blonde')*3)

Ескалиране на грешката

  • Когато Python се натъкне на изключение в даден блок и в него то не се обработи, изключението се праща към горния блок, после към по-горния и така докато или изключението не бъде прехванато или не стигнем най-отгоре и интерпретаторът не спре програма по познатия ни вече начин (в червеничко).
  • Можем да се намесим в следната схема или като прихванем изключението (вече знаем как), или като пратим изключението нагоре по трасето. Последното става с голо извикване на raise:
    try:
    mityo.live_a_day()
    except GirlfriendMityoError:
    mityo.lonely = True
    # Митьо не може да се оправя с това, нека тези отгоре да се грижат
    raise

assert

  • assert <проверка>, [<данни>]
  • Целта на твърдението assert е да се подсигурите, че важно за вашата програма условие е изпълнено
  • assert test, data е еквивалентно на:
    if __debug__:
    if not test:
    raise AssertionError, data
    data никак не е задължително
  • Както си личи от примера, assert рядко се ползва в крайния продукт, а най-вече по време на разработка за да си спестим главоболия и да сме сигурни в целостта на данните си
  • По подразбиране глобалният атрибут __debug__ има стойност 1 като може да бъде променена от вас или от опцията на командния ред -O (оптимизация), която го установява на False
  • def fib(n):
    assert n >= 0
    if n <= 1:
    return n
    else:
    return fib(n-2) + fib(n-1)

Вградени класове за изключения

  • Основният, който всички наследяват е Exception
  • Главни категории:
    StandardError
    родител на всички вградени изключения; директен наследник на Exception
    ArithmeticError
    родител на OverflowError, ZeroDivisionError, FloatingPointError
    LookupError
    родител на IndexError, KeyError
    EnvironmentError
    родител на изключенията, които се случват извън интерпретора: IOError, OSError
  • Какво може да направи Exception за нас?
    • >>> class MyError(Exception): pass
      >>> raise MyError("Failed, you have!", -1)
      Traceback (most recent call last):
      File "", line 1, in ?
      __main__.MyError: ('Failed, you have!', -1)
      >>> error = MyError("Failed, you have!", -1)
      >>> error.args
      ('Failed, you have!', -1)
    • пази аргументите, които сме дали при създаване в args
    • дефинира __str__, така че да връща нещо като: map(str, self.args)

Използване на изключение не само за грешки

  • за прихващане на събития през няколко блока дълбочина
    try:
    for box in boxes:
    for jar in box.jars():
    jar.has(throw=True, colour='velvet', eyes='cheese')
    except JarFound, good_jar:
    print "We found the jar! Its name is %s" % good_jar.name
    else:
    print "I couldn't find it :("
  • за всякакви подобни странности на потока на програмата ни
  • за обработка на частни случаи

Аз хващам прекалено много

  • def func():
    try:
    … # някъде тук възниква IndexError
    except:
    … # тук хващаме всичко и отпечатваме съобщение за грешка

    try:
    func()
    except IndexError: # нъцки, няма да го хванем тук
  • прихващайте не повече отколкото ви трябва
  • използвайте гол except главно в най-високото ниво на програмата си

Ти пък прекалено малко

  • try:
    mityo.live_a_day()
    except (MityoWantsACracker, MityoWantsADrink,
    MityoWantsAnIsland, MityoWantsA333YearOldWhiskey,
    MityoNeedsPanties, MityoNeedsNewSlippers), thing:
    you.buy(whom=mityo, what=thing)
  • какво ще се случи ако Митьо може да поиска и мармотче? Ами само 111 годишно?
  • винаги е по-добре изключенията да се структурират, за да се избегне дългото, кошмарно за поддръжка и податливо на много грешки изброяване:
    except (MityoWants, MityoNeeds), thing:

Нека обобщим

Няколко неща, за които може да ползваме изключения:
  • обработка на грeшки:
    • структурирани, прихващаеми, позволяващи предаване на допълнителна информация
    • вградените функции и твърдения широко ги използват
    • пораждане и прихващане на собствени изключения
  • безусловно извършване на заключителни действия — finally
  • предаване на събития между отдалечени структурно части от кода