Handy links:
- special class methods like _ getattr _ and _ new _
- dive into python - native datatypes
- Inside story on new style classes - ter info: Python3 heeft enkel “newstyle” classes!
Method overriding
Is niet mogelijk. Gebruik default values!
def func(i, j = 2, k 3):
return i + j + k
func(1) # 6
func(1, 1) # 5
func(1, 1, 1) # 3
Wat wel gaat, evt met decorators, zie Five-minute multimethods in Python - is __call__
implementeren en dan met metaprogrammeren te loopen over alle methods en te kijken of de argumenten overeen komen met het type dat required is. Fancypancy!
Opgelet met pitfalls
Nummer 1: default variables worden herbruikt:
When Python executes a “def” statement, it takes some ready-made pieces (including the compiled code for the function body and the current namespace), and creates a new function object. When it does this, it also evaluates the default values. […] Another way to reset the defaults is to simply re-execute the same “def” statement. Python will then create a new binding to the code object, evaluate the defaults, and assign the function object to the same variable as before. But again, only do that if you know exactly what you’re doing.
Default als arr = []
Gaat de array muteren. Heel handig voor memoization, heel verwarrend anders. Oplossing? arr None
en dan arr = [] if arr is None
.
Zie ook Default parameter values in Python voor in-depth uitleg.
Nummer 2: Python’s nested scopes bind to variables, not object values.
for i in range(10):
def callback():
print "clicked button", i
UI.Button("button %s" % i, callback)
variabele i
gaat altijd 9 zijn - wordt niet op value gebind. Oplossing is explicit binding door de functie definitie van callback():
te veranderen naar callback(i=i):
.
Fields dynamisch definiëren
Thing(a=1, b2)
kan op een paar manieren gedefiniëerd worden.
fields expliciet declareren
class Thing:
def __init__(self, a, b):
self.a, self.b = a, b
dynamisch uw eigen dictionary updaten
class Thing:
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
❗ Dit is uiteraard heel gevaarlijk aangezien het al uw method bodies kan vervangen door een param value. BOEM.
* *name
(zonder spatie, wiki markup, nvdr) geeft alle argumenten in een dict terug. *name
gaat ook, geeft u een lijst van argumenten terug. Combinatie gaat ook, één ster moet voor de tweede komen. Zoiets is dus mogelijk: def _ _init_ _(self, arg1, arg2, *allargs, * *allargsasdict)
.
Alles als een message passing systeem zien
In Ruby is er een andere manier om def name block end
te schrijven, hoe het geïnterpreteerd wordt: self.class.send(:name, args) { block }
def opt_to_s opt={}
opt.empty? ? '' : ' ' + opt.map {|e,v| "#{e}=<br/>"#{v}<br/>""}.join(', ')
end
[:html, :body, :h1].each do |el|
start="<#{el}"
fin="</#{el}>"
self.class.send(:define_method, el) {|options={}, &blk| start + opt_to_s(options) + '>' + blk.call + fin}
end
# Now, we can neatly nest tags and content
html do
body do
h1 :class=>"bold-h1", :id>"h1_99" do
"header"
end
end
end
=> "<body><h1 class<br/>"bold-h1<br/>", id=<br/>"h1_99<br/>">header</h1></body>"
Voilà, een DSL in no-time. Holy crap. Bron: do you understand ruby’s objects messages and blocks?
Superclassing
Klassen aanmaken is niet al te moeilijk, maar een call uitvoeren naar de overridden method is iets minder evident: zie super() in python docs
Een voorbeeld van een custom http handler:
class HttpHandler(SimpleHTTPRequestHandler):
def readStatus(self):
return {
"Status": "blabla",
"StartTime": ""
}
def do_GET(self):
try:
print('serving %s now' % self.path)
if "status.json" in self.path:
self.send_response(200)
self.send_header('Content-type', 'text/json')
self.end_headers()
self.wfile.write(json.dumps(self.readStatus()).encode())
else:
SimpleHTTPRequestHandler.do_GET(self)
except IOError:
self.send_error(500, 'internal server error in server python source: %s' % self.path)
Wat is hier speciaal aan:
super.do_GET(self)
=>SimpleHTTPRequestHandler.do_GET(self)
- eigen method aanroepen:
self.readStatus()
met deself
prefix
Diamond inheritance
BaseClass.method()
is impliciet hetzelfde als super().method()
, behalve dat je met super
een argument kan meegeven, over welke superklasse het gaat.
Zie ook Things to know about Python’s super()
Closures en lambda’s
functies in functies aanmaken werkt perfect, “closed over” die lexicale scope, net zoals je zou verwachten zoals bijvoorbeeld in javascript:
def readBuildStatus(self):
html = urllib.request.urlopen("http://bla/lastbuildstatus.htm").read().decode()
def extractVersion():
versionString = "Version: "
versionIndex = html.find("Version: ")
return html[versionIndex + len(versionString) : versionIndex + len(versionString) + len("YYYY.MM")]
def extractStatus():
return "Succeeded" if html.find("SUCCESS") != -1 else "Failed"
de twee andere methods lezen de html
variabele uit. Hier hoef je geen self.
prefix te gebruiken, binnen de readBuildStatus()
functie zelf - hierbuiten zijn de closures verdwenen natuurlijk (out of scope).
unittest module
Spreekt voor zich:
import unittest
from calculator import Calculator
class TestCalculator(unittest.TestCase):
def setUp(self):
self.calc = Calculator().calculate;
def test_calculateBasicNumberReturnsNumber(self):
self.assertEqual(3, self.calc('3'))
def test_calculateSimpleMultiplicationReturnsResult(self):
self.assertEqual(10, self.calc('5*2'))
def test_calculateInvalidStringShouldThrowException(self):
self.assertRaises(ValueError, self.calc, ('blabl'))
Zie http://docs.python.org/3/library/unittest.html
setUp
wordt automatisch aangeroepen. Beforeclass, aftereach etc etc bestaat ook.- alle methods met
test_
worden automatisch herkend.
Hoe voer ik dit nu uit?
Dit stuk onder uw py file plakken:
if __name__ == '__main__':
unittest.main()
En dan python -m unittest -v calculatorTest
. de v flag geeft wat extra output, anders staat er gewoon OK. De test op zich builden in bijvoorbeeld sublime met de main method erin zorgt er ook voor dat deze automatisch uitgevoerd wordt.
automatic test case discovery
python -m unittest discover
gaat alle unit testen vanaf huidig dir scannen en uitvoeren (instelbaar met params). Moet voldoen aan:
- extenden van
unittest.TestCase
- voldoen aan python module structuur. Testen in files met prefix “test_x.py”.
- Indien in subfolder “test”: vergeet geen “init.py” file.
autotest
Mogelijk met onder andere autonose
(nose is een alternatief voor unittest) en sniffer
. Om die te installeren moet je via de pip package manager gaan, en dan gewoon sniffer uitvoeren in uw base directory.