Go para Pythonistas

Francesc Campoy Flores

Gopher en Google

Video

Un video de esta charla está disponible en YouTube

Mi estrategia

Dejaros con ganas de aprender Go.

Mi táctica

1. Hablaros de como se parece a Python.

2. Hablaros de como NO se parece a Python.

Python, Go y yo

Software Engineer en Google Feb 11-Aug 12

Go Developer Relations Aug 12 - datetime.now()

Cosas de Python que no me gustan (seré breve)

Elegante y sencillo

Tipos dinámicos - código conciso, como Python.

a = "hola"
b = 1
# pero también
a = 2

Tipos estáticos - puede ser repetitivo, como Java o C++.

Foo foo = new Foo();

Tipos estáticos con tipos inferidos por el compilador, como Go.

a := "hola"
b := 1
// pero no
a = 2

Tipos estáticos en Python? mypy y Cython te podrían interesar.

Pirotecnia en tiempo de ejecución

#!/usr/bin/python

import random

nombre = 'pythonista'

if random.random() > 0.5:
    print 'hey '+nombre+', has ganado!'
else:
    print 'lo siento '+nonbre+', has perdido'

No quiero empezar una guerra, pero ...

Una cobertura del 100% del código es un síntoma

Otras cosas que no me gustan

Una lista de los métodos mágicos de Python:

Pero *sí* que me gusta la concurrencia!

Mucho se ha dicho sobre el Global Interpreter Lock.

Os recomiendo Mindblowing Python GIL, de David Beazley.

Cosas que me gustan de Python

Cosas que me gustan de Python

Un poco de código

fib.py

Os suena Fibonacci?

#!/usr/bin/python

def fib(n):
    a, b = 0, 1
    for i in range(n):
        a, b = b, a + b
    return b

def fib_rec(n):
    if n <= 1:
        return 1
    else:
        return fib_rec(n-1) + fib_rec(n-2)

for x in range(10):
    print fib(x), fib_rec(x)

fib.go

Algo familiar?

package main

import "fmt"

func fib(n int) int {
    a, b := 0, 1
    for i := 0; i < n; i++ {
        a, b = b, a+b
    }
    return b
}

func fibRec(n int) int {
    if n <= 1 {
        return 1
    }
    return fibRec(n-1) + fibRec(n-2)
}

func main() {
    for i := 0; i < 10; i++ {
        fmt.Println(fib(i), fibRec(i))
    }
}

Fibonacci sin generators? Cómo?

Los generators Python son geniales.

def fib(n):
    a, b = 0, 1
    for i in range(n):
        a, b = b, a + b
        yield a

Conceptualmente complejos.

#!/usr/bin/python

def fib(n):
    a, b = 0, 1
    for i in range(n):
        a, b = b, a + b
        yield a

f = fib(10)
try:
    while True:
        print f.next()
except StopIteration:
    print 'done'

for x in fib(10):
	print x
print 'done'

Pero muy fáciles de usar.

#!/usr/bin/python

def fib(n):
    a, b = 0, 1
    for i in range(n):
        a, b = b, a + b
        yield a

f = fib(10)
try:
	while True:
		print f.next()
except StopIteration:
	print 'done'

for x in fib(10):
    print x
print 'done'

Generators Python

Parece que se ejecuten concurrentemente. Hmmm ... me gusta la concurrencia.

Concurrencia en Go

Basada en goroutines y channels

Goroutines al estilo "generator"

Goroutines al estilo "generator"

En vez de yield enviamos los valores por el canal.

func fib(n int, c chan int) {
    a, b := 0, 1
    for i := 0; i < n; i++ {
        a, b = b, a+b
        c <- a
    }
    close(c)
}

range recibe todos los valores de un canal hasta su cierre.

package main

import "fmt"

func fib(n int, c chan int) {
	a, b := 0, 1
	for i := 0; i < n; i++ {
		a, b = b, a+b
		c <- a // HL
	}
	close(c)
}

func main() {
    c := make(chan int)
    go fib(10, c)

    for x := range c {
        fmt.Println(x)
    }
}

Go routines al rescate

O incluso más al estilo Python:

package main

import "fmt"

func fib(n int) chan int {
    c := make(chan int)
    go func() {
        a, b := 0, 1
        for i := 0; i < n; i++ {
            a, b = b, a+b
            c <- a
        }
        close(c)
    }()
    return c
}

func main() {
    for x := range fib(10) {
        fmt.Println(x)
    }
}

Ejercicio: generador de números primos

