En 128 líneas de código entra exactamente ESTE browser.
Por supuesto, podría hacer más, pero hasta yo tengo mis standards!
No usar
;
No usar
if whatever: f()
Salvo eso, hice algunos trucos sucios, pero, en este momento, es un browser bastante completo en 127 líneas de código según sloccount, así que ya jugué suficiente y mañana tengo trabajo que hacer.
Pero antes, consideremos como se implementaron algunos features (voy a cortar las líneas para que la página quede razonablemente angosta), y veamos también las versiones "normales" de lo mismo. La versión "normal" no está probada, avisen si está rota ;-)
Esto noes algo que deba aprenderse. De hecho es casi un tratado en como no hacer las cosas. Es el código menos pitónico y menos claro que vas a ver esta semana.
Es corto, es expresivo, pero es feo feo.
Voy a comentar sobre esta versión.
Soporte deProxy
Un browser no es gran cosa si no se puede usar con proxy. Por suerte el stack de red de Qt tiene buen soporte de proxy. El chiste es configurarlo.
De Vicenzo soporta proxies HTTP y SOCKS parseando la variable de entorno http_proxy
y seteando el proxy a nivel aplicación en Qt:
proxy_url = QtCore.QUrl(os.environ.get('http_proxy', '')) QtNetwork.QNetworkProxy.setApplicationProxy(QtNetwork.QNetworkProxy(\ QtNetwork.QNetworkProxy.HttpProxy if unicode(proxy_url.scheme()).startswith('http')\ else QtNetwork.QNetworkProxy.Socks5Proxy, proxy_url.host(),\ proxy_url.port(), proxy_url.userName(), proxy_url.password())) if\ 'http_proxy' in os.environ else None
Como es la versión normal de esa cosa?
if 'http_proxy' in os.environ: proxy_url = QtCore.QUrl(os.environ['http_proxy']) if unicode(proxy_url.scheme()).starstswith('http'): protocol = QtNetwork.QNetworkProxy.HttpProxy else: protocol = QtNetwork.QNetworkProxy.Socks5Proxy QtNetwork.QNetworkProxy.setApplicationProxy( QtNetwork.QNetworkProxy( protocol, proxy_url.host(), proxy_url.port(), proxy_url.userName(), proxy_url.password()))
Los abusos principales contra python son el uso del operador ternario para hacer un if de una línea (y anidarlo) y el largo de línea.
Propiedades y Señales al crear un objeto
Este feature está en versiones recientes de PyQt: si pasás nombres de propiedades como argumentos con nombre, se les asigna el valor. Si pasás una señal como argumento con nombre, se conectan al valor.
Es un feature excelente, que te ayuda a crear código claro, local y conciso, y me encanta tenerlo. Pero si te querés ir a la banquina, es mandada a hacer.
Esto está por todos lados en De Vicenzo, éste es sólo un ejemplo (sí, es una sola línea):
QtWebKit.QWebView.__init__(self, loadProgress=lambda v:\ (self.pbar.show(), self.pbar.setValue(v)) if self.amCurrent() else\ None, loadFinished=self.pbar.hide, loadStarted=lambda:\ self.pbar.show() if self.amCurrent() else None, titleChanged=lambda\ t: container.tabs.setTabText(container.tabs.indexOf(self), t) or\ (container.setWindowTitle(t) if self.amCurrent() else None))
Por adonde empiezo...
Hay expresiones lambda
usadas para definir los callbacks en el lugar en vez de conectarse con una función o método "de verdad".
Hya lambdas con el operador ternario:
Hay lambdas que usan or
o una tupla para engañar al intérprete y que haga más de una cosa en un solo lambda!
No voy ni a intentar desenredar esto con fines educativos, pero digamos que esa línea contiene cosas que deberían ser 3 métodos separados, y debería estar repartida en 6 líneas o mas.
Download Manager
Llamarlo un manager es exagerar porque no se puede parar una descarga después que empieza, pero bueno, te deja bajar cosas y seguir browseando, y te da un reporte de progreso!
Primero, en la línea 16
creé un diccionario bars
para llevar registro de los downloads.
Después, tenía que delegar el contenido no soportado al método indicado, y eso se hace en las líneas 108 and 109
Básicamente, con eso cada vez que hacés click en algo que WebKit no puede manejar, se llama al método fetch
con el pedido de red como argumento.
def fetch(self, reply): destination = QtGui.QFileDialog.getSaveFileName(self, \ "Save File", os.path.expanduser(os.path.join('~',\ unicode(reply.url().path()).split('/')[-1]))) if destination: bar = QtGui.QProgressBar(format='%p% - ' + os.path.basename(unicode(destination))) self.statusBar().addPermanentWidget(bar) reply.downloadProgress.connect(self.progress) reply.finished.connect(self.finished) self.bars[unicode(reply.url().toString())] = [bar, reply,\ unicode(destination)]
No hay mucho golf acá salvo las líneas largas, pero una vez que metés enters es la manera obvia de hacerlo:
Pedí un nombre de archivo
Creás un progressbar, lo ponés en el statusbar, y lo conectas a las señales de progreso de la descarga.
Entonces, por supuesto, está el slot progress
que actualiza la barra:
progress = lambda self, received, total:\ self.bars[unicode(self.sender().url().toString())][0]\ .setValue(100. * received / total)
Sí, definí un método como lambda para ahorrar una línea. [facepalm]
Y elslot finished
para cuando termina el download:
def finished(self): reply = self.sender() url = unicode(reply.url().toString()) bar, _, fname = self.bars[url] redirURL = unicode(reply.attribute(QtNetwork.QNetworkRequest.\ RedirectionTargetAttribute).toString()) del self.bars[url] bar.deleteLater() if redirURL and redirURL != url: return self.fetch(redirURL, fname) with open(fname, 'wb') as f: f.write(str(reply.readAll()))
hasta soporta redirecciones correctamente! Más allá d eso, nada más esconde la barra, guarda los datos, fin del cuentito. La línea larga ni siquiera es mi culpa!
Hay un problema en que el archivo entero se mantiene en memoria hasta el fin de la descarga. Si te bajás un DVD, te va a doler.
Usar el with
ahorra una línea y no pierde un file handle, comparado con las alternativas.
Impresión
De nuevo Qt me salva las papas, porque hacer esto a mano debe ser difícil. Sin embargo, resulta que el soporte de impresión... está hecho. Qt, especialmente usado vía PyQt es tan completo!
self.previewer = QtGui.QPrintPreviewDialog(\ paintRequested=self.print_) self.do_print = QtGui.QShortcut("Ctrl+p",\ self, activated=self.previewer.exec_)
No necesité nada de golf. Eso es exactamente el código que se necesita, y es la manera recomendada de enganchar "Ctrl+p" con la impresión de la página.
Otros Trucos
No hay otros trucos. Todo lo que queda es crear widgets, conectar unas cosas con otras, y disfrutar la increíble experience de programar PyQt, donde podés escribir un web browser entero (salvo el motor) en 127 líneas de código.