Líbí se mi koncept specializovaných nástrojů propojovaných rourou, a tak jsem přemýšlel, jak bych ho dostal i do PowerConsole. Včera večer jsem nastřelil jednoduchý koncept, jak v Pythonu pospojovat objekty pomocí „roury“ s využitím přetěžování operátorů.
Výchozí myšlenkou bylo použití co nejjednoduššího zápisu co nejvíce podobného zápisu roury v shellu, tedy: A | B | C atd. Protože znak „|“ slouží v Pythonu pro zápis operace OR, lze využít možnosti redefinovat implementaci tohoto operátoru na přenos dat mezi objekty. A protože se jedná o operaci OR, je vyhodnocování prováděno postupně hezky zleva doprava, tak jako u propojování příkazů rourou v shellu. Zbývá už jen dořešit, jak dostat prvotní nebo zpracovaná dat z objektu. Protože se bude jednat v podstatě o sekvenci nějakých dat (řádky textu, sled objektů, cokoliv), nabízí se jako nejjednodušší řešení použití iteračního protokolu.
Následující třída demonstruje možnou implementaci.
class Pipe(object):
Data = None
def __init__(self,rows=0):
self.rows = rows
def next(self):
if self.__row < self.rows:
self.__row += 1
return "(%i) Data from (%s)" % (self.__row,self.__class__.__name__)
else:
raise StopIteration()
def processRecord(self,record):
if self.Data is None:
self.Data = []
self.Data.append("(%s) Processed %s" % (self.__class__.__name__,record))
def __or__(self,other):
for data in self:
other.processRecord(data)
return other
def __iter__(self):
if self.Data is None:
self.__row = 0
return self
else:
return (x for x in self.Data)
Pro testování si nadefinujeme i tři jednoduché potomky, kteří budou představovat elementární příkazy (jako je ls, sort, awk, re apod.):
class A(Pipe):
pass
class B(Pipe):
pass
class C(Pipe):
pass
Pokud si vše výše uvedené uložíte do souboru, např. s názvem pipe.py, pak v shellu Pythonu stačí zadat
from pipe import *
a máte k dispozici tři elementární příkazy: A(), B() a C(). Zápis závorek je bohužel nezbytný, ale s tím se dá žít. Nyní můžete použít konstrukci jako
A(rows=5) | B() | C()
která vygeneruje 5 řádek dat pomocí A, pošle je ke zpracování do B, která je pošle ke zpracování do C, a výsledek zpřístupní jako instanci třídy C. Odtud je můžete dostat pomocí iterace, např. příkazem:
for x in A(rows=5) | B() | C():
prin x
Jak to funguje? Parametry zpracování definujete přímo pro konstruktor __init__. Pro tuto jednoduchou ukázku je definován pouze parametr rows, který slouží k vygenerování zkušebních dat. V reálu by to bylo spíše něco jako:
class ls(Pipe):
def __init__(self,path='.',full=False,attr='XYZ'):
self.path = path
self.full = full
self.attr = attr
Metoda __init__ žádné zpracování dat neprovádí, protože zatím ještě není jasné, co a jak se bude dělat. Slouží pouze pro definici akceptovatelných parametrů příkazu a jejich uložení pro pozdější použití.
Pro získání dat dle parametrů slouží metoda next. Ta je prováděna tehdy, pokud je příkaz požádán o data (voláním metody __iter__), a žádná data zatím nemá (self.Data = None).
Pro zpracování dat přijatých z roury slouží metoda processRecord. Ta je automaticky volána pro každý blok dat na vstupu, a jejím úkolem je provést zpracování dat dle parametrů předaných __init__, a uložení dat do seznamu self.Data.
Pro předání dat dalšímu objektu slouží metoda __or__. Ta využívá iterace objektu přes sebe sama k získání dat a jejich postupnému předání dalšímu objektu.
Metoda __iter__ vrací iterátor na data objektu. Pokud objekt data zatím nemá, vrací odkaz na samotný objekt, takže je volána metoda next; jinak vrací generátor který předá zpracovaná data uložená v self.Data.
Třída Pipe obsahuje pomocný kód pro potřeby prototypu, a který v konečné třídě není potřeba. Pokud se vám tento koncept líbí, a chcete ho použít pro vlastní objekty, použijte spíše následující „holou“ verzi třídy Pipe.
class Pipe(object):
Data = None
def next(self):
raise StopIteration()
def processRecord(self,record):
if self.Data is None:
self.Data = []
self.Data.append(record)
def __or__(self,other):
for data in self:
other.processRecord(data)
return other
def __iter__(self):
if self.Data is None:
return self
else:
return (x for x in self.Data)
V potomcích pak stačí definovat __init__ a přepsat next a processRecord dle vašich potřeb.
[2] Ono není přetěžování operátorů jako přetěžování operátorů, a v tomto případě jde spíš o jeho specifikaci než přetěžování, protože uživatelské objekty standardně operaci OR nepodporují (třída object nemá implementaci metody __or__).
Naopak bych to viděl jako silnou stránku Pythonu. Pouhou implementací metody __iter__ dosáhnu toho, že libovolná třída podporuje iteraci ve smyčkách, implementací __or__ a podobných metod zase dosáhnu toho, že objekt třídy lze použít v jiných konstrukcích, můžu definovat pravidla pro porovnávání apod. Vedle hračiček typu roura je to použitelné především pro usnadnění a zpřehlednění tvorby komplexních seskupení objektů. Nádherným příkladem je např. modul pyparsing.
[1] V programu bych to asi moc nepoužil, ale pro přímé použití v shellu Pythonu je to ideální. Na Linuxu si sice můžu odskočit do shellu, ale spousta lidí používá Python protože je multiplatformní, a hodil by se jim "pythoní bash". Klidně si dokážu představit rourovatelné objekty typu glob, re, ls, cp, awk, rm, které by se používaly podobně jako z bashe (třeba i se stejnými přepínači, od čeho je tu modul optparse, že?), ale fungovaly by na každé platformě, a jako bonus je lze zaintegrovat i do vlastních aplikací.
Prijde mi to jako drbat se levou nohou za pravym uchem
Python je pro mnoho lidi zajimavy tim, brani v tvoreni podobnych prasaren jako jdou psat treba v perlu, tak proc se o neco takoveho pokouset.
taky nevidim duvod, proc chces aby to vypadalo jako roury v shellu - python neni shell!
v pythonu (stejne jako v dalsich jazycich) je daleko elegantnejsi zpusoby jak udelat to samy a to jsou generatory - klasicky elegantni zpusob producent-konzument ... jeden objekt generuje data a druhej je prubezne zpracovava:
def producent(...):
for radek in list_neceho:
yield radek
for neco in producent(...):
zpracuj(neco)
Netradicni, ale zajimave.
Premyslim, kdyz napisu tu radku s rourou syntakticky spatne. Dostanu nejakou rozumnou chybu nebo spise vyjimky, ze ktere relativne tezko urcim, ve ktere casti radky jsem udelal chybu a jakou?
To je problem LaTeXu, kdy TeX byl taky rozsiren smerem, ktery nebyl prilis predpokladam a podporovan a tak obcas tvurce LaTeXovskych dokumentu celi zahadnym TeXarskym chybam a pracne odhaluje, o jako chybu v LaTeX syntaxi se vlastne jedna. Napadlo me, jestli tohle neni podobny pripad.
[7] V případě chyby je samozřejmě nejlepší vyvolat výjimku, a záleží jen na autorovi objektu jak bude vypadat příslušné chybové hlášení. Pokud bude součástí zprávy i jméno třídy, pak je okamžitě jasné, v které částí výrazu došlo k chybě. Pokud budou špatně zadány parametry, vyhodí chybu metoda __init__, pokud dojde k chybě při zpracování dat, pak metoda processRecord.
[3] O tom prave mluvim -> pokud pretezuju (definuju) __or__() nebo __iter__() tak by mely delat to co se od nich ceka.
Samozrejme, je to silna stranka Pythonu, ale jen do doby nez prijde nekdo kdo udela takovyhle peklo. :-)
Proto chapu, ze nektery jazyky to neumoznujou - potom mate jistotu, ze A+B bude doopravdy scitat.
[6] 1. Jako roury v shellu to vypadá proto, že OR používá stejný znak. Náhoda, ale milá, protože roury v shellu umí používat každý. Pokud roury mezi objekty fungují stejně a používají prakticky stejný zápis, je hned snadnější je používat.
2. Pythoní konzole *je* shell. Dá se používat k řešení úplně stejných úloh jako bash, zsh nebo jiný "shell". Jen je to trochu víc ukecanější. K skriptování je dokonce lepší než bash, jen má horší podporu pro práci s externími programy. Spousta lidí používá Python pro řešení úloh, které jiní řeší v bashi. Existují i zajímavé projekty typu osh (http://geophile.com/osh/index.html), IPython (http://ipython.scipy.org/moin/About), Quasi (http://quasi-shell.sourceforge.net/) nebo pyshell (http://pyshell.sourceforge.net/).
3. Elegantnější způsoby? Přesně to co tu popisuješ dělá Pipe objekt, stačí se jen pozorněji podívat do zdrojáku, jenže...
za a) má jednodušší zápis, což je obzvláště šikovné při interaktivní práci v Pythoní konzoli,
za b) používá push místo pull metody,
a především za c) umožňuje snadno zřetězit více objektů, což klasický zápis který uvádíš neumožňuje, rozhodně ne jednoduše.
[9] Já to chápu, ale tady nejde o čísla nebo řetězce, ani o "jednoduché" datové struktury jako seznam nebo slovník. Jak chcete *obecně* definovat sčítání faktur? Přitom taková operace není nemyslitelná. A tady ani nejde o tak jasný koncept jako aritmetická operace, ale o operaci logickou - OR. Je naprosto korektní definovat logické operace nad objekty v souladu s pravidly pro konkrétní problémovou doménu.
[12] Pravidla "pro konkrétní doménu" jsou dána de iure nebo de facto standardy, tedy nejvíce používaným řešením. Například máme de iure standard pro operace s čísly, příkladem de facto standardu jsou operátory pro specifikaci gramatiky v modulu pyparsing.
De facto standard pro operaci transformace dat pomocí objektů je roura, kde levý operand je zdrojem dat a pravý filtrem. Výsledek "výrazu" může být zdrojem (levou stranou) dalšího výrazu roura. Je jedno zda operandy jsou programy, objekty programovacího jazyka nebo hvězdy na nebi, princip je stále stejný.
Možná se vám nezdá použití interního mechanizmu pro zpracování OR jako reprezentace pro operaci transformace, ale když se nad tím zamyslíte, tak to není vůbec od věci. Způsob vyhodnocení výrazu je v obou případech shodný, a výsledkem jsou vstupní data levého operandu prohnaná "filtrem" pravého operandu (ve smyslu binárního OR, nikoliv logického).
Teda obdivuji vaši kreativitu, opravdu povedený nápad.
Dostal jsem neodolatelnou chuť něco takového si vyzkoušet taky, ani ne tak vážně jako jste to pojal Vy, ale prostě vyzkoušet něco nového a něco se u toho naučit.
Nejsem pythonista, tak si s tím hraju v Ruby. Zatím přemýšlím nad návrhem, původně jsem to chtěl hodně stavět na preprocesoru, ale pak jsem si uvědomil, že by to byla docela nuda a jen primitivní bušení dat do PC, bez nějaké větší zábavy, de facto hlavně prohazování příkazů a objektů a dávání tam tečky (sort co => co.sort). Navíc bylo by to takové, abych tak řekl "málo nativní", mohlo by to v obskurních případech dělat neplechu.
Čili současný návrh je asi dost podobný tomu Vašemu. Na volání externích příkazů používám method_missing. Tedy resp. nejčastější chování při "nálezu" chybějící metody je volání externího příkazu s daným jménem a parametry.
Pro vlastní konsoli využívám přímo knihovnu "irb", tedy právě tu, co tvoří interaktivní ruby konsoli (prostě totéž co python -i).
Přeji krásný den a mnoho úspěchů s Vaší PowerConsolí :)
<p>Je to výborná věc, nicméně žádná novinka. V IPythonu to už je dlouho, zkusit si to může každý: from ipipe import *</p>
<p>Jinak více viz: <a href=""http://projects.scipy.org/ipython/ipython/wiki/UsingIPipe>Using ipipe</a></p>
<p>Vubec mám tak trochu pocit, že celý tenhle projekt je tak trochu znovuvynalézáním kola (IPythonu). Ale jinak nic proti ;-)</p>
[15] Díky za informaci o ipipe, nějak jsem tenhle extension přehlédl (a to IPython používám když zrovna nedělám v SPE). Ale jak tak koukám do zdrojáku ipipe, je to docela moloch :-) A funguje bohužel jen v IPythonu, a to pouze přímo z příkazové řádky, nikoliv v uživatelem definovaných funkcích (a to ani v těch definovaných interaktivně v konzoli IPythonu). Ale zbytečné to nebylo, protože idea použít __ror__ pro napojení výstupu roury na jakýkoliv zdrojový objekt mě nenapadla. Určitě to do své verze doplním.
Jinak PowerConsole není znovuvynalézání IPythonu, protože koncepce je zcela odlišná. IPython je primárně vylepšený shell Pythonu, nic víc a nic míň. A svůj účel plní výborně (i když osobně se mi na něm pár věcí nelíbí).
Cílem PowerConsole není vytvořit lepší shell Pythonu, ale vytvořit prostředí pro tvorbu shellů s různým rozhraním a variabilní sadou příkazů dle problémové domény (viz např. přímá podpora SQL příkazů) "okořeněná" možností promíchat tyto příkazy s kódem psaným v Pythonu. Něco jako velmi sofistikovaná verze standardního modulu cmd z Pythonu.
Nez tohle reseni, to uz se mi spis zamlouvala ukazka na ASPN. Prijde mi elegantnejsi.
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/276960
[14] Jakube, není co obdivovat, my takové věci máme už roky. :-)
http://kronavita.de/chris/data/pipeline_rdoc.html
http://whytheluckystiff.net/ruby/ruby-pipe.rb
(Trošku přeháním, Pavel měl samozřejmě moc dobrý nápad. Zdar a sílu ohnivákovi i hadovi! :-))
[2] Radši budu používat jazyky pro chytré lidi (Ruby, Lisp) než jazyky pro blbečky. :-) Ano, člověk se občas spálí, ale radši trochu toho občasného rizika a myšlenkovou volnost po drtivou většinu zbývajícího času, než abych ve vatičce B&D jazyka smolil vyšší struktury až o patro nad jazykem. (Kam podle mě rozhodně nepatří, protože pak nejsou pořádně vidět. O problému laditelnosti těchhle věcí (a její časové efektivity) se mezi fanoušky Kafe a spol. jaksi "taktně" mlčí. :-))
*22.6.1968
Od mala mě fascinoval potenciál počítačů a od prvního osobního seznámení s nimi jsem věděl, že tahle „věcička“ je přesně tím, čím se chci zabývat celý život. Hned po maturitě jsem si našel práci, kde jsem s nimi mohl pracovat a hlavně učit se. V průběhu let jsem vystřídal řadu zaměstnavatelů a specializací (např. ekonomické systémy, implementace BIOSu pro CP/M, řízení tech. procesů) až jsem nakonec na dlouhá léta zakotvil u Delphi a databází (hlavně InterBase), nejdříve ve firmě PCS, pak AKTIS (nyní ABRA) a posléze Borland ČR. Od uvolnění zdrojových textů InterBase v r. 2000 a zrodu projektu Firebird se podílím na jeho vývoji (nyní hlavně jako QA manager). Od r. 2001 pracuji pro spol. IBPhoenix. Mým preferovaným programovacím jazykem je již dlouhá léta Python.
Přečteno 17 742×
Přečteno 16 909×
Přečteno 8 451×
Přečteno 8 370×
Přečteno 6 617×