Escribe una función que devuelve un canal en el cual envia los n primeros
números primos.

Dada la función prime:

// prime returns true if n is a prime number.
func prime(n int) bool {
    for i := 2; i < n; i++ {
        if n%i == 0 {
            return false
        }
    }
    return true
}

Utiliza el Go playground:

Solución: generador de números primos

func primes(n int) chan int {
    c := make(chan int)
    go func() {
        for i := 1; n > 0; i++ {
            if prime(i) {
                c <- i
                n--
            }
        }
        close(c)
    }()
    return c
}
package main

import "fmt"

// prime returns true if n is a prime number.
func prime(n int) bool {
	for i := 2; i < n; i++ {
		if n%i == 0 {
			return false
		}
	}
	return true
}

// primes returns a channel of ints on which it writes the first n prime
// numbers before closing it.
func primes(n int) chan int {
	c := make(chan int)
	go func() {
		for i := 1; n > 0; i++ {
			if prime(i) {
				c <- i
				n--
			}
		}
		close(c)
	}()
	return c
}

func main() {
    for p := range primes(10) {
        fmt.Println(p)
    }
}

Ejercicio: Primos de Fibonacci

Escribe una función filterPrimes que dado un canal de enteros como parámetro
devuelve otro canal de enteros.

Todos los números primos que filterPrimes reciba del canal de entrada los ha
de enviar en el canal de salida.

Completa este código:

Solución: Primos de Fibonacci

func filterPrimes(cin chan int) chan int {
    cout := make(chan int)
    go func() {
        for v := range cin {
            if prime(v) {
                cout <- v
            }
        }
        close(cout)
    }()
    return cout
}
package main

import "fmt"

// prime returns true if n is a prime number.
func prime(n int) bool {
	for i := 2; i < n; i++ {
		if n%i == 0 {
			return false
		}
	}
	return true
}

// fib returns a channel on which the first n Fibonacci numbers are written.
func fib(n int) chan int {
	c := make(chan int)
	go func() {
		a, b := 0, 1
		for i := 0; i < n; i++ {
			a, b = b, a+b
			c <- a
		}
		close(c)
	}()
	return c
}

// filterPrimes returns a channel of ints on which it writes all the prime
// numbers read from cin, and closes the returned channel when cin is closed.
func filterPrimes(cin chan int) chan int {
	cout := make(chan int)
	go func() {
		for v := range cin {
			if prime(v) {
				cout <- v
			}
		}
		close(cout)
	}()
	return cout
}

func main() {
    for p := range filterPrimes(fib(20)) {
        fmt.Println(p)
    }
}

Hay mucho más

Las goroutines y los channels no solo sirven para modelar generadores. También
pueden modelar muchos otros sistemas concurrentes.

Para aprender más:

Go orientado a objetos

Go orientado a objetos

Una declaración de tipo.

type Name struct {
    First  string
    Middle string
    Last   string
}

Una declaración de método.

func (n Name) String() string {
    return fmt.Sprintf("%s %c. %s", n.First, n.Middle[0], strings.ToUpper(n.Last))
}

Construyendo y usando un Name.

package main

import (
	"fmt"
	"strings"
)

type Name struct {
	First  string
	Middle string
	Last   string
}

func (n Name) String() string {
	return fmt.Sprintf("%s %c. %s", n.First, n.Middle[0], strings.ToUpper(n.Last))
}

type SimpleName string

func (s SimpleName) String() string { return string(s) }

func main() {
    n := Name{"William", "Mike", "Smith"}
    fmt.Printf("%s", n.String())
	return
	// second OMIT
	n = Name{"William", "Mike", "Smith"}
	fmt.Println(n)
}

Métodos en otros tipos

Hay más que structs.

type SimpleName string

Se pueden definir métodos en cualquier tipo.

func (s SimpleName) String() string { return string(s) }

O casi cualquiera.

func (s string) NoWay()

Se pueden declarar métodos sólo en tipos definidos en el mismo paquete.

Duck typing

Duck typing

Si anda como un pato ...

Qué define un pato?

s/pato/file-like object/

Cuac?

Interfaces Go

Simplemente una lista de métodos.

El paquete fmt define:

type Stringer interface {
    String() string
}

fmt.Println utiliza el método String si el parámetro pasado es un Stringer.

package main

import (
	"fmt"
	"strings"
)

type Name struct {
	First  string
	Middle string
	Last   string
}

