Go for Pythonistas
Francesc Campoy Flores
Gopher at Google
Francesc Campoy Flores
Gopher at Google
A recording of this talk is available.
2Whetting your appetite for Go
1. Showing you how Go is like Python.
2. Showing you how Go is not like Python.
4Software Engineer at Google: Feb 11-Aug 12
Go Developer Relations: Aug 12 - datetime.now()
Dynamic typing - nice because it's concise, like Python.
a = "hello" b = 1 # but also a = 2
Static typing - can be verbose, like Java or C++.
Foo foo = new Foo();
Static typing with inferred types, like Go.
a := "hello" b := 1 // but no a = 2
Statically-typed Python? Check mypy and Cython.
7#!/usr/bin/python
import random
name = 'pythonista' # This code only works half of the time. if random.random() > 0.5: print 'hey '+name+', you win!' else: print 'sorry '+nane+', you lose'
I don't want start a flame war here but ...
100% code coverage is a symptom
__magic__
: **kargs
, __getattr__
)A list of magic methods in Python:
www.rafekettler.com/magicmethods.html
9A lot has been said about Python's infamous Global Interpreter Lock.
You should watch Mindblowing Python GIL, by David Beazley.
10Have you ever heard of 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)
Something familiar?
// +build ignore,OMIT
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)) } }
Python generators are awesome.
def fib(n): a, b = 0, 1 for i in range(n): a, b = b, a + b yield a
Mechanically complex.
#!/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'
But very easy to use.
#!/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'
Note the generator executes concurrently. Hmm... I like concurrency.
17Based on goroutines and channels.
Uses a channel send instead of yield
.
func fib(c chan int, n int) { a, b := 0, 1 for i := 0; i < n; i++ { a, b = b, a+b c <- a } close(c) }
// +build ignore,OMIT
package main
import "fmt"
func fib(c chan int, n 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(c, 10) for x := range c { fmt.Println(x) } }
A more generator-like style:
// +build ignore,OMIT
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) } }
Write a function that returns a channel and sends the first n prime numbers on
it.
Given the function 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 }
Use the Go playground:
22func 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 }
// +build ignore,OMIT
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) } }
Write a filterPrimes
function that takes a channel of ints as a
parameter and returns another channel of ints.
All the prime numbers that filterPrimes
receives from the input channel are
sent into the output channel.
Complete this code snippet:
24func 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 }
// +build ignore,OMIT
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) } }
Goroutines and channels aren't just for generators. They can be used to model
all kinds of concurrent systems.
To learn more:
A type declaration.
type Name struct { First string Middle string Last string }
A method declaration.
func (n Name) String() string { return fmt.Sprintf("%s %c. %s", n.First, n.Middle[0], strings.ToUpper(n.Last)) }
Constructing a Name
and using it.
// +build ignore,OMIT
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)
}
There's more to types than structs.
type SimpleName string
You can define methods on any type.
func (s SimpleName) String() string { return string(s) }
Or almost any type.
func (s string) NoWay()
You can only define methods on types within the same package.
29If it walks like a duck ...
What defines a duck?
s/duck/file-like object/g
31Simply a set of methods.
From the fmt
package:
type Stringer interface { String() string }
fmt.Println
calls the String method if the parameter is a Stringer
.
// +build ignore,OMIT
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)
}
A type with all the methods of the interface implements the interface.
Implicit satisfaction == No "implements"
Structural typing: it doesn't just sound like a duck, it is a duck.
And that's checked at compile time.
33A convenient way to wrap a function.
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
A function can be decorated using @
.
class myHandler(BaseHTTPRequestHandler): @auth_required def do_GET(self): self.wfile.write('Hello, %s!' % self.user)
If we run it.
#!/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()
This is unauthorized:
This is authorized:
36Not exactly, but close enough.
Go doesn't provide decorators in the language, but its function literal syntax and simple scoping rules make it easy to do something similar.
var hiHandler = authRequired( func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hi, %v", r.FormValue("user")) }, )
A wrapper function.
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) } }
// +build ignore,OMIT
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) }
This is unauthorized:
This is authorized:
38In Go, functions can return errors to indicate that something bad happened.
The net/http
package from the standard library defines the type HandlerFunc
.
type HandlerFunc func(ResponseWriter, *Request)
But it's often useful to unify the error handling into a single function to avoid
repetition.
type errorHandler func(http.ResponseWriter, *http.Request) error
Write a decorator that given a errorHandler
returns a http.HandlerFunc
.
If an error occurs it logs it and returns an http error page.
Given the function 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 }
We want to use it as follows.
http.HandleFunc("/hi", handleError(handler))
Implement handleError
using the playground.
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)
// +build ignore,OMIT
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)
}
"A monkey patch is a way to extend or modify the run-time code of dynamic languages without altering the original source code." - Wikipedia
Also known as "duck punching" ... poor duck.
Often used for testing purposes.
For example, say we want to test this function:
def say_hi(usr): if auth(usr): print 'Hi, %s' % usr else: print 'unknown user %s' % usr
Which depends on a function that makes an HTTP request:
def auth(usr): try: r = urllib.urlopen(auth_url + '/' + usr) return r.getcode() == 200 except: return False
We can test say_hi
without making HTTP requests by stubbing out 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()
The same effect can be achieved in Go.
func sayHi(user string) { if !auth(user) { fmt.Printf("unknown user %v\n", user) return } fmt.Printf("Hi, %v\n", user) }
Which depends on
var auth = func(user string) bool { res, err := http.Get(authURL + "/" + user) return err == nil && res.StatusCode == http.StatusOK }
Our test code can change the value of auth easily.
// +build ignore,OMIT
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()
}
Go is a bit like Python
but a bit different too
Disclaimer :
Next steps
Learn Go from your browser
The community: golang-nuts
groups.google.com/d/forum/golang-nuts
50