Object Oriented programming in Python.
Originally part 3 of a 4 lectures seminar for the Networking class of the Computer Science course at the University of Parma
2. Aumentare il dinamismo
Python offre come builtin getattr, setattr e
hasattr
f.foo getattr(f, ‘foo’)
f.foo = 4 setattr(f, ‘foo’, 4)
hasattr controlla solo se c’è l’attributo
Questo vuole dire che possiamo chiedere
un attributo a runtime conoscendone il
nome
Forte capacità di introspezione (assieme al
3. Esempio
class Capsule(object):
def __init__(self, obj):
self.obj = obj
def get(self, attribute_path):
parts = attribute_path.split('.')
current_obj = self.obj
for attribute in parts:
current_obj = getattr(current_obj, attribute)
return current_obj
if __name__ == "__main__":
d = datetime.date(1983, 7, 1)
e = Capsule(Employee('Enrico Franchi', d))
print e.get('birth_date.month')
4. Ripasso MRO
Sia a oggetto di tipo A, sottoclasse di B e
C
a.foo cerca nell’ordine:
1.fra gli attributi dell’oggetto a
2.fra gli attributi dell’oggetto classe A
3.fra gli attributi dell’oggetto classe B
4.fra gli attributi dell’oggetto classe C
Se non viene trovato, viene lanciato
AttributeError
5. Eppure...
Di fatto prima di lanciare AttributeError, si
vede se è disponibile un metodo
__getattr__(self, name)
In questo caso viene passato il nome
dell’attributo come stringa (in name)
Il corpo di __getattr__ può specificare cosa
ritornare (o se lanciare eccezione)
NB: siccome i metodi sono anche attributi,
funziona anche con loro
Per i “PRO” ci sono anche i descrittori...
6. Esempio
class Capsule(object):
def __init__(self, obj):
self.obj = obj
def get(self, attribute_path):
parts = attribute_path.split('.')
current_obj = self.obj
for attribute in parts:
current_obj = getattr(current_obj, attribute)
return current_obj
def __getattr__(self, attribute):
return getattr(self.obj, attribute)
7. Esempio
class Capsule(object):
def __init__(self, obj):
self.obj = obj
def get(self, attribute_path):
parts = attribute_path.split('.')
current_obj = self.obj
for attribute in parts:
current_obj = getattr(current_obj, attribute)
return current_obj
def __getattr__(self, attribute):
return getattr(self.obj, attribute)
Facile delegare...
(pensate un po’ al decorator pattern...)
8. Esempio
class Enum(object):
def __init__(self, *args, **kargs):
self._enums = dict(((name, i) for i, name in enumerate(args)))
min_val, max_val = 0, len(args)
for k, v in kargs.iteritems():
if min_val < v < max_val:
raise ValueError, ('Value %s already specified' % v)
else:
self._enums[k] = v
def __getattr__(self, name):
try:
return self._enums[name]
except KeyError:
raise AttributeError
9. Esempio
class Enum(object):
def __init__(self, *args, **kargs):
self._enums = dict(((name, i) for i, name in enumerate(args)))
min_val, max_val = 0, len(args)
for k, v in kargs.iteritems():
if min_val < v < max_val:
raise ValueError, ('Value %s already specified' % v)
else:
self._enums[k] = v
def __getattr__(self, name):
try:
return self._enums[name]
except KeyError:
raise AttributeError
Per Enum fatte a modo, qui.
Meglio ancora le named tuples.
10. Funzioni create a runtime
class Collection(object):
def __init__(self, l=None):
self.buf = l or []
def find(self, **kargs):
temp_buf = self.buf
for k, v in kargs.iteritems():
temp_buf = [item for item in temp_buf
if getattr(item, k) == v]
return temp_buf
11. Funzioni create a runtime
class Collection(object):
def __init__(self, l=None):
self.buf = l or []
def find(self, **kargs):
temp_buf = self.buf
for k, v b1 kargs.iteritems():
in = book.Book('Python in a Nutshell', 'Alex Martelli')
temp_buf = [item for item in temp_buf 'Alex Martelli')
b2 = book.Book('Python Cookbook',
b3 if getattr(item, k) == v]
= book.Book('Python', 'Marco Beri')
return temp_buf
b4 = book.Book('Sviluppare applicazioni web con Django',
'Marco Beri')
b5 = book.Book('Espressioni Regolari', 'Marco Beri')
c = Collection([b1, b2, b3, b4, b5])
for b in c.find(title='Python', author='Marco Beri'):
print b
12. Funzioni create a runtime
class Collection(object):
def __init__(self, l=None):
self.buf = l or []
def find(self, **kargs):
temp_buf = self.buf
for k, v in kargs.iteritems():
temp_buf = [item for item in temp_buf
if getattr(item, k) == v]
return temp_buf
@classmethod
def add_method(cls, func, name):
func.im_class = cls
func.im_func = func
func.im_self = None
func.func_name = name
setattr(cls, name, func)
14. __getattr__ avanzato
def __getattr__(self, name):
if name.startswith('find_by_'):
key = name[8:]
def _aux(self, value):
return Collection.find(self, **{key : value})
Collection.add_method(_aux, name)
return getattr(self, name)
else: raise TypeError
Creiamo una funzione a runtime
15. __getattr__ avanzato
def __getattr__(self, name):
if name.startswith('find_by_'):
key = name[8:]
def _aux(self, value):
return Collection.find(self, **{key : value})
Collection.add_method(_aux, name)
return getattr(self, name)
else: raise TypeError
Creiamo una funzione a runtime
La facciamo diventare metodo della classe
16. __getattr__ avanzato
def __getattr__(self, name):
if name.startswith('find_by_'):
key = name[8:]
def _aux(self, value):
return Collection.find(self, **{key : value})
Collection.add_method(_aux, name)
return getattr(self, name)
else: raise TypeError
Creiamo una funzione a runtime
La facciamo diventare metodo della classe
Ora getattr la può trovare (nella fase 2.) e
restituisce il metodo
17. __getattr__ avanzato
def __getattr__(self, name):
if name.startswith('find_by_'):
key = name[8:]
def _aux(self, value):
return Collection.find(self, **{key : value})
Collection.add_method(_aux, name)
return getattr(self, name)
else: raise TypeError
Creiamo una funzione a runtime
for b in c.find_by_author('Marco Beri'):
La facciamo diventare metodo della classe
print b
for b in c.find_by_author('Alex Martelli'):
Ora getattr la può trovare (nella fase 2.) e
print b
restituisce il metodo
18. Server side modules
Scriviamo un gestore (“handler”) che
contiene il codice per implementare il
protocollo
Creiamo un server cui passiamo l’handler
Il server gestisce i dettagli, noi il protocollo
Server prefabbricati:
TCPServer ForkingUDPServer
UDPServer ThreadingTCPServe
r
ForkingTCPServer
19. Handler
Per ogni richiesta viene creato un nuovo
handler
Alcuni server (Threading, Forking)
gestiscono concorrenza, gli altri in serie
con un server Forking di base le modifiche
allo stato dei figli non si riflette sul padre
con un Threading bisogna gestire lock,
etc...
20. Handler (2)
Si sottoclassa un handler
(SocketServer.BaseRequestHandler)
Si “overridda” handle(self) [e altro se
opport.]
Si hanno come variabili d’istanza
self.request: la richiesta (socket)
self.client_address: guess...
self.server: un’istanza del server
Entro certi limiti, si è indipendenti dal
21. Handler (2)
Si sottoclassa un handler
(SocketServer.BaseRequestHandler)
Si “overridda” handle(self) [e altro se
opport.]
Chi scrive trova
Si hanno come variabili d’istanza particolarmente
self.request: la richiesta (socket) orrendo
self.client_address: guess... il termine
self.server: un’istanza del server
Entro certi limiti, si è indipendenti dal
22. import SocketServer
class EchoHandler(SocketServer.BaseRequestHandler):
def handle(self):
print 'Connected from', self.client_address
try:
for message in self.messages():
if message.upper().startswith('QUIT'):
break
self.request.sendall(message)
finally:
self.request.close()
print 'Disconnected from', self.client_address
def messages(self):
while True:
rec_data = self.request.recv(8192)
if not rec_data: break
yield rec_data
try:
srv = SocketServer.ForkingTCPServer(('', 8881), EchoHandler)
srv.serve_forever()
except KeyboardInterrupt:
print 'Stopping server...'
23. Scriviamo Gumball!
Scriviamo un simulatore di macchine che
distribuiscono palline di gomma
Inserisci un quarto di dollaro
Gira la manovella
Fatti restituire il quarto di dollaro
[consegna la pallina]
L’esempio viene da Head First Design
Patterns, Freeman&Freeman, O’Reilly
24.
25. Il metodo tradizionale
class GumballMachine(object):
SOLD_OUT = 0
NO_QUARTER = 1
HAS_QUARTER = 2
SOLD = 3
def __init__(self, count):
self.count = count
self.state = GumballMachine.SOLD_OUT
if self.count > 0:
self.state = GumballMachine.NO_QUARTER
def insert_quarter(self):
if self.state == GumballMachine.HAS_QUARTER:
print "You can't insert another quarter"
elif self.state == GumballMachine.NO_QUARTER:
self.state = GumballMachine.HAS_QUARTER
print "You inserted a quarter"
elif self.state == GumballMachine.SOLD_OUT:
print "You can't insert a quarter, the machine is sold out"
elif self.state == GumballMachine.SOLD:
print "Please wait, we are already giving you a gumball"
26. Il metodo tradizionale
def eject_quarter(self):
if self.state == GumballMachine.HAS_QUARTER:
print "Quarter returned"
self.state = GumballMachine.NO_QUARTER
elif self.state == GumballMachine.NO_QUARTER:
print "You haven't inserted a quarter"
elif self.state == GumballMachine.SOLD_OUT:
print "You can't eject, you haven't inserted a quarter yet"
elif self.state == GumballMachine.SOLD:
print "Sorry, you already turned the crank"
def turn_crank(self):
if self.state == GumballMachine.SOLD:
print "Turning twice doesn't get you another gumball"
elif self.state == GumballMachine.NO_QUARTER:
print "You turned, but there is no quarter"
elif self.state == GumballMachine.SOLD_OUT:
print "You turned, but there is no quarter"
elif self.state == GumballMachine.HAS_QUARTER:
print "You turned..."
self.state = GumballMachine.SOLD
self.dispense()
27. Il metodo tradizionale
def dispense(self):
if self.state == GumballMachine.SOLD:
print "A gumball comes rolling out of the slot"
self.count -= 1
if self.count == 0:
print "Oops, out of gumballs"
self.state = GumballMachine.SOLD_OUT
else:
self.state = GumballMachine.NO_QUARTER
elif self.state == GumballMachine.NO_QUARTER:
print "You need to pay first"
elif self.state == GumballMachine.SOLD_OUT:
print "No gumball dispensed"
elif self.state == GumballMachine.HAS_QUARTER:
print "No gumball dispensed"
28. Il metodo tradizionale
def dispense(self):
if self.state == GumballMachine.SOLD:
print "A gumball comes rolling out of the slot"
self.count -= 1
if self.count == 0:
print "Oops, out of gumballs"
self.state = GumballMachine.SOLD_OUT
else:
self.state = GumballMachine.NO_QUARTER
elif self.state == GumballMachine.NO_QUARTER:
print "You need to pay first"
elif self.state == GumballMachine.SOLD_OUT:
print "No gumball dispensed"
elif self.state == GumballMachine.HAS_QUARTER:
print "No gumball dispensed"
Ora supponiamo di volere aggiungere uno stato...
31. State pattern e protocolli
Design pattern nei linguaggi dinamici
State pattern
Vediamo il punto di vista per implementare
un protocollo?
32. Pattern state
Gli stati sono rappresentati da oggetti
L’oggetto principale “delega” le azioni ai
metodi degli stati
Eventualmente questi cambiano lo stato
Aggiungere stati diventa facile
33. class GumballMachine(object):
'The true and only gumball machine'
actions = set(['insert_quarter', 'eject_quarter',
'turn_crank', 'dispense'])
def __init__(self, count):
self.no_quarter = NoQuarterState(self)
self.has_quarter = HasQuarterState(self)
self.sold = SoldState(self)
self.sold_out = SoldOutState(self)
self.count = count
self.state = self.sold_out
if self.count > 0:
self.state = self.no_quarter
def __getattr__(self, action):
if action in GumballMachine.actions:
return getattr(self.state, action)
def __str__(self):
return ("nMighty Gumball, Incn"
"Python-enabled Standing Gumball Model #2009n"
"Built from orginal specifications by Freeman&Freeman,"
" Head First Design Patternsn"
"Inventory: %d gumballsn") % (self.count)
34. class HasQuarterState(object):
'Represent a state where the machine has a quarter inside.'
def __init__(self, gumball):
self.gumball = gumball
def insert_quarter(self):
'Perform quarter insertion'
print "You can't insert another quarter"
def eject_quarter(self):
'Ask the quarter back'
print "Quarter returned"
self.gumball.state = self.gumball.no_quarter
def turn_crank(self):
'Turn the crank'
print "You turned..."
self.gumball.state = self.gumball.sold
self.gumball.dispense()
def dispense(self):
'Actually gives the gumball'
print "No gumball dispensed"
35. class NoQuarterState(object):
'Represent a state where the machine has no quarter inside.'
def __init__(self, gumball):
self.gumball = gumball
def insert_quarter(self):
'Perform quarter insertion'
self.gumball.state = self.gumball.has_quarter
print "You inserted a quarter"
def eject_quarter(self):
'Ask the quarter back'
print "You haven't inserted a quarter"
def turn_crank(self):
'Turn the crank'
print "You turned, but there is no quarter"
def dispense(self):
'Actually gives the gumball'
print "You need to pay first"
36. class SoldState(object):
'The machine is to dispense the ball'
def __init__(self, gumball):
self.gumball = gumball
def insert_quarter(self):
'Perform quarter insertion'
print "Please wait, we are already giving you a gumball"
def eject_quarter(self):
'Ask the quarter back'
print "Sorry, you already turned the crank"
def turn_crank(self):
'Turn the crank'
print "Turning twice doesn't get you another gumball"
def dispense(self):
'Actually gives the gumball'
print "A gumball comes rolling out of the slot"
self.gumball.count -= 1
if self.gumball.count == 0:
print "Oops, out of gumballs"
self.gumball.state = self.gumball.sold_out
else:
self.gumball.state = self.gumball.no_quarter
37. class SoldOutState(object):
'No more balls. Sorry guys.'
def __init__(self, gumball):
self.gumball = gumball
def insert_quarter(self):
'Perform quarter insertion'
print "You can't insert a quarter, the machine is sold out"
def eject_quarter(self):
'Ask the quarter back'
print "You can't eject, you haven't inserted a quarter yet"
def turn_crank(self):
'Turn the crank'
print "You turned, but there is no quarter"
def dispense(self):
'Actually gives the gumball'
print "No gumball dispensed"
38. Setattr
def __setattr__(self, name, value): ...
__getattr__viene chiamato solo quando non
si trova l’attributo, __setattr__ è chiamato
sempre
Attenzione ai loop se lo ridefinite!
def __setattr__(self, name, value):
if name in self.names:
self.k = value * self.conversion[name]
elif name == ‘k’:
object.__setattr__(self, name, value)
else:
raise AttributeError, name
41. Web
cgi (sconsigliato)/fastcgi
WSGI: modulo di medio livello per interfacciarsi con i
server
Django: framework completo, molto usato
Nevow: framework completo basato su Twisted
Zope2/3: “mega framework”
Google App Engine (+web.py ev. Django)
web.py: modulo minimalista, facile comprensione
42. Web.py
import web
urls = (
'/(.*)', 'hello'
)
app = web.application(urls, globals())
class hello:
def GET(self, name):
if not name:
name = 'world'
return 'Hello, ' + name + '!'
if __name__ == "__main__":
app.run()
43. XML-RPC Server
from SimpleXMLRPCServer import SimpleXMLRPCServer
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler
class RequestHandler(SimpleXMLRPCRequestHandler):
rpc_paths = ('/RPC2',)
server = SimpleXMLRPCServer(("localhost", 8000),
requestHandler=RequestHandler)
server.register_introspection_functions()
server.register_function(pow)
def adder_function(x,y):
return x + y
server.register_function(adder_function, 'add')
class MyFuncs:
def div(self, x, y):
return x // y
server.register_instance(MyFuncs())
server.serve_forever()
44. XML-RPC Client
import xmlrpclib
s = xmlrpclib.ServerProxy('http://localhost:8000')
print s.pow(2,3) # Returns 2**3 = 8
print s.add(2,3) # Returns 5
print s.div(5,2) # Returns 5//2 = 2
# Print list of available methods
print s.system.listMethods()