func (n Name) String() string {
	return fmt.Sprintf("%s %c. %s", n.First, n.Middle[0], strings.ToUpper(n.Last))
}

type SimpleName string

func (s SimpleName) String() string { return string(s) }

func main() {
	n := Name{"William", "Mike", "Smith"}
	fmt.Printf("%s", n.String())
	return
    n = Name{"William", "Mike", "Smith"}
    fmt.Println(n)
}

Satisfacción implícitia == No "implements"

Si un tipo tiene todos los métodos de una interfaz la implementa.

Structural typing: no solo suena como un pato, es un pato.

Ejemplo:

type myWriter interface {
    Write([]byte) (int, error)
}
package main

import (
	"fmt"
	"os"
)

type myWriter interface {
	Write([]byte) (int, error)
}

func main() {
    var w myWriter
    w = os.Stdout
    fmt.Fprintln(w, "hello")
}

Decoradores

Decoradores

Una manera conveniente de envolver una función existente.

def auth_required(myfunc):
    def checkuser(self):
        user = parse_qs(urlparse(self.path).query).get('user')
        if user:
            self.user = user[0]
            myfunc(self)
        else:
            self.wfile.write('unknown user')
    return checkuser

Una función se puede decorar con @.

class myHandler(BaseHTTPRequestHandler):
    @auth_required
    def do_GET(self):
        self.wfile.write('Hello, %s!' % self.user)

Decoradores

Si lo ejecutamos

#!/usr/bin/python

from BaseHTTPServer import BaseHTTPRequestHandler,HTTPServer
from urlparse import urlparse,parse_qs

PORT_NUMBER = 8080

def auth_required(myfunc):
	def checkuser(self):
		user = parse_qs(urlparse(self.path).query).get('user')
		if user:
			self.user = user[0]
			myfunc(self)
		else:
			self.wfile.write('unknown user')
	return checkuser


class myHandler(BaseHTTPRequestHandler):
	@auth_required
	def do_GET(self):
		self.wfile.write('Hello, %s!' % self.user)

try:
    server = HTTPServer(('', PORT_NUMBER), myHandler)
    server.serve_forever()

except KeyboardInterrupt:
    server.socket.close()

Petición no autorizada:

Petición autorizada:

Decoradores en Go?

No exactamente, pero casi.

Go no ofrece decoradores en el lenguaje, pero las funciones literals y las reglas de scoping sencillas hace que sea fácil implementar algo similar.

var hiHandler = authRequired(
    func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hi, %v", r.FormValue("user"))
    },
)

La función de envoltorio.

func authRequired(f http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        if r.FormValue("user") == "" {
            http.Error(w, "unknown user", http.StatusForbidden)
            return
        }
        f(w, r)
    }
}

Decoradores en Go?

package main

import (
	"fmt"
	"net/http"
)

func authRequired(f http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if r.FormValue("user") == "" {
			http.Error(w, "unknown user", http.StatusForbidden)
			return
		}
		f(w, r)
	}
}

var hiHandler = authRequired(
	func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Hi, %v", r.FormValue("user"))
	},
)

func main() {
    http.HandleFunc("/hi", hiHandler)
    http.ListenAndServe(":8080", nil)
}

Petición no autorizada:

Petición autorizada:

Ejercicio: errores en handlers HTTP

En Go, las funciones suelen devolver errores para indicar que algo malo ha sucedido.

El paquete net/http de la standard library define el tipo HandlerFunc.

type HandlerFunc func(ResponseWriter, *Request)

Pero a menudo es útil unificar la gestión de errores en una sola función para evitar repeticiones.

type errorHandler func(http.ResponseWriter, *http.Request) error

Escribe un decorador que dado un errorHandler devuelva un http.HandlerFunc.
Si un error sucede crea un log y devuelve una página de error http.

Ejercicio: errores en handlers HTTP (continuación)

Dada la función handler.

func handler(w http.ResponseWriter, r *http.Request) error {
    name := r.FormValue("name")
    if name == "" {
        return fmt.Errorf("empty name")
    }
    fmt.Fprintln(w, "Hi,", name)
    return nil
}

La queremos utilizar como sigue.

    http.HandleFunc("/hi", handleError(handler))

Implementa handleError usando el playground.

Solución: errores en handlers HTTP

func handleError(f errorHandler) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        err := f(w, r)
        if err != nil {
            log.Printf("%v", err)
            http.Error(w, "Oops!", http.StatusInternalServerError)
        }
    }
}
    // Fake request without 'name' parameter.
    r := &http.Request{}
    w := newDummyResp()
    handleError(handler)(w, r)
    fmt.Println("resp a:", w)
package main

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"net/http"
)

type errorHandler func(http.ResponseWriter, *http.Request) error

func handleError(f errorHandler) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		err := f(w, r)
		if err != nil {
			log.Printf("%v", err)
			http.Error(w, "Oops!", http.StatusInternalServerError)
		}
	}
}

func handler(w http.ResponseWriter, r *http.Request) error {
	name := r.FormValue("name")
	if name == "" {
		return fmt.Errorf("empty name")
	}
	fmt.Fprintln(w, "Hi,", name)
	return nil
}

// resp implements http.ResponseWriter writing
type dummyResp struct {
	io.Writer
	h int
}

func newDummyResp() http.ResponseWriter {
	return &dummyResp{Writer: &bytes.Buffer{}}
}

func (w *dummyResp) Header() http.Header { return make(http.Header) }
func (w *dummyResp) WriteHeader(h int)   { w.h = h }
func (w *dummyResp) String() string      { return fmt.Sprintf("[%v] %q", w.h, w.Writer) }

func main() {
	http.HandleFunc("/hi", handleError(handler))

	// ListenAndServe is not allowed on the playground.
	// http.ListenAndServe(":8080", nil)

	// In the playground we call the handler manually with dummy requests.

	// Fake request without 'name' parameter.
	r := &http.Request{}
	w := newDummyResp()
	handleError(handler)(w, r)
	fmt.Println("resp a:", w)

    // Fake request with 'name' parameter 'john'.
    r.Form["name"] = []string{"john"}
    w = newDummyResp()
    handleError(handler)(w, r)
    fmt.Println("resp b:", w)

}

Monkey patching

Monkey patching

"A monkey patch is a way to extend or modify the run-time code of dynamic languages without altering the original source code." - Wikipedia

Monkey patching

También llamado "duck punching" ... pobres patos!

Técnica utilizada a menudo para test.

Por ejemplo, si queremos validar:

def say_hi(usr):
    if auth(usr):
        print 'Hi, %s' % usr
    else:
        print 'unknown user %s' % usr

Que depende de una función que hace peticiones HTTP:

def auth(usr):
    try:
        r = urllib.urlopen(auth_url + '/' + usr)
        return r.getcode() == 200
    except:
        return False

Monkey patching

Podemos validar say_hi sin necesidad de peticiones HTTP remplazando auth.

#!/usr/bin/python

import urllib

auth_url = 'http://google.com'

def auth(usr):
	try:
		r = urllib.urlopen(auth_url + '/' + usr)
		return r.getcode() == 200
	except:
		return False

def say_hi(usr):
	if auth(usr):
		print 'Hi, %s' % usr
	else:
		print 'unknown user %s' % usr

def sayhitest():
    # Test authenticated user
    globals()['auth'] = lambda x: True
    say_hi('John')

    # Test unauthenticated user
    globals()['auth'] = lambda x: False
    say_hi('John')

sayhitest()

Gopher punching!

El mismo efecto se puede conseguir con Go.

func sayHi(user string) {
    if !auth(user) {
        fmt.Printf("unknown user %v\n", user)
        return
    }
    fmt.Printf("Hi, %v\n", user)
}

Que depende de

var auth = func(user string) bool {
    res, err := http.Get(authURL + "/" + user)
    return err == nil && res.StatusCode == http.StatusOK
}

Gopher punching!

Nuestro test puede cambiar el valor de auth fácilmente.

package main

import (
	"fmt"
	"net/http"
)

var authURL = ""

var auth = func(user string) bool {
	res, err := http.Get(authURL + "/" + user)
	return err == nil && res.StatusCode == http.StatusOK
}

func sayHi(user string) {
	if !auth(user) {
		fmt.Printf("unknown user %v\n", user)
		return
	}
	fmt.Printf("Hi, %v\n", user)
}

func TestSayHi() {
    auth = func(string) bool { return true }
    sayHi("John")

    auth = func(string) bool { return false }
    sayHi("John")
}

func init() {
	auth = func(string) bool { return true }
}

func TestAnythingElse() {
	// auth has been already set to the fake version
}

func main() {
	TestSayHi()
}

Conclusión

Conclusión

Go es un poco como Python

pero también un poco distinto

Disclaimer :

Pruébalo

Próximos pasos.

Aprende Go desde tu navegador.

Nuestra comunidad: golang-nuts

Thank you

Francesc Campoy Flores

Gopher en Google