diff options
Diffstat (limited to 'pypers/oxford')
153 files changed, 20305 insertions, 0 deletions
diff --git a/pypers/oxford/BaseClass.py b/pypers/oxford/BaseClass.py new file mode 100755 index 0000000..8dc01c2 --- /dev/null +++ b/pypers/oxford/BaseClass.py @@ -0,0 +1,6 @@ +# BaseClass.py
+
+class BaseClass(object):
+ "Do something"
+
+
diff --git a/pypers/oxford/Makefile b/pypers/oxford/Makefile new file mode 100755 index 0000000..ea6efa7 --- /dev/null +++ b/pypers/oxford/Makefile @@ -0,0 +1,14 @@ +rst = "/home/micheles/mp/ms/tools/rst.py" +all: + make all.rst +all.rst: frontpage.txt loops.txt objects.txt magic.txt + $(rst) frontpage.txt loops.txt objects.txt magic.txt +pdf: all.rst + $(rst) -Ptd all; xpdf all.pdf +test: all.rst + python2.4 -mdoctester < all.rst +zip: all.rst + zip oxford-lectures README.txt all.rst all.html all.pdf accu2005.png \ + doctester.py +upload: + scp oxford-lectures.zip alpha.phyast.pitt.edu:public_html diff --git a/pypers/oxford/README.txt b/pypers/oxford/README.txt new file mode 100755 index 0000000..9bf0ebc --- /dev/null +++ b/pypers/oxford/README.txt @@ -0,0 +1,38 @@ +** PYTHON 2.4 IS REQUIRED TO RUN THE EXAMPLES IN THE LECTURE NOTES ** + +Instructions: + +$ unzip oxford-lectures.zip +$ cd oxford-lectures +$ python -m doctester < all.rst + +This will extracts all code snippets from the source file and run all tests. +You should get the message + +doctest: run 209 tests, failed 0 + +If you are on Windows, you will likely miss the "crypt" module, so a few +examples may not run. Don't worry about them. I have not tried +to make the scripts portable (for instance I have hard coded the +location of optparse in the import_with_metaclass example; if the +pathname differs in your system you will have to change it). + +Generally speaking these lectures are unpolished, too concise, with +more code than words, and with cutting edge/very experimental code. +Read them at your risk and peril. Take in account the fact that they +were prepared as a last minute replacement for Alex Martelli's tutorial, +with a limited amount of time and a very advanced program to follow. + +My main concern in preparing these notes was to give the readers a few +*ideas*, not polished solutions. If you are reading these notes, you +will be more than capable to customize these ideas to your own situation +and to fix the unavoidable little bugs, imperfections, annoyances. + +Whereas I recommend the first lecture about iterators and generators +to everybody, take in account than the second and especially the +third lecture may cause your head to explode. I do not take any +responsability in that case. + +Enjoy! + + Michele Simionato, April 2005 diff --git a/pypers/oxford/XMLtag.py b/pypers/oxford/XMLtag.py new file mode 100755 index 0000000..aaeb1b3 --- /dev/null +++ b/pypers/oxford/XMLtag.py @@ -0,0 +1,25 @@ +# XMLtag.py
+
+def makeattr(dict_or_list_of_pairs):
+ dic = dict(dict_or_list_of_pairs)
+ return " ".join('%s="%s"' % (k, dic[k]) for k in dic) # simplistic
+
+class XMLTag(object):
+ def __getattr__(self, name):
+ def tag(value, **attr):
+ """value can be a string or a sequence of strings."""
+ if hasattr(value, "__iter__"): # is iterable
+ value = " ".join(value)
+ return "<%s %s>%s</%s>" % (name, makeattr(attr), value, name)
+ return tag
+
+class XMLShortTag(object):
+ def __getattr__(self, name):
+ def tag(**attr):
+ return "<%s %s />" % (name, makeattr(attr))
+ return tag
+
+tag = XMLTag()
+tg = XMLShortTag()
+
+
diff --git a/pypers/oxford/after-the-event-1.txt b/pypers/oxford/after-the-event-1.txt new file mode 100755 index 0000000..dc1d52a --- /dev/null +++ b/pypers/oxford/after-the-event-1.txt @@ -0,0 +1,104 @@ +This should go in a blog, but I do not have one, nor any intention to +start one, so I thought I will post here instead. + +Warning: this is a long post! + +ACCU Conference (PyUK) 2005: a personal view +======================================================= + +Maybe not everybody knows that last week (19-23 of April) we had a +pretty important event in Oxford: the fifth PyUK conference - hosted +by the ACCU association - which is probably the second most important +Python-related event in Europe after EuroPython. + +ACCU means Association of C and C++ Users, so most of the people there +were not Python programmers; still it amazed me how much steam Python +has gathered in the last years between C++ programmers. + +Ideally, I was there to give just a short presentation on doctest, +but since Alex Martelli got hired from Google, I had to act as +replacement of the Martelli & Ravenscroft couple, since it not +that easy to find somebody crazy enough to take over or a 6-hours +guru-level Python course. And, of course, there was some Italian +mafia involved ;) + +It is not easy to act as a replacement for the martellibot, +especially on short notice, but I tried to do my best. BTW, +interested people can find my slides somehere on the ACCU website +https://www.accu.org/conference/ or on my site +http://www.phyast.pitt.edu/~micheles/python/oxford-lectures.zip + +I was actually worried a bit about people deserting the lectures once +they discovered that Alex & Anna where not coming; it turns out my +worries were injustified. We actually had something like 20 persons +there, so we filled the room pretty well. + +The interesting thing was that at least half the people were +experienced C++ programmers willing to learn Python, and not actual +Python programmers. So, I had to correct the scope of the lectures +in real time and I could not cover metaclasses, whereas I covered +decorators but not as well as they deserve. Next year the tutorial will +probably have a title such as "Python for C++ programmers" and the program +will be changed accordingly. Anyway, people were extremely interested +and the session (originally scheduled to end at 4 PM) actually +went go until after 6 PM! + +It turned out that one of my "students" was Stephen Turner from Microsoft: +Steve's title is "Developer evangelist" and it is part of his job to +present to the developers the new cool projects Microsoft is working on: +in this particular case, he went to the course since he in charge +of evangelizing Jim Hugunin's brainchild, IronPython, i.e. Python +running (fast!) on Dot Net, and he wanted to have a good picture +of CPython capabilities. + +Obviously when I discovered that, I immediately asked him if he was +willing to give a presentation on IronPython. We were lucky, since he +accepted, he got some slides from Jim Hugunin's PyCON presentation +and he gaves us a truly wonderful demonstration of IronPython +capabilities. *Really* impressive. + +One cannot overrate the importance of this development for the future +of Python. I asked Steve if Microsoft plans to support IronPython as +part of the DotNet choice of languages: the answer was that there is no +intention to sell IronPython. IronPython is an OpenSource project based +on DotNet but it is not part of the DotNet offer and there are no +plans in this sense. + +Some of you may be surprised (I certainly was) but Microsoft has been +financing various Open Source projects in the last few years, released +under BSD-style licences. IronPython is just one of these projects. +There will probably be more. So stay tuned and keep an eye on what Redmont +is doing. What it clear is that now Microsoft knows about the existence of +Python and it is actually investing money on it. + +This is quite a change, especially with respect to what our keynote +speaker, Greg Stein, told us about his experience with Microsoft +7-8 years ago, when he was employed by them: at that time Microsoft's +reaction to Python was something along the line of "Python what? is +that a programming language?". + +Greg also told has about the programming language policy at Google +(his current) employer: Googles uses and acknowledges officially only +three mainstream languages: C++, Java, and Python. Python *is* mainstream +for them. And judging from the space accorded to Python at the ACCU conference, +Python is mainstream for the ACCU members too. + +An extremely impressive accomplishement for Python, if you think about +it. And if Microsoft and Google are not enough, know that Nokia +is offering Python on their mobile phones. Tapio Tallgren gaves us an +extremely interesting technical talk on how you can program the +Series 90 mobile using Python. They are targetting Python 2.2 +and most of the standard library just works, the speed is pretty +good and actually they were surprised of how easy was to make the +port. + +All in all, pretty good news, people! It seems a pretty good moment +to be a Python programmer! + +I have something else to say, but I will make another post for that. + + Michele Simionato + + +#IronPython +http://www.gotdotnet.com/workspaces/workspace.aspx?id=ad7acff7-ab1e-4bcb-99c0-57ac5a3a9742 diff --git a/pypers/oxford/after-the-event-2.txt b/pypers/oxford/after-the-event-2.txt new file mode 100755 index 0000000..b68139b --- /dev/null +++ b/pypers/oxford/after-the-event-2.txt @@ -0,0 +1,92 @@ +The previous post was getting too long, so I decided to put my +impressions on the technical talks here. You may also want to +read what our chairman Andy Robinson has to say: +http://www.reportlab.org/~andy/accu2005/accu2005.html +There you can find the materials of the conference (slides etc.) + +ACCU Conference (PyUK) 2005: a personal view (part 2) +======================================================= + +I am definitely a technical person (as opposed to a marketing person) +so what impressed me the most were the presentations by Armin Rigo. + +He gave two presentations: the official one, about the PyPy project, and +a lightening talk about greenlets. Of course, I had heard something +about these topics previously, but it is pretty different to see a live +demonstration. Armin shown us really *impressive* stuff. + +Basically, PyPy is already there and it is working! He explained us +the idea behind just-in-time compilation (which is actually quite simple), +type inference at work, as well as other really impressive stuff, such +as sharing an object through an Internet connection (!) + +The only problem with PyPy as it is now, is that it runs on top of +traditional Python and it is terribly slow. Nevertheless, Armin says +that it can be made fast and actually faster than current Python. +If any other guy would have made the same claim I would have been +skeptical, but Armin is the person who gave us Psyco, so I am forced to +take him seriously. And the other persons working full time on the +project have names such as Christian Tismer, Samuele Pedroni, +Holger Krekel, etc. As reported by Jacob Hallen, Guido himself has +great hopes for the project and Tim Peters would have liked to work +full time on PyPy. + +The European Community founded the project for 1,300,000+ Euros in two +years. So there is money to join the project and to cover travel expenses, +if you are an Europen citizen. If you are no EU member, +things are more complicated from the burocratical point +of view. If somebody knows how to turn around the +restrictions and to get some money for non-EU nationalities, +I am sure Jacob Hallen would be happy to know. + +Jacob Hallen is in charge of the organization of the +sprints and of some of the burocratical part. He looks +like he is doing a pretty good job. Of course his firm, +the Strakt, has a very strong and direct interest in the +project. It is worth repeating that Alex Martelli worked for Strakt, +that Laura Chreighton is there as well, and that recently +they hired Samuele Pedroni of Jython fame. So, it looks +like they are really doing a LOT for Python in Europe. + +On my part, I have resolved to keep a close eye on the +development in http://codespeak.net, since there is really +cool stuff in there, and really innovative ideas. It is +unfortunate that most of the stuff is still under SVN +and not easy available to the average Joe User, even if +there are plenty of things that would be useful to the average +Joe User, such as py.test (I would not talk about py.test since +it has been discussed elsewhere pretty well, but I definitely +like it a lot). + +I certainly hope that part of this very substantial effort will go back +into the standard library someday. + +It is interesting to notice that Armin and I were the two speakers to +use non-traditional presentation materials. + +Whereas I have just shown simple HTML slides generated by a hand-cooked +Python script, he made a very effective usage of Pygame, so you +got the impression type inference was as easy as playing PacMan. +His presentation is available for download, at the reportlab link +I mentioned before, so everybody can see it. Armin also used GraphViz to +plot the output of PyPy type-inferencer (I am not sure of the right term +to use here, just as I am not sure of what "interpreter" and "compiler" +means anymore) and he got really nice-looking results. I am a +known evangelist of the "dot" language, +http://www.linuxdevcenter.com/pub/a/linux/2004/05/06/graphviz_dot.html, +so I very much liked the approach. + +It is also fun to notice that both Armin's and my talk were +the two metacircular talks of the conference: Armin was talking about +implementing a programming language using itself as implementation +language (let me remind that PyPy stands for Python in Python) whereas +I presented a web application to demonstrate doctest which was used to +doctest itself. + +Well, that's all for the moment. I have written enough for today, now +it is time to start playing with greenlets. They are extremely cool, +I don't know where I want to use them yet, but I am pretty sure I'll +figure out something eventually ;) + + + Michele Simionato diff --git a/pypers/oxford/after-the-event-3.txt b/pypers/oxford/after-the-event-3.txt new file mode 100755 index 0000000..b727bd5 --- /dev/null +++ b/pypers/oxford/after-the-event-3.txt @@ -0,0 +1,77 @@ +In the previous post I have talked only about the PyPy project. +But there was very interesting things even for people living in +the real world. So, let's talk about them. + +ACCU Conference (PyUK) 2005: a personal view (part 3) +======================================================= + +Installation & Deployment of Python applications +------------------------------------------------------ + +I should really thank Andy Robinson for the great job he +did as chairman and organizer of the Python track, as well +for the choice of the talks. He really covered everything +from the most abstract and suble matters to the most concrete +and real life practical things. So we had a couple of talks +on installing and deploying Python applications. + +I really liked the one by Nathan Yergler, presenting his work +on Creatives Common applications. His talk was 0% marketing and +100% sound advice and very concrete help on what works and what +does not work if you are trying to distribute a multiplatform +application based on wxPython. + +In the same vein, Andy Robinson gave a talk on the challenges +Reportlab faced with the installation and maintenance of their +toolkit on many platforms. The message I got is that distutils is +not enough, and that at the end they had to develop your own +installation software. + +This is sad, but I recognized the truth in what Nathan and +Yergler were saying: installation and deployement are +higly non-trivial and time-consuming activities which +are now more complex than needed. Andy said he took +inspiration from what Ant did in the Java world, when +writing his own installation tools and he made a made +strong advocacy for Ant, saying that we should have something like that +in standard library. I do not know Ant personally, but I have +use distutils recently and I must say that whereas is quite +at some things, it is not that easy to build on top of it. +In practice, it is just faster to write a custom tool than +to use distutils. + + +Comparison of Graphics Toolkits +------------------------------------------------- + +We got a really nice demonstration about Qt Designer by +John Pinner, supplemented by a lightening talk by Trolltech +(which must have the youngest and prettiest girl-engineers +out there). I am not really capable of using such tools +(I would probably be more at home with an s-expression +representation of the widgets) but still I was pretty +impressed by the easy of use. Plus, after a few click, +you get an impressively clean piece of Python code +with all the classes and the objects corresponding +to the pictures you just drawn. + +There was also a comparable presentation by Alex Tweedly about +PythonCard, but I missed it since I attended a Agile talk by +an Italian friend I met there. + +Web Frameworks +------------------------------------------------- + +There was a talk about Zope3 which I was very interested in, +and a lightening talk about CherryPy by Ron Collins. +Plus Andy Robinsons discussing the Reportlab hand-made +Web framework, and the problems they got testing it. + +This is a subject I was very interested to hear about, +since I have been experimenting with the subject. Unfortunately, +it looks like everybody is trying to solve the issue in +similar ways (essentially building custom tools on top of urllib). +I discovered various simple applications to do Web scripting +but nothing standard or widely used. This is certainly an area +where the standard library could be extended. + diff --git a/pypers/oxford/all.html b/pypers/oxford/all.html new file mode 100755 index 0000000..56f2314 --- /dev/null +++ b/pypers/oxford/all.html @@ -0,0 +1,2366 @@ +<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<meta name="generator" content="Docutils 0.3.10: http://docutils.sourceforge.net/" />
+<title>Lectures on Advanced Python Programming</title>
+<style type="text/css">
+
+/*
+:Author: David Goodger
+:Contact: goodger@users.sourceforge.net
+:Date: $Date: 2005-06-06 15:09:07 +0200 (Mon, 06 Jun 2005) $
+:Version: $Revision: 3442 $
+:Copyright: This stylesheet has been placed in the public domain.
+
+Default cascading style sheet for the HTML output of Docutils.
+*/
+
+/* "! important" is used here to override other ``margin-top`` and
+ ``margin-bottom`` styles that are later in the stylesheet or
+ more specific. See http://www.w3.org/TR/CSS1#the-cascade */
+.first {
+ margin-top: 0 ! important }
+
+.last, .with-subtitle {
+ margin-bottom: 0 ! important }
+
+.hidden {
+ display: none }
+
+a.toc-backref {
+ text-decoration: none ;
+ color: black }
+
+blockquote.epigraph {
+ margin: 2em 5em ; }
+
+dl.docutils dd {
+ margin-bottom: 0.5em }
+
+/* Uncomment (and remove this text!) to get bold-faced definition list terms
+dl.docutils dt {
+ font-weight: bold }
+*/
+
+div.abstract {
+ margin: 2em 5em }
+
+div.abstract p.topic-title {
+ font-weight: bold ;
+ text-align: center }
+
+div.admonition, div.attention, div.caution, div.danger, div.error,
+div.hint, div.important, div.note, div.tip, div.warning {
+ margin: 2em ;
+ border: medium outset ;
+ padding: 1em }
+
+div.admonition p.admonition-title, div.hint p.admonition-title,
+div.important p.admonition-title, div.note p.admonition-title,
+div.tip p.admonition-title {
+ font-weight: bold ;
+ font-family: sans-serif }
+
+div.attention p.admonition-title, div.caution p.admonition-title,
+div.danger p.admonition-title, div.error p.admonition-title,
+div.warning p.admonition-title {
+ color: red ;
+ font-weight: bold ;
+ font-family: sans-serif }
+
+/* Uncomment (and remove this text!) to get reduced vertical space in
+ compound paragraphs.
+div.compound .compound-first, div.compound .compound-middle {
+ margin-bottom: 0.5em }
+
+div.compound .compound-last, div.compound .compound-middle {
+ margin-top: 0.5em }
+*/
+
+div.dedication {
+ margin: 2em 5em ;
+ text-align: center ;
+ font-style: italic }
+
+div.dedication p.topic-title {
+ font-weight: bold ;
+ font-style: normal }
+
+div.figure {
+ margin-left: 2em }
+
+div.footer, div.header {
+ clear: both;
+ font-size: smaller }
+
+div.line-block {
+ display: block ;
+ margin-top: 1em ;
+ margin-bottom: 1em }
+
+div.line-block div.line-block {
+ margin-top: 0 ;
+ margin-bottom: 0 ;
+ margin-left: 1.5em }
+
+div.sidebar {
+ margin-left: 1em ;
+ border: medium outset ;
+ padding: 1em ;
+ background-color: #ffffee ;
+ width: 40% ;
+ float: right ;
+ clear: right }
+
+div.sidebar p.rubric {
+ font-family: sans-serif ;
+ font-size: medium }
+
+div.system-messages {
+ margin: 5em }
+
+div.system-messages h1 {
+ color: red }
+
+div.system-message {
+ border: medium outset ;
+ padding: 1em }
+
+div.system-message p.system-message-title {
+ color: red ;
+ font-weight: bold }
+
+div.topic {
+ margin: 2em }
+
+h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
+h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
+ margin-top: 0.4em }
+
+h1.title {
+ text-align: center }
+
+h2.subtitle {
+ text-align: center }
+
+hr.docutils {
+ width: 75% }
+
+img.align-left {
+ clear: left }
+
+img.align-right {
+ clear: right }
+
+img.borderless {
+ border: 0 }
+
+ol.simple, ul.simple {
+ margin-bottom: 1em }
+
+ol.arabic {
+ list-style: decimal }
+
+ol.loweralpha {
+ list-style: lower-alpha }
+
+ol.upperalpha {
+ list-style: upper-alpha }
+
+ol.lowerroman {
+ list-style: lower-roman }
+
+ol.upperroman {
+ list-style: upper-roman }
+
+p.attribution {
+ text-align: right ;
+ margin-left: 50% }
+
+p.caption {
+ font-style: italic }
+
+p.credits {
+ font-style: italic ;
+ font-size: smaller }
+
+p.label {
+ white-space: nowrap }
+
+p.rubric {
+ font-weight: bold ;
+ font-size: larger ;
+ color: maroon ;
+ text-align: center }
+
+p.sidebar-title {
+ font-family: sans-serif ;
+ font-weight: bold ;
+ font-size: larger }
+
+p.sidebar-subtitle {
+ font-family: sans-serif ;
+ font-weight: bold }
+
+p.topic-title {
+ font-weight: bold }
+
+pre.address {
+ margin-bottom: 0 ;
+ margin-top: 0 ;
+ font-family: serif ;
+ font-size: 100% }
+
+pre.line-block {
+ font-family: serif ;
+ font-size: 100% }
+
+pre.literal-block, pre.doctest-block {
+ margin-left: 2em ;
+ margin-right: 2em ;
+ background-color: #eeeeee }
+
+span.classifier {
+ font-family: sans-serif ;
+ font-style: oblique }
+
+span.classifier-delimiter {
+ font-family: sans-serif ;
+ font-weight: bold }
+
+span.interpreted {
+ font-family: sans-serif }
+
+span.option {
+ white-space: nowrap }
+
+span.pre {
+ white-space: pre }
+
+span.problematic {
+ color: red }
+
+span.section-subtitle {
+ /* font-size relative to parent (h1..h6 element) */
+ font-size: 80% }
+
+table.citation {
+ border-left: solid thin gray }
+
+table.docinfo {
+ margin: 2em 4em }
+
+table.docutils {
+ margin-top: 0.5em ;
+ margin-bottom: 0.5em }
+
+table.footnote {
+ border-left: solid thin black }
+
+table.docutils td, table.docutils th,
+table.docinfo td, table.docinfo th {
+ padding-left: 0.5em ;
+ padding-right: 0.5em ;
+ vertical-align: top }
+
+table.docutils th.field-name, table.docinfo th.docinfo-name {
+ font-weight: bold ;
+ text-align: left ;
+ white-space: nowrap ;
+ padding-left: 0 }
+
+h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
+h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
+ font-size: 100% }
+
+tt.docutils {
+ background-color: #eeeeee }
+
+ul.auto-toc {
+ list-style-type: none }
+
+</style>
+</head>
+<body>
+<div class="document" id="lectures-on-advanced-python-programming">
+<h1 class="title">Lectures on Advanced Python Programming</h1>
+<img alt="accu2005.png" src="accu2005.png" />
+<table class="docutils field-list" frame="void" rules="none">
+<col class="field-name" />
+<col class="field-body" />
+<tbody valign="top">
+<tr class="field"><th class="field-name">Author:</th><td class="field-body">Michele Simionato</td>
+</tr>
+<tr class="field"><th class="field-name">Given:</th><td class="field-body">19 April 2005</td>
+</tr>
+<tr class="field"><th class="field-name">Revised:</th><td class="field-body">7 September 2005</td>
+</tr>
+</tbody>
+</table>
+<div class="contents topic" id="contents">
+<p class="topic-title first"><a name="contents">Contents</a></p>
+<ul class="simple">
+<li><a class="reference" href="#lecture-1-loops-i-e-iterators-generators" id="id1" name="id1">Lecture 1: Loops (i.e. iterators & generators)</a><ul>
+<li><a class="reference" href="#part-i-iterators" id="id2" name="id2">Part I: iterators</a><ul>
+<li><a class="reference" href="#iterators-are-everywhere" id="id3" name="id3">Iterators are everywhere</a></li>
+<li><a class="reference" href="#iterables-and-iterators" id="id4" name="id4">Iterables and iterators</a></li>
+<li><a class="reference" href="#simpler-way-to-get-an-iterator" id="id5" name="id5">Simpler way to get an iterator</a></li>
+<li><a class="reference" href="#sentinel-syntax-iter-callable-sentinel" id="id6" name="id6">Sentinel syntax iter(callable, sentinel)</a></li>
+<li><a class="reference" href="#second-simpler-way-to-get-an-iterator-generator-expressions" id="id7" name="id7">Second simpler way to get an iterator: generator-expressions</a></li>
+<li><a class="reference" href="#iteration-caveats" id="id8" name="id8">Iteration caveats</a></li>
+</ul>
+</li>
+<li><a class="reference" href="#part-ii-generators" id="id9" name="id9">Part II: generators</a><ul>
+<li><a class="reference" href="#a-simple-recipe-skip-redundant" id="id10" name="id10">A simple recipe: skip redundant</a></li>
+<li><a class="reference" href="#another-real-life-example-working-with-nested-structures" id="id11" name="id11">Another real life example: working with nested structures</a></li>
+<li><a class="reference" href="#another-typical-use-case-for-generators-parsers" id="id12" name="id12">Another typical use case for generators: parsers</a></li>
+<li><a class="reference" href="#other-kinds-of-iterables" id="id13" name="id13">Other kinds of iterables</a></li>
+<li><a class="reference" href="#the-itertools-module" id="id14" name="id14">The itertools module</a></li>
+<li><a class="reference" href="#anytrue" id="id15" name="id15">anyTrue</a></li>
+<li><a class="reference" href="#chop" id="id16" name="id16">chop</a></li>
+<li><a class="reference" href="#tee" id="id17" name="id17">tee</a></li>
+<li><a class="reference" href="#grouping-and-sorting" id="id18" name="id18">Grouping and sorting</a></li>
+</ul>
+</li>
+</ul>
+</li>
+<li><a class="reference" href="#lecture-2-objects-delegation-inheritance" id="id19" name="id19">Lecture 2: Objects (delegation & inheritance)</a><ul>
+<li><a class="reference" href="#part-i-delegation" id="id20" name="id20">Part I: delegation</a><ul>
+<li><a class="reference" href="#accessing-simple-attributes" id="id21" name="id21">Accessing simple attributes</a></li>
+<li><a class="reference" href="#accessing-methods" id="id22" name="id22">Accessing methods</a></li>
+<li><a class="reference" href="#converting-functions-into-methods" id="id23" name="id23">Converting functions into methods</a></li>
+<li><a class="reference" href="#hack-a-very-slick-adder" id="id24" name="id24">Hack: a very slick adder</a></li>
+<li><a class="reference" href="#descriptor-protocol" id="id25" name="id25">Descriptor Protocol</a></li>
+<li><a class="reference" href="#multilingual-attribute" id="id26" name="id26">Multilingual attribute</a></li>
+<li><a class="reference" href="#another-use-case-for-properties-storing-users" id="id27" name="id27">Another use case for properties: storing users</a></li>
+<li><a class="reference" href="#low-level-delegation-via-getattribute" id="id28" name="id28">Low-level delegation via __getattribute__</a></li>
+<li><a class="reference" href="#traditional-delegation-via-getattr" id="id29" name="id29">Traditional delegation via __getattr__</a></li>
+<li><a class="reference" href="#keyword-dictionaries-with-getattr-setattr" id="id30" name="id30">Keyword dictionaries with __getattr__/__setattr__</a></li>
+<li><a class="reference" href="#delegation-to-special-methods-caveat" id="id31" name="id31">Delegation to special methods caveat</a></li>
+</ul>
+</li>
+<li><a class="reference" href="#part-ii-inheritance" id="id32" name="id32">Part II: Inheritance</a><ul>
+<li><a class="reference" href="#why-you-need-to-know-about-mi-even-if-you-do-not-use-it" id="id33" name="id33">Why you need to know about MI even if you do not use it</a></li>
+<li><a class="reference" href="#a-few-details-about-super-not-the-whole-truth" id="id34" name="id34">A few details about <tt class="docutils literal"><span class="pre">super</span></tt> (not the whole truth)</a></li>
+<li><a class="reference" href="#subclassing-built-in-types-new-vs-init" id="id35" name="id35">Subclassing built-in types; __new__ vs. __init__</a></li>
+<li><a class="reference" href="#be-careful-when-using-new-with-mutable-types" id="id36" name="id36">Be careful when using __new__ with mutable types</a></li>
+</ul>
+</li>
+</ul>
+</li>
+<li><a class="reference" href="#lecture-3-magic-i-e-decorators-and-metaclasses" id="id37" name="id37">Lecture 3: Magic (i.e. decorators and metaclasses)</a><ul>
+<li><a class="reference" href="#part-i-decorators" id="id38" name="id38">Part I: decorators</a><ul>
+<li><a class="reference" href="#a-typical-decorator-traced" id="id39" name="id39">A typical decorator: traced</a></li>
+<li><a class="reference" href="#a-decorator-factory-timed" id="id40" name="id40">A decorator factory: Timed</a></li>
+<li><a class="reference" href="#a-powerful-decorator-pattern" id="id41" name="id41">A powerful decorator pattern</a></li>
+<li><a class="reference" href="#a-deferred-decorator" id="id42" name="id42">A deferred decorator</a></li>
+</ul>
+</li>
+<li><a class="reference" href="#part-ii-metaclasses" id="id43" name="id43">Part II: metaclasses</a><ul>
+<li><a class="reference" href="#rejuvenating-old-style-classes" id="id44" name="id44">Rejuvenating old-style classes</a></li>
+<li><a class="reference" href="#a-typical-metaclass-example-metatracer" id="id45" name="id45">A typical metaclass example: MetaTracer</a></li>
+<li><a class="reference" href="#real-life-example-check-overriding" id="id46" name="id46">Real life example: check overriding</a></li>
+<li><a class="reference" href="#logfile" id="id47" name="id47">LogFile</a></li>
+<li><a class="reference" href="#cooperative-hierarchies" id="id48" name="id48">Cooperative hierarchies</a></li>
+<li><a class="reference" href="#metaclass-enhanced-modules" id="id49" name="id49">Metaclass-enhanced modules</a></li>
+<li><a class="reference" href="#magic-properties" id="id50" name="id50">Magic properties</a></li>
+<li><a class="reference" href="#hack-evil-properties" id="id51" name="id51">Hack: evil properties</a></li>
+<li><a class="reference" href="#why-i-suggest-not-to-use-metaclasses-in-production-code" id="id52" name="id52">Why I suggest <em>not</em> to use metaclasses in production code</a></li>
+<li><a class="reference" href="#is-there-an-automatic-way-of-solving-the-conflict" id="id53" name="id53">Is there an automatic way of solving the conflict?</a></li>
+</ul>
+</li>
+</ul>
+</li>
+</ul>
+</div>
+<div class="section" id="lecture-1-loops-i-e-iterators-generators">
+<h1><a class="toc-backref" href="#id1" name="lecture-1-loops-i-e-iterators-generators">Lecture 1: Loops (i.e. iterators & generators)</a></h1>
+<div class="section" id="part-i-iterators">
+<h2><a class="toc-backref" href="#id2" name="part-i-iterators">Part I: iterators</a></h2>
+<div class="section" id="iterators-are-everywhere">
+<h3><a class="toc-backref" href="#id3" name="iterators-are-everywhere">Iterators are everywhere</a></h3>
+<pre class="doctest-block">
+>>> for i in 1, 2, 3:
+... print i
+1
+2
+3
+</pre>
+<p>The 'for' loop is using <em>iterators</em> internally:</p>
+<pre class="literal-block">
+it = iter((1,2,3))
+while True:
+ try:
+ print it.next()
+ except StopIteration:
+ break
+</pre>
+</div>
+<div class="section" id="iterables-and-iterators">
+<h3><a class="toc-backref" href="#id4" name="iterables-and-iterators">Iterables and iterators</a></h3>
+<p><em>Iterable</em> = anything you can loop over = any sequence + any object with an __iter__ method;</p>
+<p>Not every sequence has an __iter__ method:</p>
+<pre class="doctest-block">
+>>> "hello".__iter__()
+Traceback (most recent call last):
+ ...
+AttributeError: 'str' object has no attribute '__iter__'
+</pre>
+<p><em>Iterator</em> = any object with a .next method and an __iter__ method returning self</p>
+</div>
+<div class="section" id="simpler-way-to-get-an-iterator">
+<h3><a class="toc-backref" href="#id5" name="simpler-way-to-get-an-iterator">Simpler way to get an iterator</a></h3>
+<pre class="doctest-block">
+>>> it = iter("hello")
+>>> it.next()
+'h'
+>>> it.next()
+'e'
+>>> it.next()
+'l'
+>>> it.next()
+'l'
+>>> it.next()
+'o'
+>>> it.next()
+Traceback (most recent call last):
+ ...
+StopIteration
+</pre>
+</div>
+<div class="section" id="sentinel-syntax-iter-callable-sentinel">
+<h3><a class="toc-backref" href="#id6" name="sentinel-syntax-iter-callable-sentinel">Sentinel syntax iter(callable, sentinel)</a></h3>
+<p>Example:</p>
+<pre class="literal-block">
+$ echo -e "value1\nvalue2\nEND\n" > data.txt
+$ python -c "print list(iter(file('data.txt').readline, 'END\n'))"
+['value1\n', 'value2\n']
+</pre>
+<p>Beware of infinite iterators:</p>
+<pre class="doctest-block">
+>>> repeat = iter(lambda : "some value", "")
+>>> repeat.next()
+'some value'
+</pre>
+</div>
+<div class="section" id="second-simpler-way-to-get-an-iterator-generator-expressions">
+<h3><a class="toc-backref" href="#id7" name="second-simpler-way-to-get-an-iterator-generator-expressions">Second simpler way to get an iterator: generator-expressions</a></h3>
+<pre class="doctest-block">
+>>> squares = (i*i for i in range(1,11))
+>>> list(squares)
+[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
+</pre>
+<p>Excessive parenthesis can be skipped, so use</p>
+<pre class="doctest-block">
+>>> dict((i, i*i) for i in range(1,11))
+{1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81, 10: 100}
+</pre>
+<p>instead of</p>
+<pre class="doctest-block">
+>>> dict([(i, i*i) for i in range(1,11)])
+{1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81, 10: 100}
+</pre>
+<p>(as usual, the most elegant version is the most efficient).</p>
+</div>
+<div class="section" id="iteration-caveats">
+<h3><a class="toc-backref" href="#id8" name="iteration-caveats">Iteration caveats</a></h3>
+<pre class="doctest-block">
+>>> ls = [i for i in (1,2,3)]
+>>> i
+3
+</pre>
+<pre class="doctest-block">
+>>> it = (j for j in (1,2,3))
+>>> j
+Traceback (most recent call last):
+ ...
+NameError: name 'j' is not defined
+</pre>
+<p>A subtler example:</p>
+<pre class="doctest-block">
+>>> ls = [lambda :i for i in (1,2,3)]
+>>> ls[0]()
+3
+</pre>
+<p>instead</p>
+<pre class="doctest-block">
+>>> it = (lambda :i for i in (1,2,3))
+>>> it.next()()
+1
+>>> it.next()()
+2
+>>> it.next()()
+3
+</pre>
+<p><em>seems</em> to be working but it is not really the case:</p>
+<pre class="doctest-block">
+>>> it = (lambda :i for i in (1,2,3))
+>>> f1 = it.next()
+>>> f2 = it.next()
+>>> f3 = it.next()
+>>> f1()
+3
+</pre>
+<p>The reason is that Python does LATE binding <em>always</em>. The solution is ugly:</p>
+<pre class="doctest-block">
+>>> it = list(lambda i=i:i for i in (1,2,3))
+>>> it[0]()
+1
+>>> it[1]()
+2
+>>> it[2]()
+3
+</pre>
+</div>
+</div>
+<div class="section" id="part-ii-generators">
+<h2><a class="toc-backref" href="#id9" name="part-ii-generators">Part II: generators</a></h2>
+<p>Trivial example:</p>
+<pre class="doctest-block">
+>>> def gen123(): # "function" which returns an iterator over the values 1, 2, 3
+... yield 1
+... yield 2
+... yield 3
+...
+>>> it = gen123()
+>>> it.next()
+1
+>>> it.next()
+2
+>>> it.next()
+3
+>>> it.next()
+Traceback (most recent call last):
+ ...
+StopIteration
+</pre>
+<p>Real life example: using generators to generate HTML tables</p>
+<pre class="literal-block">
+#<htmltable.py>
+
+def HTMLTablegen(table):
+ yield "<table>"
+ for row in table:
+ yield "<tr>"
+ for col in row:
+ yield "<td>%s</td>" % col
+ yield "</tr>"
+ yield "</table>"
+
+def test():
+ return "\n".join(HTMLTablegen([["Row", "City"],
+ [1,'London'], [2, 'Oxford']]))
+
+if __name__ == "__main__": # example
+ print test()
+
+#</htmltable.py>
+</pre>
+<pre class="doctest-block">
+>>> from htmltable import test
+>>> print test()
+<table>
+<tr>
+<td>Row</td>
+<td>City</td>
+</tr>
+<tr>
+<td>1</td>
+<td>London</td>
+</tr>
+<tr>
+<td>2</td>
+<td>Oxford</td>
+</tr>
+</table>
+</pre>
+<div class="section" id="a-simple-recipe-skip-redundant">
+<h3><a class="toc-backref" href="#id10" name="a-simple-recipe-skip-redundant">A simple recipe: skip redundant</a></h3>
+<p>How to remove duplicates by keeping the order:</p>
+<pre class="literal-block">
+#<skip_redundant.py>
+
+def skip_redundant(iterable, skipset=None):
+ "Redundant items are repeated items or items in the original skipset."
+ if skipset is None: skipset = set()
+ for item in iterable:
+ if item not in skipset:
+ skipset.add(item)
+ yield item
+
+#</skip_redundant.py>
+</pre>
+<pre class="doctest-block">
+>>> from skip_redundant import skip_redundant
+>>> print list(skip_redundant("<hello, world>", skipset=set("<>")))
+['h', 'e', 'l', 'o', ',', ' ', 'w', 'r', 'd']
+</pre>
+</div>
+<div class="section" id="another-real-life-example-working-with-nested-structures">
+<h3><a class="toc-backref" href="#id11" name="another-real-life-example-working-with-nested-structures">Another real life example: working with nested structures</a></h3>
+<pre class="literal-block">
+#<walk.py>
+
+def walk(iterable, level=0):
+ for obj in iterable:
+ if not hasattr(obj, "__iter__"): # atomic object
+ yield obj, level
+ else: # composed object: iterate again
+ for subobj, lvl in walk(obj, level + 1):
+ yield subobj, lvl
+
+def flatten(iterable):
+ return (obj for obj, level in walk(iterable))
+
+def pprint(iterable):
+ for obj, level in walk(iterable):
+ print " "*level, obj
+
+#</walk.py>
+</pre>
+<pre class="doctest-block">
+>>> from walk import flatten, pprint
+>>> nested_ls = [1,[2,[3,[[[4,5],6]]]],7]
+>>> pprint(nested_ls)
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+>>> pprint(flatten(nested_ls))
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+</pre>
+</div>
+<div class="section" id="another-typical-use-case-for-generators-parsers">
+<h3><a class="toc-backref" href="#id12" name="another-typical-use-case-for-generators-parsers">Another typical use case for generators: parsers</a></h3>
+<p>A very stripped down parser for nested expressions</p>
+<pre class="literal-block">
+#<sexpr2indent.py>
+"""A simple s-expression formatter."""
+
+import re
+
+def parse(sexpr):
+ position = 0
+ nesting_level = 0
+ paren = re.compile(r"(?P<paren_beg>\()|(?P<paren_end>\))")
+ while True:
+ match = paren.search(sexpr, position)
+ if match:
+ yield nesting_level, sexpr[position: match.start()]
+ if match.lastgroup == "paren_beg":
+ nesting_level += 1
+ elif match.lastgroup == "paren_end":
+ nesting_level -= 1
+ position = match.end()
+ else:
+ break
+
+def sexpr_indent(sexpr):
+ for nesting, text in parse(sexpr.replace("\n", "")):
+ if text.strip(): print " "*nesting, text
+
+#</sexpr2indent.py>
+</pre>
+<pre class="doctest-block">
+>>> from sexpr2indent import sexpr_indent
+>>> sexpr_indent("""\
+... (html (head (title Example)) (body (h1 s-expr formatter example)
+... (a (@ (href http://www.example.com)) A link)))""")
+... #doctest: +NORMALIZE_WHITESPACE
+ html
+ head
+ title Example
+ body
+ h1 s-expr formatter example
+ a
+ @
+ href http://www.example.com
+ A link
+</pre>
+</div>
+<div class="section" id="other-kinds-of-iterables">
+<h3><a class="toc-backref" href="#id13" name="other-kinds-of-iterables">Other kinds of iterables</a></h3>
+<p>The following class generates iterable which are not iterators:</p>
+<pre class="literal-block">
+#<reiterable.py>
+
+class ReIter(object):
+ "A re-iterable object."
+ def __iter__(self):
+ yield 1
+ yield 2
+ yield 3
+
+#</reiterable.py>
+</pre>
+<pre class="doctest-block">
+>>> from reiterable import ReIter
+>>> rit = ReIter()
+>>> list(rit)
+[1, 2, 3]
+>>> list(rit) # it is reiterable!
+[1, 2, 3]
+</pre>
+</div>
+<div class="section" id="the-itertools-module">
+<h3><a class="toc-backref" href="#id14" name="the-itertools-module">The itertools module</a></h3>
+<blockquote>
+<ul class="simple">
+<li>count([n]) --> n, n+1, n+2, ...</li>
+<li>cycle(p) --> p0, p1, ... plast, p0, p1, ...</li>
+<li>repeat(elem [,n]) --> elem, elem, elem, ... endlessly or up to n times</li>
+<li>izip(p, q, ...) --> (p[0], q[0]), (p[1], q[1]), ...</li>
+<li>ifilter(pred, seq) --> elements of seq where pred(elem) is True</li>
+<li>ifilterfalse(pred, seq) --> elements of seq where pred(elem) is False</li>
+<li>islice(seq, [start,] stop [, step]) --> elements from seq[start:stop:step]</li>
+<li>imap(fun, p, q, ...) --> fun(p0, q0), fun(p1, q1), ...</li>
+<li>starmap(fun, seq) --> fun(*seq[0]), fun(*seq[1]), ...</li>
+<li>tee(it, n=2) --> (it1, it2 , ... itn) splits one iterator into n</li>
+<li>chain(p, q, ...) --> p0, p1, ... plast, q0, q1, ...</li>
+<li>takewhile(pred, seq) --> seq[0], seq[1], until pred fails</li>
+<li>dropwhile(pred, seq) --> seq[n], seq[n+1], starting when pred fails</li>
+<li>groupby(iterable[, keyfunc]) --> sub-iterators grouped by value of keyfunc(v)</li>
+</ul>
+</blockquote>
+</div>
+<div class="section" id="anytrue">
+<h3><a class="toc-backref" href="#id15" name="anytrue">anyTrue</a></h3>
+<pre class="doctest-block">
+>>> import itertools
+>>> def anyTrue(predicate, iterable):
+... return True in itertools.imap(predicate, iterable)
+...
+>>> fname = "picture.gif"
+>>> anyTrue(fname.endswith, ".jpg .gif .png".split())
+True
+</pre>
+<p>AnyTrue does <em>short-circuit</em>:</p>
+<pre class="doctest-block">
+>>> def is3(i):
+... print "i=%s" % i
+... return i == 3
+</pre>
+<pre class="doctest-block">
+>>> anyTrue(is3, range(10))
+i=0
+i=1
+i=2
+i=3
+True
+</pre>
+</div>
+<div class="section" id="chop">
+<h3><a class="toc-backref" href="#id16" name="chop">chop</a></h3>
+<p>You want to chop an iterable in batches of a given size:</p>
+<pre class="doctest-block">
+>>> from chop import chop
+>>> list(chop([1, 2, 3, 4], 2))
+[[1, 2], [3, 4]]
+>>> list(chop([1, 2, 3, 4, 5, 6, 7],3))
+[[1, 2, 3], [4, 5, 6], [7]]
+</pre>
+<p>Here is a possible implementation:</p>
+<pre class="literal-block">
+#<chop.py>
+
+# see also http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/303279
+
+import itertools
+
+def chop(iterable, batchsize):
+ it = iter(iterable)
+ while True:
+ batch = list(itertools.islice(it, batchsize))
+ if batch: yield batch
+ else: break
+
+#</chop.py>
+</pre>
+<p>For people thinking Python is too readable, here is a one-liner:</p>
+<pre class="doctest-block">
+>>> chop = lambda it, n : itertools.izip(*(iter(it),)*n)
+...
+>>> list(chop([1,2,3,4], 2))
+[(1, 2), (3, 4)]
+</pre>
+</div>
+<div class="section" id="tee">
+<h3><a class="toc-backref" href="#id17" name="tee">tee</a></h3>
+<p>To make copies of iterables; typically used in parsers:</p>
+<pre class="doctest-block">
+>>> from itertools import tee, chain, izip
+>>> chars, prevs = tee("abc")
+>>> prevs = chain([None], prevs)
+>>> for char, prev in izip(chars, prevs):
+... print char, prev
+...
+a None
+b a
+c b
+</pre>
+</div>
+<div class="section" id="grouping-and-sorting">
+<h3><a class="toc-backref" href="#id18" name="grouping-and-sorting">Grouping and sorting</a></h3>
+<pre class="doctest-block">
+>>> from itertools import groupby
+>>> from operator import itemgetter
+</pre>
+<pre class="doctest-block">
+>>> NAME, AGE = 0, 1
+>>> query_result = ("Smith", 34), ("Donaldson", 34), ("Lee", 22), ("Orr", 22)
+</pre>
+<p>Grouping together people of the same age:</p>
+<pre class="doctest-block">
+>>> for k, g in groupby(query_result, key=itemgetter(AGE)):
+... print k, list(g)
+...
+34 [('Smith', 34), ('Donaldson', 34)]
+22 [('Lee', 22), ('Orr', 22)]
+</pre>
+<p>Sorting by name:</p>
+<pre class="doctest-block">
+>>> for tup in sorted(query_result, key=itemgetter(NAME)):
+... print tup
+('Donaldson', 34)
+('Lee', 22)
+('Orr', 22)
+('Smith', 34)
+</pre>
+</div>
+</div>
+</div>
+<div class="section" id="lecture-2-objects-delegation-inheritance">
+<h1><a class="toc-backref" href="#id19" name="lecture-2-objects-delegation-inheritance">Lecture 2: Objects (delegation & inheritance)</a></h1>
+<div class="section" id="part-i-delegation">
+<h2><a class="toc-backref" href="#id20" name="part-i-delegation">Part I: delegation</a></h2>
+<p>Understanding how attribute access works: internal delegation via <em>descriptors</em></p>
+<div class="section" id="accessing-simple-attributes">
+<h3><a class="toc-backref" href="#id21" name="accessing-simple-attributes">Accessing simple attributes</a></h3>
+<pre class="doctest-block">
+>>> class C(object):
+... a = 2
+... def __init__(self, x):
+... self.x = x
+...
+</pre>
+<pre class="doctest-block">
+>>> c = C(1)
+>>> c.x
+1
+>>> c.a
+2
+</pre>
+<p>We are retrieving</p>
+<pre class="doctest-block">
+>>> c.__dict__["x"]
+1
+</pre>
+<p>If there is nothing in c.__dict__, Python looks at C.__dict__:</p>
+<pre class="doctest-block">
+>>> print c.__dict__.get("a")
+None
+</pre>
+<pre class="doctest-block">
+>>> C.__dict__["a"]
+2
+</pre>
+<p>If there is nothing in C.__dict__, Python looks at the superclasses according
+to the MRO (see part II).</p>
+</div>
+<div class="section" id="accessing-methods">
+<h3><a class="toc-backref" href="#id22" name="accessing-methods">Accessing methods</a></h3>
+<pre class="doctest-block">
+>>> c.__init__ #doctest: +ELLIPSIS
+<bound method C.__init__ of <__main__.C object at 0x...>>
+</pre>
+<p>since __init__ is not in c.__dict__ Python looks in the class dictionary
+and finds</p>
+<pre class="doctest-block">
+>>> C.__dict__["__init__"] #doctest: +ELLIPSIS
+<function __init__ at 0x...>
+</pre>
+<p>Then it magically converts the function into a method bound to the instance
+"c".</p>
+<p>NOTE: this mechanism works for new-style classes only.</p>
+<p>The old-style mechanism is less consistent and the attribute lookup of special
+methods is special: (*)</p>
+<pre class="doctest-block">
+>>> class C(object): pass
+>>> c = C()
+>>> c.__str__ = lambda : "hello!"
+>>> print c #doctest: +ELLIPSIS
+<__main__.C object at ...>
+</pre>
+<p>whereas for old-style classes</p>
+<pre class="doctest-block">
+>>> class C: pass
+>>> c = C()
+>>> c.__str__ = lambda : "hello!"
+>>> print c
+hello!
+</pre>
+<p>the special method is looked for in the instance dictionary too.</p>
+<p>(*) modulo a very subtle difference for __getattr__-delegated special methods,
+see later.</p>
+</div>
+<div class="section" id="converting-functions-into-methods">
+<h3><a class="toc-backref" href="#id23" name="converting-functions-into-methods">Converting functions into methods</a></h3>
+<p>It is possible to convert a function into a bound or unbound method
+by invoking the <tt class="docutils literal"><span class="pre">__get__</span></tt> special method:</p>
+<pre class="doctest-block">
+>>> def f(x): pass
+>>> f.__get__ #doctest: +ELLIPSIS
+<method-wrapper object at 0x...>
+</pre>
+<pre class="doctest-block">
+>>> class C(object): pass
+...
+</pre>
+<pre class="doctest-block">
+>>> def f(self): pass
+...
+>>> f.__get__(C(), C) #doctest: +ELLIPSIS
+<bound method C.f of <__main__.C object at 0x...>>
+</pre>
+<pre class="doctest-block">
+>>> f.__get__(None, C)
+<unbound method C.f>
+</pre>
+<p>Functions are the simplest example of <em>descriptors</em>.</p>
+<p>Access to methods works since internally Python transforms</p>
+<blockquote>
+<tt class="docutils literal"><span class="pre">c.__init__</span> <span class="pre">-></span> <span class="pre">type(c).__dict__['__init__'].__get__(c,</span> <span class="pre">type(c))</span></tt></blockquote>
+<p>Note: not <em>all</em> functions are descriptors:</p>
+<pre class="doctest-block">
+>>> from operator import add
+>>> add.__get__
+Traceback (most recent call last):
+ ...
+AttributeError: 'builtin_function_or_method' object has no attribute '__get__'
+</pre>
+</div>
+<div class="section" id="hack-a-very-slick-adder">
+<h3><a class="toc-backref" href="#id24" name="hack-a-very-slick-adder">Hack: a very slick adder</a></h3>
+<p>The descriptor protocol can be (ab)used as a way to avoid the late binding
+issue in for loops:</p>
+<pre class="doctest-block">
+>>> def add(x,y):
+... return x + y
+>>> closures = [add.__get__(i) for i in range(10)]
+>>> closures[5](1000)
+1005
+</pre>
+<p>Notice: operator.add will not work.</p>
+</div>
+<div class="section" id="descriptor-protocol">
+<h3><a class="toc-backref" href="#id25" name="descriptor-protocol">Descriptor Protocol</a></h3>
+<p>Everything at <a class="reference" href="http://users.rcn.com/python/download/Descriptor.htm">http://users.rcn.com/python/download/Descriptor.htm</a></p>
+<p>Formally:</p>
+<pre class="literal-block">
+descr.__get__(self, obj, type=None) --> value
+descr.__set__(self, obj, value) --> None
+descr.__delete__(self, obj) --> None
+</pre>
+<p>Examples of custom descriptors:</p>
+<pre class="literal-block">
+#<descriptor.py>
+
+
+class AttributeDescriptor(object):
+ def __get__(self, obj, cls=None):
+ if obj is None and cls is None:
+ raise TypeError("__get__(None, None) is invalid")
+ elif obj is None:
+ return self.get_from_class(cls)
+ else:
+ return self.get_from_obj(obj)
+ def get_from_class(self, cls):
+ print "Getting %s from %s" % (self, cls)
+ def get_from_obj(self, obj):
+ print "Getting %s from %s" % (self, obj)
+
+
+class Staticmethod(AttributeDescriptor):
+ def __init__(self, func):
+ self.func = func
+ def get_from_class(self, cls):
+ return self.func
+ get_from_obj = get_from_class
+
+
+class Classmethod(AttributeDescriptor):
+ def __init__(self, func):
+ self.func = func
+ def get_from_class(self, cls):
+ return self.func.__get__(cls, type(cls))
+ def get_from_obj(self, obj):
+ return self.get_from_class(obj.__class__)
+
+class C(object):
+ s = Staticmethod(lambda : 1)
+ c = Classmethod(lambda cls : cls.__name__)
+
+c = C()
+
+assert C.s() == c.s() == 1
+assert C.c() == c.c() == "C"
+
+#</descriptor.py>
+</pre>
+</div>
+<div class="section" id="multilingual-attribute">
+<h3><a class="toc-backref" href="#id26" name="multilingual-attribute">Multilingual attribute</a></h3>
+<p>Inspirated by a question in the Italian Newsgroup:</p>
+<pre class="literal-block">
+#<multilingual.py>
+
+import sys
+from descriptor import AttributeDescriptor
+
+class MultilingualAttribute(AttributeDescriptor):
+ """When a MultilingualAttribute is accessed, you get the translation
+ corresponding to the currently selected language.
+ """
+ def __init__(self, **translations):
+ self.trans = translations
+ def get_from_class(self, cls):
+ return self.trans[getattr(cls, "language", None) or
+ sys.modules[cls.__module__].language]
+ def get_from_obj(self, obj):
+ return self.trans[getattr(obj, "language", None) or
+ sys.modules[obj.__class__.__module__].language]
+
+
+language = "en"
+
+# a dummy User class
+class DefaultUser(object):
+ def has_permission(self):
+ return False
+
+class WebApplication(object):
+ error_msg = MultilingualAttribute(
+ en="You cannot access this page",
+ it="Questa pagina non e' accessibile",
+ fr="Vous ne pouvez pas acceder cette page",)
+ user = DefaultUser()
+ def __init__(self, language=None):
+ self.language = language or getattr(self.__class__, "language", None)
+ def show_page(self):
+ if not self.user.has_permission():
+ return self.error_msg
+
+
+app = WebApplication()
+assert app.show_page() == "You cannot access this page"
+
+app.language = "fr"
+assert app.show_page() == "Vous ne pouvez pas acceder cette page"
+
+app.language = "it"
+assert app.show_page() == "Questa pagina non e' accessibile"
+
+app.language = "en"
+assert app.show_page() == "You cannot access this page"
+
+#</multilingual.py>
+</pre>
+<p>The same can be done with properties:</p>
+<pre class="literal-block">
+#<multilingualprop.py>
+
+language = "en"
+
+# a dummy User class
+class DefaultUser(object):
+ def has_permission(self):
+ return False
+
+def multilingualProperty(**trans):
+ def get(self):
+ return trans[self.language]
+ def set(self, value):
+ trans[self.language] = value
+ return property(get, set)
+
+class WebApplication(object):
+ language = language
+ error_msg = multilingualProperty(
+ en="You cannot access this page",
+ it="Questa pagina non e' accessibile",
+ fr="Vous ne pouvez pas acceder cette page",)
+ user = DefaultUser()
+ def __init__(self, language=None):
+ if language: self.language = self.language
+ def show_page(self):
+ if not self.user.has_permission():
+ return self.error_msg
+
+#</multilingualprop.py>
+</pre>
+<p>This also gives the possibility to set the error messages.</p>
+<p>The difference with the descriptor approach</p>
+<pre class="doctest-block">
+>>> from multilingual import WebApplication
+>>> app = WebApplication()
+>>> print app.error_msg
+You cannot access this page
+>>> print WebApplication.error_msg
+You cannot access this page
+</pre>
+<p>is that with properties there is no nice access from the class:</p>
+<pre class="doctest-block">
+>>> from multilingualprop import WebApplication
+>>> WebApplication.error_msg #doctest: +ELLIPSIS
+<property object at ...>
+</pre>
+</div>
+<div class="section" id="another-use-case-for-properties-storing-users">
+<h3><a class="toc-backref" href="#id27" name="another-use-case-for-properties-storing-users">Another use case for properties: storing users</a></h3>
+<p>Consider a library providing a simple User class:</p>
+<pre class="literal-block">
+#<crypt_user.py>
+
+class User(object):
+ def __init__(self, username, password):
+ self.username, self.password = username, password
+
+#</crypt_user.py>
+</pre>
+<p>The User objects are stored in a database as they are.
+For security purpose, in a second version of the library it is
+decided to crypt the password, so that only crypted passwords
+are stored in the database. With properties, it is possible to
+implement this functionality without changing the source code for
+the User class:</p>
+<pre class="literal-block">
+#<crypt_user.py>
+
+from crypt import crypt
+
+def cryptedAttribute(seed="x"):
+ def get(self):
+ return getattr(self, "_pw", None)
+ def set(self, value):
+ self._pw = crypt(value, seed)
+ return property(get, set)
+
+User.password = cryptedAttribute()
+</pre>
+<p>#</crypt_user.py></p>
+<pre class="doctest-block">
+>>> from crypt_user import User
+>>> u = User("michele", "secret")
+>>> print u.password
+xxZREZpkHZpkI
+</pre>
+<p>Notice the property factory approach used here.</p>
+</div>
+<div class="section" id="low-level-delegation-via-getattribute">
+<h3><a class="toc-backref" href="#id28" name="low-level-delegation-via-getattribute">Low-level delegation via __getattribute__</a></h3>
+<p>Attribute access is managed by the__getattribute__ special method:</p>
+<pre class="literal-block">
+#<tracedaccess.py>
+
+class TracedAccess(object):
+ def __getattribute__(self, name):
+ print "Accessing %s" % name
+ return object.__getattribute__(self, name)
+
+
+class C(TracedAccess):
+ s = staticmethod(lambda : 'staticmethod')
+ c = classmethod(lambda cls: 'classmethod')
+ m = lambda self: 'method'
+ a = "hello"
+
+#</tracedaccess.py>
+</pre>
+<pre class="doctest-block">
+>>> from tracedaccess import C
+>>> c = C()
+>>> print c.s()
+Accessing s
+staticmethod
+>>> print c.c()
+Accessing c
+classmethod
+>>> print c.m()
+Accessing m
+method
+>>> print c.a
+Accessing a
+hello
+>>> print c.__init__ #doctest: +ELLIPSIS
+Accessing __init__
+<method-wrapper object at 0x...>
+>>> try: c.x
+... except AttributeError, e: print e
+...
+Accessing x
+'C' object has no attribute 'x'
+</pre>
+<pre class="doctest-block">
+>>> c.y = 'y'
+>>> c.y
+Accessing y
+'y'
+</pre>
+<p>You are probably familiar with <tt class="docutils literal"><span class="pre">__getattr__</span></tt> which is similar
+to <tt class="docutils literal"><span class="pre">__getattribute__</span></tt>, but it is called <em>only for missing attributes</em>.</p>
+</div>
+<div class="section" id="traditional-delegation-via-getattr">
+<h3><a class="toc-backref" href="#id29" name="traditional-delegation-via-getattr">Traditional delegation via __getattr__</a></h3>
+<p>Realistic use case in "object publishing":</p>
+<pre class="literal-block">
+#<webapp.py>
+
+class WebApplication(object):
+ def __getattr__(self, name):
+ return name.capitalize()
+
+
+app = WebApplication()
+
+assert app.page1 == 'Page1'
+assert app.page2 == 'Page2'
+
+#</webapp.py>
+</pre>
+<p>Here is another use case in HTML generation:</p>
+<pre class="literal-block">
+#<XMLtag.py>
+
+def makeattr(dict_or_list_of_pairs):
+ dic = dict(dict_or_list_of_pairs)
+ return " ".join('%s="%s"' % (k, dic[k]) for k in dic) # simplistic
+
+class XMLTag(object):
+ def __getattr__(self, name):
+ def tag(value, **attr):
+ """value can be a string or a sequence of strings."""
+ if hasattr(value, "__iter__"): # is iterable
+ value = " ".join(value)
+ return "<%s %s>%s</%s>" % (name, makeattr(attr), value, name)
+ return tag
+
+class XMLShortTag(object):
+ def __getattr__(self, name):
+ def tag(**attr):
+ return "<%s %s />" % (name, makeattr(attr))
+ return tag
+
+tag = XMLTag()
+tg = XMLShortTag()
+
+#</XMLtag.py>
+</pre>
+<pre class="doctest-block">
+>>> from XMLtag import tag, tg
+>>> print tag.a("example.com", href="http://www.example.com")
+<a href="http://www.example.com">example.com</a>
+>>> print tg.br(**{'class':"br_style"})
+<br class="br_style" />
+</pre>
+</div>
+<div class="section" id="keyword-dictionaries-with-getattr-setattr">
+<h3><a class="toc-backref" href="#id30" name="keyword-dictionaries-with-getattr-setattr">Keyword dictionaries with __getattr__/__setattr__</a></h3>
+<pre class="literal-block">
+#<kwdict.py>
+
+class kwdict(dict): # or UserDict, to make it to work with Zope
+ """A typing shortcut used in place of a keyword dictionary."""
+ def __getattr__(self, name):
+ return self[name]
+ def __setattr__(self, name, value):
+ self[name] = value
+
+#</kwdict.py>
+</pre>
+<p>And now for a completely different solution:</p>
+<pre class="literal-block">
+#<dictwrapper.py>
+
+class DictWrapper(object):
+ def __init__(self, **kw):
+ self.__dict__.update(kw)
+
+#</dictwrapper.py>
+</pre>
+</div>
+<div class="section" id="delegation-to-special-methods-caveat">
+<h3><a class="toc-backref" href="#id31" name="delegation-to-special-methods-caveat">Delegation to special methods caveat</a></h3>
+<pre class="doctest-block">
+>>> class ListWrapper(object):
+... def __init__(self, ls):
+... self._list = ls
+... def __getattr__(self, name):
+... if name == "__getitem__": # special method
+... return self._list.__getitem__
+... elif name == "reverse": # regular method
+... return self._list.reverse
+... else:
+... raise AttributeError("%r is not defined" % name)
+...
+>>> lw = ListWrapper([0,1,2])
+>>> print lw.x
+Traceback (most recent call last):
+ ...
+AttributeError: 'x' is not defined
+</pre>
+<pre class="doctest-block">
+>>> lw.reverse()
+>>> print lw.__getitem__(0)
+2
+>>> print lw.__getitem__(1)
+1
+>>> print lw.__getitem__(2)
+0
+>>> print lw[0]
+Traceback (most recent call last):
+ ...
+TypeError: unindexable object
+</pre>
+</div>
+</div>
+<div class="section" id="part-ii-inheritance">
+<h2><a class="toc-backref" href="#id32" name="part-ii-inheritance">Part II: Inheritance</a></h2>
+<p>The major changes in inheritance from Python 2.1 to 2.2+ are:</p>
+<ol class="arabic simple">
+<li>you can subclass built-in types (as a consequence the constructor__new__
+has been exposed to the user, to help subclassing immutable types);</li>
+<li>the Method Resolution Order (MRO) has changed;</li>
+<li>now Python allows <em>cooperative method calls</em>, i.e. we have <em>super</em>.</li>
+</ol>
+<div class="section" id="why-you-need-to-know-about-mi-even-if-you-do-not-use-it">
+<h3><a class="toc-backref" href="#id33" name="why-you-need-to-know-about-mi-even-if-you-do-not-use-it">Why you need to know about MI even if you do not use it</a></h3>
+<p>In principle, the last two changes are relevant only if you use multiple
+inheritance. If you use single inheritance only, you don't need <tt class="docutils literal"><span class="pre">super</span></tt>:
+you can just name the superclass.
+However, somebody else may want to use your class in a MI hierarchy,
+and you would make her life difficult if you don't use <tt class="docutils literal"><span class="pre">super</span></tt>.</p>
+<p>My SI hierarchy:</p>
+<pre class="literal-block">
+#<why_super.py>
+
+class Base(object):
+ def __init__(self):
+ print "B.__init__"
+
+class MyClass(Base):
+ "I do not cooperate with others"
+ def __init__(self):
+ print "MyClass.__init__"
+ Base.__init__(self) #instead of super(MyClass, self).__init__()
+
+#</why_super.py>
+</pre>
+<p>Her MI hierarchy:</p>
+<pre class="literal-block">
+#<why_super.py>
+
+class Mixin(Base):
+ "I am cooperative with others"
+ def __init__(self):
+ print "Mixin.__init__"
+ super(Mixin, self).__init__()
+
+class HerClass(MyClass, Mixin):
+ "I am supposed to be cooperative too"
+ def __init__(self):
+ print "HerClass.__init__"
+ super(HerClass, self).__init__()
+
+#</why_super.py>
+</pre>
+<pre class="doctest-block">
+>>> from why_super import HerClass
+>>> h = HerClass() # Mixin.__init__ is not called!
+HerClass.__init__
+MyClass.__init__
+B.__init__
+</pre>
+<blockquote>
+<pre class="literal-block">
+ 4 object
+ |
+ 3 Base
+ / \
+1 MyClass 2 Mixin
+ \ /
+ 0 HerClass
+</pre>
+</blockquote>
+<pre class="doctest-block">
+>>> [ancestor.__name__ for ancestor in HerClass.mro()]
+['HerClass', 'MyClass', 'Mixin', 'Base', 'object']
+</pre>
+<p>In order to be polite versus your future users, you should use <tt class="docutils literal"><span class="pre">super</span></tt>
+always. This adds a cognitive burden even for people not using MI :-(</p>
+<p>Notice that there is no good comprehensive reference on <tt class="docutils literal"><span class="pre">super</span></tt> (yet)
+Your best bet is still <a class="reference" href="http://www.python.org/2.2.3/descrintro.html#cooperation">http://www.python.org/2.2.3/descrintro.html#cooperation</a></p>
+<p>The MRO instead is explained here: <a class="reference" href="http://www.python.org/2.3/mro.html">http://www.python.org/2.3/mro.html</a></p>
+<p>Notice that I DO NOT recommand Multiple Inheritance.</p>
+<p>More often than not you are better off using composition/delegation/wrapping,
+etc.</p>
+<p>See Zope 2 -> Zope 3 experience.</p>
+</div>
+<div class="section" id="a-few-details-about-super-not-the-whole-truth">
+<h3><a class="toc-backref" href="#id34" name="a-few-details-about-super-not-the-whole-truth">A few details about <tt class="docutils literal docutils literal"><span class="pre">super</span></tt> (not the whole truth)</a></h3>
+<pre class="doctest-block">
+>>> class B(object):
+... def __init__(self): print "B.__init__"
+...
+>>> class C(B):
+... def __init__(self): print "C.__init__"
+...
+>>> c = C()
+C.__init__
+</pre>
+<p><tt class="docutils literal"><span class="pre">super(cls,</span> <span class="pre">instance)</span></tt>, where <tt class="docutils literal"><span class="pre">instance</span></tt> is an instance of <tt class="docutils literal"><span class="pre">cls</span></tt> or of
+a subclass of <tt class="docutils literal"><span class="pre">cls</span></tt>, retrieves the right method in the MRO:</p>
+<pre class="doctest-block">
+>>> super(C, c).__init__ #doctest: +ELLIPSIS
+<bound method C.__init__ of <__main__.C object at 0x...>>
+</pre>
+<pre class="doctest-block">
+>>> super(C, c).__init__.im_func is B.__init__.im_func
+True
+</pre>
+<pre class="doctest-block">
+>>> super(C, c).__init__()
+B.__init__
+</pre>
+<p><tt class="docutils literal"><span class="pre">super(cls,</span> <span class="pre">subclass)</span></tt> works for unbound methods:</p>
+<pre class="doctest-block">
+>>> super(C, C).__init__
+<unbound method C.__init__>
+</pre>
+<pre class="doctest-block">
+>>> super(C, C).__init__.im_func is B.__init__.im_func
+True
+>>> super(C, C).__init__(c)
+B.__init__
+</pre>
+<p><tt class="docutils literal"><span class="pre">super(cls,</span> <span class="pre">subclass)</span></tt> is also necessary for classmethods and staticmethods.
+Properties and custom descriptorsw works too:</p>
+<pre class="literal-block">
+#<super_ex.py>
+
+from descriptor import AttributeDescriptor
+
+class B(object):
+ @staticmethod
+ def sm(): return "staticmethod"
+
+ @classmethod
+ def cm(cls): return cls.__name__
+
+ p = property()
+ a = AttributeDescriptor()
+
+class C(B): pass
+
+#</super_ex.py>
+</pre>
+<pre class="doctest-block">
+>>> from super_ex import C
+</pre>
+<p>Staticmethod usage:</p>
+<pre class="doctest-block">
+>>> super(C, C).sm #doctest: +ELLIPSIS
+<function sm at 0x...>
+>>> super(C, C).sm()
+'staticmethod'
+</pre>
+<p>Classmethod usage:</p>
+<pre class="doctest-block">
+>>> super(C, C()).cm
+<bound method type.cm of <class 'super_ex.C'>>
+>>> super(C, C).cm() # C is automatically passed
+'C'
+</pre>
+<p>Property usage:</p>
+<pre class="doctest-block">
+>>> print super(C, C).p #doctest: +ELLIPSIS
+<property object at 0x...>
+>>> super(C, C).a #doctest: +ELLIPSIS
+Getting <descriptor.AttributeDescriptor object at 0x...> from <class 'super_ex.C'>
+</pre>
+<p><tt class="docutils literal"><span class="pre">super</span></tt> does not work with old-style classes, however you can use the
+following trick:</p>
+<pre class="literal-block">
+#<super_old_new.py>
+class O:
+ def __init__(self):
+ print "O.__init__"
+
+class N(O, object):
+ def __init__(self):
+ print "N.__init__"
+ super(N, self).__init__()
+
+#</super_old_new.py>
+</pre>
+<pre class="doctest-block">
+>>> from super_old_new import N
+>>> new = N()
+N.__init__
+O.__init__
+</pre>
+<p>There are dozens of tricky points concerning <tt class="docutils literal"><span class="pre">super</span></tt>, be warned!</p>
+</div>
+<div class="section" id="subclassing-built-in-types-new-vs-init">
+<h3><a class="toc-backref" href="#id35" name="subclassing-built-in-types-new-vs-init">Subclassing built-in types; __new__ vs. __init__</a></h3>
+<pre class="literal-block">
+#<point.py>
+
+class NotWorkingPoint(tuple):
+ def __init__(self, x, y):
+ super(NotWorkingPoint, self).__init__((x,y))
+ self.x, self.y = x, y
+
+#</point.py>
+</pre>
+<pre class="doctest-block">
+>>> from point import NotWorkingPoint
+>>> p = NotWorkingPoint(2,3)
+Traceback (most recent call last):
+ ...
+TypeError: tuple() takes at most 1 argument (2 given)
+</pre>
+<pre class="literal-block">
+#<point.py>
+
+class Point(tuple):
+ def __new__(cls, x, y):
+ return super(Point, cls).__new__(cls, (x,y))
+ def __init__(self, x, y):
+ super(Point, self).__init__((x, y))
+ self.x, self.y = x, y
+
+#</point.py>
+</pre>
+<p>Notice that__new__ is a staticmethod, not a classmethod, so one needs
+to pass the class explicitely.</p>
+<pre class="doctest-block">
+>>> from point import Point
+>>> p = Point(2,3)
+>>> print p, p.x, p.y
+(2, 3) 2 3
+</pre>
+</div>
+<div class="section" id="be-careful-when-using-new-with-mutable-types">
+<h3><a class="toc-backref" href="#id36" name="be-careful-when-using-new-with-mutable-types">Be careful when using __new__ with mutable types</a></h3>
+<pre class="doctest-block">
+>>> class ListWithDefault(list):
+... def __new__(cls):
+... return super(ListWithDefault, cls).__new__(cls, ["hello"])
+...
+>>> print ListWithDefault() # beware! NOT ["hello"]!
+[]
+</pre>
+<p>Reason: lists are re-initialized to empty lists in list.__init__!</p>
+<p>Instead</p>
+<pre class="doctest-block">
+>>> class ListWithDefault(list):
+... def __init__(self):
+... super(ListWithDefault, self).__init__(["hello"])
+...
+>>> print ListWithDefault() # works!
+['hello']
+</pre>
+</div>
+</div>
+</div>
+<div class="section" id="lecture-3-magic-i-e-decorators-and-metaclasses">
+<h1><a class="toc-backref" href="#id37" name="lecture-3-magic-i-e-decorators-and-metaclasses">Lecture 3: Magic (i.e. decorators and metaclasses)</a></h1>
+<div class="section" id="part-i-decorators">
+<h2><a class="toc-backref" href="#id38" name="part-i-decorators">Part I: decorators</a></h2>
+<p>Decorators are just sugar: their functionality was already in the language</p>
+<pre class="doctest-block">
+>>> def s(): pass
+>>> s = staticmethod(s)
+</pre>
+<pre class="doctest-block">
+>>> @staticmethod
+... def s(): pass
+...
+</pre>
+<p>However sugar <em>does</em> matter.</p>
+<div class="section" id="a-typical-decorator-traced">
+<h3><a class="toc-backref" href="#id39" name="a-typical-decorator-traced">A typical decorator: traced</a></h3>
+<pre class="literal-block">
+#<traced.py>
+
+def traced(func):
+ def tracedfunc(*args, **kw):
+ print "calling %s.%s" % (func.__module__, func.__name__)
+ return func(*args, **kw)
+ tracedfunc.__name__ = func.__name__
+ return tracedfunc
+
+@traced
+def f(): pass
+
+#</traced.py>
+</pre>
+<pre class="doctest-block">
+>>> from traced import f
+>>> f()
+calling traced.f
+</pre>
+</div>
+<div class="section" id="a-decorator-factory-timed">
+<h3><a class="toc-backref" href="#id40" name="a-decorator-factory-timed">A decorator factory: Timed</a></h3>
+<pre class="literal-block">
+#<timed.py>
+
+import sys, time
+
+class Timed(object):
+ """Decorator factory: each decorator object wraps a function and
+ executes it many times (default 100 times).
+ The average time spent in one iteration, expressed in milliseconds,
+ is stored in the attributes wrappedfunc.time and wrappedfunc.clocktime,
+ and displayed into a log file which defaults to stdout.
+ """
+ def __init__(self, repeat=100, logfile=sys.stdout):
+ self.repeat = repeat
+ self.logfile = logfile
+ def __call__(self, func):
+ def wrappedfunc(*args, **kw):
+ fullname = "%s.%s ..." % (func.__module__, func.func_name)
+ print >> self.logfile, 'Executing %s' % fullname.ljust(30),
+ time1 = time.time()
+ clocktime1 = time.clock()
+ for i in xrange(self.repeat):
+ res = func(*args,**kw) # executes func self.repeat times
+ time2 = time.time()
+ clocktime2 = time.clock()
+ wrappedfunc.time = 1000*(time2-time1)/self.repeat
+ wrappedfunc.clocktime = 1000*(clocktime2 - clocktime1)/self.repeat
+ print >> self.logfile, \
+ 'Real time: %s ms;' % self.rounding(wrappedfunc.time),
+ print >> self.logfile, \
+ 'Clock time: %s ms' % self.rounding(wrappedfunc.clocktime)
+ return res
+ wrappedfunc.func_name = func.func_name
+ wrappedfunc.__module__ = func.__module__
+ return wrappedfunc
+ @staticmethod
+ def rounding(float_):
+ "Three digits rounding for small numbers, 1 digit rounding otherwise."
+ if float_ < 10.:
+ return "%5.3f" % float_
+ else:
+ return "%5.1f" % float_
+
+#</timed.py>
+</pre>
+<pre class="doctest-block">
+>>> from timed import Timed
+>>> from random import sample
+>>> example_ls = sample(xrange(1000000), 1000)
+>>> @Timed()
+... def list_sort(ls):
+... ls.sort()
+...
+>>> list_sort(example_ls) #doctest: +ELLIPSIS
+Executing __main__.list_sort ... Real time: 0... ms; Clock time: 0... ms
+</pre>
+</div>
+<div class="section" id="a-powerful-decorator-pattern">
+<h3><a class="toc-backref" href="#id41" name="a-powerful-decorator-pattern">A powerful decorator pattern</a></h3>
+<pre class="literal-block">
+#<traced_function2.py>
+
+from decorators import decorator
+
+def trace(f, *args, **kw):
+ print "calling %s with args %s, %s" % (f.func_name, args, kw)
+ return f(*args, **kw)
+
+traced_function = decorator(trace)
+
+@traced_function
+def f1(x):
+ pass
+
+@traced_function
+def f2(x, y):
+ pass
+
+#</traced_function2.py>
+</pre>
+<pre class="doctest-block">
+>>> from traced_function2 import traced_function, f1, f2
+>>> f1(1)
+calling f1 with args (1,), {}
+>>> f2(1,2)
+calling f2 with args (1, 2), {}
+</pre>
+<p>works with pydoc:</p>
+<pre class="literal-block">
+$ pydoc2.4 traced_function2.f2
+Help on function f1 in traced_function2:
+
+traced_function2.f1 = f1(x)
+
+$ pydoc2.4 traced_function2.f2
+Help on function f2 in traced_function2:
+
+traced_function2.f2 = f2(x, y)
+</pre>
+<p>Here is the source code:</p>
+<pre class="literal-block">
+#<decorators.py>
+
+import inspect, itertools
+
+def getinfo(func):
+ """Return an info dictionary containing:
+ - name (the name of the function : str)
+ - argnames (the names of the arguments : list)
+ - defarg (the values of the default arguments : list)
+ - fullsign (the full signature : str)
+ - shortsign (the short signature : str)
+ - arg0 ... argn (shortcuts for the names of the arguments)
+
+ >> def f(self, x=1, y=2, *args, **kw): pass
+
+ >> info = getinfo(f)
+
+ >> info["name"]
+ 'f'
+ >> info["argnames"]
+ ['self', 'x', 'y', 'args', 'kw']
+
+ >> info["defarg"]
+ (1, 2)
+
+ >> info["shortsign"]
+ 'self, x, y, *args, **kw'
+
+ >> info["fullsign"]
+ 'self, x=defarg[0], y=defarg[1], *args, **kw'
+
+ >> info["arg0"], info["arg1"], info["arg2"], info["arg3"], info["arg4"]
+ ('self', 'x', 'y', 'args', 'kw')
+ """
+ assert inspect.ismethod(func) or inspect.isfunction(func)
+ regargs, varargs, varkwargs, defaults = inspect.getargspec(func)
+ argnames = list(regargs)
+ if varargs: argnames.append(varargs)
+ if varkwargs: argnames.append(varkwargs)
+ counter = itertools.count()
+ fullsign = inspect.formatargspec(
+ regargs, varargs, varkwargs, defaults,
+ formatvalue=lambda value: "=defarg[%i]" % counter.next())[1:-1]
+ shortsign = inspect.formatargspec(
+ regargs, varargs, varkwargs, defaults,
+ formatvalue=lambda value: "")[1:-1]
+ dic = dict(("arg%s" % n, name) for n, name in enumerate(argnames))
+ dic.update(name=func.__name__, argnames=argnames, shortsign=shortsign,
+ fullsign = fullsign, defarg = func.func_defaults or ())
+ return dic
+
+def _contains_reserved_names(dic): # helper
+ return "_call_" in dic or "_func_" in dic
+
+def _decorate(func, caller):
+ """Takes a function and a caller and returns the function
+ decorated with that caller. The decorated function is obtained
+ by evaluating a lambda function with the correct signature.
+ """
+ infodict = getinfo(func)
+ assert not _contains_reserved_names(infodict["argnames"]), \
+ "You cannot use _call_ or _func_ as argument names!"
+ execdict=dict(_func_=func, _call_=caller, defarg=func.func_defaults or ())
+ if func.__name__ == "<lambda>":
+ lambda_src = "lambda %(fullsign)s: _call_(_func_, %(shortsign)s)" \
+ % infodict
+ dec_func = eval(lambda_src, execdict)
+ else:
+ func_src = """def %(name)s(%(fullsign)s):
+ return _call_(_func_, %(shortsign)s)""" % infodict
+ exec func_src in execdict
+ dec_func = execdict[func.__name__]
+ dec_func.__doc__ = func.__doc__
+ dec_func.__dict__ = func.__dict__
+ return dec_func
+
+class decorator(object):
+ """General purpose decorator factory: takes a caller function as
+input and returns a decorator. A caller function is any function like this::
+
+ def caller(func, *args, **kw):
+ # do something
+ return func(*args, **kw)
+
+Here is an example of usage:
+
+ >> @decorator
+ .. def chatty(f, *args, **kw):
+ .. print "Calling %r" % f.__name__
+ .. return f(*args, **kw)
+
+ >> @chatty
+ .. def f(): pass
+ ..
+ >> f()
+ Calling 'f'
+ """
+ def __init__(self, caller):
+ self.caller = caller
+ def __call__(self, func):
+ return _decorate(func, self.caller)
+
+
+#</decorators.py>
+</pre>
+<p>The possibilities of this pattern are endless.</p>
+</div>
+<div class="section" id="a-deferred-decorator">
+<h3><a class="toc-backref" href="#id42" name="a-deferred-decorator">A deferred decorator</a></h3>
+<p>You want to execute a procedure only after a certain time delay (for instance
+for use within an asyncronous Web framework):</p>
+<pre class="literal-block">
+#<deferred.py>
+"Deferring the execution of a procedure (function returning None)"
+
+import threading
+from decorators import decorator
+
+def deferred(nsec):
+ def call_later(func, *args, **kw):
+ return threading.Timer(nsec, func, args, kw).start()
+ return decorator(call_later)
+
+@deferred(2)
+def hello():
+ print "hello"
+
+if __name__ == "__main__":
+ hello()
+ print "Calling hello() ..."
+
+
+#</deferred.py>
+
+$ python deferred.py
+</pre>
+<p>Show an example of an experimental decorator based web framework
+(doctester_frontend).</p>
+</div>
+</div>
+<div class="section" id="part-ii-metaclasses">
+<h2><a class="toc-backref" href="#id43" name="part-ii-metaclasses">Part II: metaclasses</a></h2>
+<p>Metaclasses are there! Consider this example from a recent post on c.l.py:</p>
+<pre class="literal-block">
+#<BaseClass.py>
+
+class BaseClass(object):
+ "Do something"
+
+#</BaseClass.py>
+</pre>
+<pre class="doctest-block">
+>>> import BaseClass # instead of 'from BaseClass import BaseClass'
+>>> class C(BaseClass): pass
+...
+Traceback (most recent call last):
+ ...
+TypeError: Error when calling the metaclass bases
+ module.__init__() takes at most 2 arguments (3 given)
+</pre>
+<p>The reason for the error is that class <tt class="docutils literal"><span class="pre">C(BaseClass):</span> <span class="pre">pass</span></tt> is
+actually calling the <tt class="docutils literal"><span class="pre">type</span></tt> metaclass with three arguments:</p>
+<pre class="literal-block">
+C = type("C", (BaseClass,), {})
+</pre>
+<p><tt class="docutils literal"><span class="pre">type.__new__</span></tt> tries to use <tt class="docutils literal"><span class="pre">type(BaseClass)</span></tt> as metaclass,
+but since BaseClass here is a module, and <tt class="docutils literal"><span class="pre">ModuleType</span></tt> is not
+a metaclass, it cannot work. The error message reflects a conflict with
+the signature of ModuleType which requires two parameters and not three.</p>
+<p>So even if you don't use them, you may want to know they exist.</p>
+<div class="section" id="rejuvenating-old-style-classes">
+<h3><a class="toc-backref" href="#id44" name="rejuvenating-old-style-classes">Rejuvenating old-style classes</a></h3>
+<pre class="doctest-block">
+>>> class Old: pass
+>>> print type(Old)
+<type 'classobj'>
+</pre>
+<pre class="doctest-block">
+>>> __metaclass__ = type # to rejuvenate class
+>>> class NotOld: pass
+...
+>>> print NotOld.__class__
+<type 'type'>
+</pre>
+</div>
+<div class="section" id="a-typical-metaclass-example-metatracer">
+<h3><a class="toc-backref" href="#id45" name="a-typical-metaclass-example-metatracer">A typical metaclass example: MetaTracer</a></h3>
+<pre class="literal-block">
+#<metatracer.py>
+
+import inspect
+from decorators import decorator
+
+@decorator
+def traced(meth, *args, **kw):
+ cls = meth.__cls__
+ modname = meth.__module__ or cls.__module__
+ print "calling %s.%s.%s" % (modname, cls.__name__, meth.__name__)
+ return meth(*args, **kw)
+
+class MetaTracer(type):
+ def __init__(cls, name, bases, dic):
+ super(MetaTracer, cls).__init__(name, bases, dic)
+ for k, v in dic.iteritems():
+ if inspect.isfunction(v):
+ v.__cls__ = cls # so we know in which class v was defined
+ setattr(cls, k, traced(v))
+
+#</metatracer.py>
+</pre>
+<p>Usage: exploring classes in the standard library</p>
+<pre class="literal-block">
+#<dictmixin.py>
+
+from metatracer import MetaTracer
+from UserDict import DictMixin
+
+class TracedDM(DictMixin, object):
+ __metaclass__ = MetaTracer
+ def __getitem__(self, item):
+ return item
+ def keys(self):
+ return [1,2,3]
+
+#</dictmixin.py>
+</pre>
+<pre class="doctest-block">
+>>> from dictmixin import TracedDM
+>>> print TracedDM()
+calling dictmixin.TracedDM.keys
+calling dictmixin.TracedDM.__getitem__
+calling dictmixin.TracedDM.__getitem__
+calling dictmixin.TracedDM.__getitem__
+{1: 1, 2: 2, 3: 3}
+</pre>
+</div>
+<div class="section" id="real-life-example-check-overriding">
+<h3><a class="toc-backref" href="#id46" name="real-life-example-check-overriding">Real life example: check overriding</a></h3>
+<pre class="literal-block">
+#<check_overriding.py>
+
+class Base(object):
+ a = 0
+
+class CheckOverriding(type):
+ "Prints a message if we are overriding a name."
+ def __new__(mcl, name, bases, dic):
+ for name, val in dic.iteritems():
+ if name.startswith("__") and name.endswith("__"):
+ continue # ignore special names
+ a_base_has_name = True in (hasattr(base, name) for base in bases)
+ if a_base_has_name:
+ print "AlreadyDefinedNameWarning: " + name
+ return super(CheckOverriding, mcl).__new__(mcl, name, bases, dic)
+
+class MyClass(Base):
+ __metaclass__ = CheckOverriding
+ a = 1
+
+class ChildClass(MyClass):
+ a = 2
+</pre>
+<p>#</check_overriding.py></p>
+<pre class="doctest-block">
+>>> import check_overriding
+AlreadyDefinedNameWarning: a
+AlreadyDefinedNameWarning: a
+</pre>
+</div>
+<div class="section" id="logfile">
+<h3><a class="toc-backref" href="#id47" name="logfile">LogFile</a></h3>
+<pre class="literal-block">
+#<logfile.py>
+
+import subprocess
+
+def memoize(func):
+ memoize_dic = {}
+ def wrapped_func(*args):
+ if args in memoize_dic:
+ return memoize_dic[args]
+ else:
+ result = func(*args)
+ memoize_dic[args] = result
+ return result
+ wrapped_func.__name__ = func.__name__
+ wrapped_func.__doc__ = func.__doc__
+ wrapped_func.__dict__ = func.__dict__
+ return wrapped_func
+
+class Memoize(type): # Singleton is a special case of Memoize
+ @memoize
+ def __call__(cls, *args):
+ return super(Memoize, cls).__call__(*args)
+
+class LogFile(file):
+ """Open a file for append."""
+ __metaclass__ = Memoize
+ def __init__(self, name = "/tmp/err.log"):
+ self.viewer_cmd = 'xterm -e less'.split()
+ super(LogFile, self).__init__(name, "a")
+
+ def display(self, *ls):
+ "Use 'less' to display the log file in a separate xterm."
+ print >> self, "\n".join(map(str, ls)); self.flush()
+ subprocess.call(self.viewer_cmd + [self.name])
+
+ def reset(self):
+ "Erase the log file."
+ print >> file(self.name, "w")
+
+if __name__ == "__main__": # test
+ print >> LogFile(), "hello"
+ print >> LogFile(), "world"
+ LogFile().display()
+
+#</logfile.py>
+
+$ python logfile.py
+</pre>
+</div>
+<div class="section" id="cooperative-hierarchies">
+<h3><a class="toc-backref" href="#id48" name="cooperative-hierarchies">Cooperative hierarchies</a></h3>
+<pre class="literal-block">
+#<cooperative_init.py>
+
+"""Given a hierarchy, makes __init__ cooperative.
+The only change needed is to add a line
+
+ __metaclass__ = CooperativeInit
+
+to the base class of your hierarchy."""
+
+from decorators import decorator
+
+class CooperativeInit(type):
+ def __init__(cls, name, bases, dic):
+
+ @decorator
+ def make_cooperative(__init__, self, *args, **kw):
+ super(cls, self).__init__(*args, **kw)
+ __init__(self, *args, **kw)
+
+ __init__ = dic.get("__init__")
+ if __init__:
+ cls.__init__ = make_cooperative(__init__)
+
+class Base:
+ __metaclass__ = CooperativeInit
+ def __init__(self):
+ print "B.__init__"
+
+class C1(Base):
+ def __init__(self):
+ print "C1.__init__"
+
+class C2(Base):
+ def __init__(self):
+ print "C2.__init__"
+
+class D(C1, C2):
+ def __init__(self):
+ print "D.__init__"
+
+#</cooperative_init.py>
+</pre>
+<pre class="doctest-block">
+>>> from cooperative_init import D
+>>> d = D()
+B.__init__
+C2.__init__
+C1.__init__
+D.__init__
+</pre>
+</div>
+<div class="section" id="metaclass-enhanced-modules">
+<h3><a class="toc-backref" href="#id49" name="metaclass-enhanced-modules">Metaclass-enhanced modules</a></h3>
+<pre class="literal-block">
+#<import_with_metaclass.py>
+"""
+``import_with_metaclass(metaclass, modulepath)`` generates
+a new module from and old module, by enhancing all of its classes.
+This is not perfect, but it should give you a start."""
+
+import os, sys, inspect, types
+
+def import_with_metaclass(metaclass, modulepath):
+ modname = os.path.basename(modulepath)[:-3] # simplistic
+ mod = types.ModuleType(modname)
+ locs = dict(
+ __module__ = modname,
+ __metaclass__ = metaclass,
+ object = metaclass("object", (), {}))
+ execfile(modulepath, locs)
+ for k, v in locs.iteritems():
+ if inspect.isclass(v): # otherwise it would be "__builtin__"
+ v.__module__ = "__dynamic__"
+ setattr(mod, k, v)
+ return mod
+</pre>
+<p>#</import_with_metaclass.py></p>
+<pre class="doctest-block">
+>>> from import_with_metaclass import import_with_metaclass
+>>> from metatracer import MetaTracer
+>>> traced_optparse = import_with_metaclass(MetaTracer,
+... "/usr/lib/python2.4/optparse.py")
+>>> op = traced_optparse.OptionParser()
+calling __dynamic__.OptionParser.__init__
+calling __dynamic__.OptionContainer.__init__
+calling __dynamic__.OptionParser._create_option_list
+calling __dynamic__.OptionContainer._create_option_mappings
+calling __dynamic__.OptionContainer.set_conflict_handler
+calling __dynamic__.OptionContainer.set_description
+calling __dynamic__.OptionParser.set_usage
+calling __dynamic__.IndentedHelpFormatter.__init__
+calling __dynamic__.HelpFormatter.__init__
+calling __dynamic__.HelpFormatter.set_parser
+calling __dynamic__.OptionParser._populate_option_list
+calling __dynamic__.OptionParser._add_help_option
+calling __dynamic__.OptionContainer.add_option
+calling __dynamic__.Option.__init__
+calling __dynamic__.Option._check_opt_strings
+calling __dynamic__.Option._set_opt_strings
+calling __dynamic__.Option._set_attrs
+calling __dynamic__.OptionContainer._check_conflict
+calling __dynamic__.OptionParser._init_parsing_state
+</pre>
+<p>traced_optparse is a dynamically generated module not leaving in the
+file system.</p>
+</div>
+<div class="section" id="magic-properties">
+<h3><a class="toc-backref" href="#id50" name="magic-properties">Magic properties</a></h3>
+<pre class="literal-block">
+#<magicprop.py>
+
+class MagicProperties(type):
+ def __init__(cls, name, bases, dic):
+ prop_names = set(name[3:] for name in dic
+ if name.startswith("get")
+ or name.startswith("set"))
+ for name in prop_names:
+ getter = getattr(cls, "get" + name, None)
+ setter = getattr(cls, "set" + name, None)
+ setattr(cls, name, property(getter, setter))
+
+class Base(object):
+ __metaclass__ = MagicProperties
+ def getx(self):
+ return self._x
+ def setx(self, value):
+ self._x = value
+
+class Child(Base):
+ def getx(self):
+ print "getting x"
+ return super(Child, self).getx()
+ def setx(self, value):
+ print "setting x"
+ super(Child, self).setx(value)
+
+#</magicprop.py>
+</pre>
+<pre class="doctest-block">
+>>> from magicprop import Child
+>>> c = Child()
+>>> c.x = 1
+setting x
+>>> print c.x
+getting x
+1
+</pre>
+</div>
+<div class="section" id="hack-evil-properties">
+<h3><a class="toc-backref" href="#id51" name="hack-evil-properties">Hack: evil properties</a></h3>
+<pre class="literal-block">
+#<evilprop.py>
+
+def convert2property(name, bases, d):
+ return property(d.get('get'), d.get('set'),
+ d.get('del'),d.get('__doc__'))
+
+class C(object):
+ class x:
+ """An evil test property"""
+ __metaclass__ = convert2property
+ def get(self):
+ print 'Getting %s' % self._x
+ return self._x
+ def set(self, value):
+ self._x = value
+ print 'Setting to', value
+
+#</evilprop.py>
+</pre>
+<pre class="doctest-block">
+>>> from evilprop import C
+>>> c = C()
+>>> c.x = 5
+Setting to 5
+>>> c.x
+Getting 5
+5
+>>> print C.x.__doc__
+An evil test property
+</pre>
+</div>
+<div class="section" id="why-i-suggest-not-to-use-metaclasses-in-production-code">
+<h3><a class="toc-backref" href="#id52" name="why-i-suggest-not-to-use-metaclasses-in-production-code">Why I suggest <em>not</em> to use metaclasses in production code</a></h3>
+<blockquote>
+<ul class="simple">
+<li>there are very few good use case for metaclasses in production code
+(i.e. 99% of time you don't need them)</li>
+<li>they put a cognitive burden on the developer;</li>
+<li>a design without metaclasses is less magic and likely more robust;</li>
+<li>a design with metaclasses makes it difficult to use other metaclasses
+for debugging.</li>
+</ul>
+</blockquote>
+<p>As far as I know, string.Template is the only metaclass-enhanced class
+in the standard library; the metaclass is used to give the possibility to
+change the defaults:</p>
+<pre class="literal-block">
+delimiter = '$'
+idpattern = r'[_a-z][_a-z0-9]*'
+</pre>
+<p>in subclasses of Template.</p>
+<pre class="doctest-block">
+>>> from string import Template
+>>> from metatracer import MetaTracer
+>>> class TracedTemplate(Template):
+... __metaclass__ = MetaTracer
+...
+Traceback (most recent call last):
+ ...
+TypeError: Error when calling the metaclass bases
+ metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
+</pre>
+<p>Solution: use a consistent metaclass</p>
+<pre class="doctest-block">
+>>> class GoodMeta(MetaTracer, type(Template)): pass
+...
+>>> class TracedTemplate(Template):
+... __metaclass__ = GoodMeta
+</pre>
+</div>
+<div class="section" id="is-there-an-automatic-way-of-solving-the-conflict">
+<h3><a class="toc-backref" href="#id53" name="is-there-an-automatic-way-of-solving-the-conflict">Is there an automatic way of solving the conflict?</a></h3>
+<p>Yes, but you really need to be a metaclass wizard.</p>
+<p><a class="reference" href="http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/204197">http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/204197</a></p>
+<pre class="doctest-block">
+>>> from noconflict import classmaker
+>>> class TracedTemplate(Template):
+... __metaclass__ = classmaker((MetaTracer,))
+>>> print type(TracedTemplate)
+<class 'noconflict._MetaTracer_TemplateMetaclass'>
+</pre>
+<pre class="literal-block">
+#<noconflict.py>
+
+import inspect, types, __builtin__
+from skip_redundant import skip_redundant
+
+memoized_metaclasses_map = {}
+
+# utility function
+def remove_redundant(metaclasses):
+ skipset = set([types.ClassType])
+ for meta in metaclasses: # determines the metaclasses to be skipped
+ skipset.update(inspect.getmro(meta)[1:])
+ return tuple(skip_redundant(metaclasses, skipset))
+
+##################################################################
+## now the core of the module: two mutually recursive functions ##
+##################################################################
+
+def get_noconflict_metaclass(bases, left_metas, right_metas):
+ """Not intended to be used outside of this module, unless you know
+ what you are doing."""
+ # make tuple of needed metaclasses in specified priority order
+ metas = left_metas + tuple(map(type, bases)) + right_metas
+ needed_metas = remove_redundant(metas)
+
+ # return existing confict-solving meta, if any
+ if needed_metas in memoized_metaclasses_map:
+ return memoized_metaclasses_map[needed_metas]
+ # nope: compute, memoize and return needed conflict-solving meta
+ elif not needed_metas: # wee, a trivial case, happy us
+ meta = type
+ elif len(needed_metas) == 1: # another trivial case
+ meta = needed_metas[0]
+ # check for recursion, can happen i.e. for Zope ExtensionClasses
+ elif needed_metas == bases:
+ raise TypeError("Incompatible root metatypes", needed_metas)
+ else: # gotta work ...
+ metaname = '_' + ''.join([m.__name__ for m in needed_metas])
+ meta = classmaker()(metaname, needed_metas, {})
+ memoized_metaclasses_map[needed_metas] = meta
+ return meta
+
+def classmaker(left_metas=(), right_metas=()):
+ def make_class(name, bases, adict):
+ metaclass = get_noconflict_metaclass(bases, left_metas, right_metas)
+ return metaclass(name, bases, adict)
+ return make_class
+
+#################################################################
+## and now a conflict-safe replacement for 'type' ##
+#################################################################
+
+__type__=__builtin__.type # the aboriginal 'type'
+# left available in case you decide to rebind __builtin__.type
+
+class safetype(__type__):
+ # this is REALLY DEEP MAGIC
+ """Overrides the ``__new__`` method of the ``type`` metaclass, making the
+ generation of classes conflict-proof."""
+ def __new__(mcl, *args):
+ nargs = len(args)
+ if nargs == 1: # works as __builtin__.type
+ return __type__(args[0])
+ elif nargs == 3: # creates the class using the appropriate metaclass
+ n, b, d = args # name, bases and dictionary
+ meta = get_noconflict_metaclass(b, (mcl,), ())
+ if meta is mcl: # meta is trivial, dispatch to the default __new__
+ return super(safetype, mcl).__new__(mcl, n, b, d)
+ else: # non-trivial metaclass, dispatch to the right __new__
+ # (it will take a second round) # print mcl, meta
+ return super(mcl, meta).__new__(meta, n, b, d)
+ else:
+ raise TypeError('%s() takes 1 or 3 arguments' % mcl.__name__)
+
+#</noconflict.py>
+</pre>
+</div>
+</div>
+</div>
+</div>
+</body>
+</html>
diff --git a/pypers/oxford/all.rst b/pypers/oxford/all.rst new file mode 100755 index 0000000..dee7230 --- /dev/null +++ b/pypers/oxford/all.rst @@ -0,0 +1,2006 @@ +Lectures on Advanced Python Programming
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. image:: accu2005.png
+
+:Author: Michele Simionato
+:Given: 19 April 2005
+:Revised: 7 September 2005
+
+.. contents::
+
+
+
+Lecture 1: Loops (i.e. iterators & generators)
+==============================================
+
+Part I: iterators
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+Iterators are everywhere
+--------------------------------
+
+>>> for i in 1, 2, 3:
+... print i
+1
+2
+3
+
+The 'for' loop is using *iterators* internally::
+
+ it = iter((1,2,3))
+ while True:
+ try:
+ print it.next()
+ except StopIteration:
+ break
+
+Iterables and iterators
+--------------------------
+
+*Iterable* = anything you can loop over = any sequence + any object with an __iter__ method;
+
+Not every sequence has an __iter__ method:
+
+>>> "hello".__iter__()
+Traceback (most recent call last):
+ ...
+AttributeError: 'str' object has no attribute '__iter__'
+
+*Iterator* = any object with a .next method and an __iter__ method returning self
+
+Simpler way to get an iterator
+--------------------------------------------------------
+
+>>> it = iter("hello")
+>>> it.next()
+'h'
+>>> it.next()
+'e'
+>>> it.next()
+'l'
+>>> it.next()
+'l'
+>>> it.next()
+'o'
+>>> it.next()
+Traceback (most recent call last):
+ ...
+StopIteration
+
+Sentinel syntax iter(callable, sentinel)
+--------------------------------------------
+
+Example::
+
+ $ echo -e "value1\nvalue2\nEND\n" > data.txt
+ $ python -c "print list(iter(file('data.txt').readline, 'END\n'))"
+ ['value1\n', 'value2\n']
+
+Beware of infinite iterators:
+
+>>> repeat = iter(lambda : "some value", "")
+>>> repeat.next()
+'some value'
+
+Second simpler way to get an iterator: generator-expressions
+-------------------------------------------------------------
+
+>>> squares = (i*i for i in range(1,11))
+>>> list(squares)
+[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
+
+Excessive parenthesis can be skipped, so use
+
+>>> dict((i, i*i) for i in range(1,11))
+{1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81, 10: 100}
+
+instead of
+
+>>> dict([(i, i*i) for i in range(1,11)])
+{1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81, 10: 100}
+
+(as usual, the most elegant version is the most efficient).
+
+Iteration caveats
+--------------------------
+
+>>> ls = [i for i in (1,2,3)]
+>>> i
+3
+
+>>> it = (j for j in (1,2,3))
+>>> j
+Traceback (most recent call last):
+ ...
+NameError: name 'j' is not defined
+
+A subtler example:
+
+>>> ls = [lambda :i for i in (1,2,3)]
+>>> ls[0]()
+3
+
+instead
+
+>>> it = (lambda :i for i in (1,2,3))
+>>> it.next()()
+1
+>>> it.next()()
+2
+>>> it.next()()
+3
+
+*seems* to be working but it is not really the case:
+
+>>> it = (lambda :i for i in (1,2,3))
+>>> f1 = it.next()
+>>> f2 = it.next()
+>>> f3 = it.next()
+>>> f1()
+3
+
+The reason is that Python does LATE binding *always*. The solution is ugly:
+
+>>> it = list(lambda i=i:i for i in (1,2,3))
+>>> it[0]()
+1
+>>> it[1]()
+2
+>>> it[2]()
+3
+
+Part II: generators
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+Trivial example:
+
+>>> def gen123(): # "function" which returns an iterator over the values 1, 2, 3
+... yield 1
+... yield 2
+... yield 3
+...
+>>> it = gen123()
+>>> it.next()
+1
+>>> it.next()
+2
+>>> it.next()
+3
+>>> it.next()
+Traceback (most recent call last):
+ ...
+StopIteration
+
+Real life example: using generators to generate HTML tables
+
+::
+
+ #<htmltable.py>
+
+ def HTMLTablegen(table):
+ yield "<table>"
+ for row in table:
+ yield "<tr>"
+ for col in row:
+ yield "<td>%s</td>" % col
+ yield "</tr>"
+ yield "</table>"
+
+ def test():
+ return "\n".join(HTMLTablegen([["Row", "City"],
+ [1,'London'], [2, 'Oxford']]))
+
+ if __name__ == "__main__": # example
+ print test()
+
+ #</htmltable.py>
+
+>>> from htmltable import test
+>>> print test()
+<table>
+<tr>
+<td>Row</td>
+<td>City</td>
+</tr>
+<tr>
+<td>1</td>
+<td>London</td>
+</tr>
+<tr>
+<td>2</td>
+<td>Oxford</td>
+</tr>
+</table>
+
+A simple recipe: skip redundant
+---------------------------------
+
+How to remove duplicates by keeping the order::
+
+ #<skip_redundant.py>
+
+ def skip_redundant(iterable, skipset=None):
+ "Redundant items are repeated items or items in the original skipset."
+ if skipset is None: skipset = set()
+ for item in iterable:
+ if item not in skipset:
+ skipset.add(item)
+ yield item
+
+ #</skip_redundant.py>
+
+>>> from skip_redundant import skip_redundant
+>>> print list(skip_redundant("<hello, world>", skipset=set("<>")))
+['h', 'e', 'l', 'o', ',', ' ', 'w', 'r', 'd']
+
+Another real life example: working with nested structures
+----------------------------------------------------------
+
+::
+
+ #<walk.py>
+
+ def walk(iterable, level=0):
+ for obj in iterable:
+ if not hasattr(obj, "__iter__"): # atomic object
+ yield obj, level
+ else: # composed object: iterate again
+ for subobj, lvl in walk(obj, level + 1):
+ yield subobj, lvl
+
+ def flatten(iterable):
+ return (obj for obj, level in walk(iterable))
+
+ def pprint(iterable):
+ for obj, level in walk(iterable):
+ print " "*level, obj
+
+ #</walk.py>
+
+>>> from walk import flatten, pprint
+>>> nested_ls = [1,[2,[3,[[[4,5],6]]]],7]
+>>> pprint(nested_ls)
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+>>> pprint(flatten(nested_ls))
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+
+Another typical use case for generators: parsers
+---------------------------------------------------------
+
+A very stripped down parser for nested expressions
+
+::
+
+ #<sexpr2indent.py>
+ """A simple s-expression formatter."""
+
+ import re
+
+ def parse(sexpr):
+ position = 0
+ nesting_level = 0
+ paren = re.compile(r"(?P<paren_beg>\()|(?P<paren_end>\))")
+ while True:
+ match = paren.search(sexpr, position)
+ if match:
+ yield nesting_level, sexpr[position: match.start()]
+ if match.lastgroup == "paren_beg":
+ nesting_level += 1
+ elif match.lastgroup == "paren_end":
+ nesting_level -= 1
+ position = match.end()
+ else:
+ break
+
+ def sexpr_indent(sexpr):
+ for nesting, text in parse(sexpr.replace("\n", "")):
+ if text.strip(): print " "*nesting, text
+
+ #</sexpr2indent.py>
+
+>>> from sexpr2indent import sexpr_indent
+>>> sexpr_indent("""\
+... (html (head (title Example)) (body (h1 s-expr formatter example)
+... (a (@ (href http://www.example.com)) A link)))""")
+... #doctest: +NORMALIZE_WHITESPACE
+ html
+ head
+ title Example
+ body
+ h1 s-expr formatter example
+ a
+ @
+ href http://www.example.com
+ A link
+
+
+Other kinds of iterables
+------------------------------------------------
+
+The following class generates iterable which are not iterators:
+::
+
+ #<reiterable.py>
+
+ class ReIter(object):
+ "A re-iterable object."
+ def __iter__(self):
+ yield 1
+ yield 2
+ yield 3
+
+ #</reiterable.py>
+
+>>> from reiterable import ReIter
+>>> rit = ReIter()
+>>> list(rit)
+[1, 2, 3]
+>>> list(rit) # it is reiterable!
+[1, 2, 3]
+
+The itertools module
+----------------------------------------------------
+
+ - count([n]) --> n, n+1, n+2, ...
+ - cycle(p) --> p0, p1, ... plast, p0, p1, ...
+ - repeat(elem [,n]) --> elem, elem, elem, ... endlessly or up to n times
+ - izip(p, q, ...) --> (p[0], q[0]), (p[1], q[1]), ...
+ - ifilter(pred, seq) --> elements of seq where pred(elem) is True
+ - ifilterfalse(pred, seq) --> elements of seq where pred(elem) is False
+ - islice(seq, [start,] stop [, step]) --> elements from seq[start:stop:step]
+ - imap(fun, p, q, ...) --> fun(p0, q0), fun(p1, q1), ...
+ - starmap(fun, seq) --> fun(\*seq[0]), fun(\*seq[1]), ...
+ - tee(it, n=2) --> (it1, it2 , ... itn) splits one iterator into n
+ - chain(p, q, ...) --> p0, p1, ... plast, q0, q1, ...
+ - takewhile(pred, seq) --> seq[0], seq[1], until pred fails
+ - dropwhile(pred, seq) --> seq[n], seq[n+1], starting when pred fails
+ - groupby(iterable[, keyfunc]) --> sub-iterators grouped by value of keyfunc(v)
+
+anyTrue
+------------------------------
+
+>>> import itertools
+>>> def anyTrue(predicate, iterable):
+... return True in itertools.imap(predicate, iterable)
+...
+>>> fname = "picture.gif"
+>>> anyTrue(fname.endswith, ".jpg .gif .png".split())
+True
+
+AnyTrue does *short-circuit*:
+
+>>> def is3(i):
+... print "i=%s" % i
+... return i == 3
+
+>>> anyTrue(is3, range(10))
+i=0
+i=1
+i=2
+i=3
+True
+
+chop
+----------------------
+
+You want to chop an iterable in batches of a given size:
+
+>>> from chop import chop
+>>> list(chop([1, 2, 3, 4], 2))
+[[1, 2], [3, 4]]
+>>> list(chop([1, 2, 3, 4, 5, 6, 7],3))
+[[1, 2, 3], [4, 5, 6], [7]]
+
+Here is a possible implementation::
+
+ #<chop.py>
+
+ # see also http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/303279
+
+ import itertools
+
+ def chop(iterable, batchsize):
+ it = iter(iterable)
+ while True:
+ batch = list(itertools.islice(it, batchsize))
+ if batch: yield batch
+ else: break
+
+ #</chop.py>
+
+For people thinking Python is too readable, here is a one-liner:
+
+>>> chop = lambda it, n : itertools.izip(*(iter(it),)*n)
+...
+>>> list(chop([1,2,3,4], 2))
+[(1, 2), (3, 4)]
+
+tee
+-----------------------
+
+To make copies of iterables; typically used in parsers:
+
+>>> from itertools import tee, chain, izip
+>>> chars, prevs = tee("abc")
+>>> prevs = chain([None], prevs)
+>>> for char, prev in izip(chars, prevs):
+... print char, prev
+...
+a None
+b a
+c b
+
+Grouping and sorting
+----------------------
+
+>>> from itertools import groupby
+>>> from operator import itemgetter
+
+>>> NAME, AGE = 0, 1
+>>> query_result = ("Smith", 34), ("Donaldson", 34), ("Lee", 22), ("Orr", 22)
+
+Grouping together people of the same age:
+
+>>> for k, g in groupby(query_result, key=itemgetter(AGE)):
+... print k, list(g)
+...
+34 [('Smith', 34), ('Donaldson', 34)]
+22 [('Lee', 22), ('Orr', 22)]
+
+Sorting by name:
+
+>>> for tup in sorted(query_result, key=itemgetter(NAME)):
+... print tup
+('Donaldson', 34)
+('Lee', 22)
+('Orr', 22)
+('Smith', 34)
+
+
+
+Lecture 2: Objects (delegation & inheritance)
+==============================================
+
+Part I: delegation
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+Understanding how attribute access works: internal delegation via *descriptors*
+
+Accessing simple attributes
+--------------------------------
+
+>>> class C(object):
+... a = 2
+... def __init__(self, x):
+... self.x = x
+...
+
+>>> c = C(1)
+>>> c.x
+1
+>>> c.a
+2
+
+We are retrieving
+
+>>> c.__dict__["x"]
+1
+
+If there is nothing in c.__dict__, Python looks at C.__dict__:
+
+>>> print c.__dict__.get("a")
+None
+
+>>> C.__dict__["a"]
+2
+
+If there is nothing in C.__dict__, Python looks at the superclasses according
+to the MRO (see part II).
+
+Accessing methods
+--------------------------------------------------------
+
+>>> c.__init__ #doctest: +ELLIPSIS
+<bound method C.__init__ of <__main__.C object at 0x...>>
+
+since __init__ is not in c.__dict__ Python looks in the class dictionary
+and finds
+
+>>> C.__dict__["__init__"] #doctest: +ELLIPSIS
+<function __init__ at 0x...>
+
+Then it magically converts the function into a method bound to the instance
+"c".
+
+NOTE: this mechanism works for new-style classes only.
+
+The old-style mechanism is less consistent and the attribute lookup of special
+methods is special: (*)
+
+>>> class C(object): pass
+>>> c = C()
+>>> c.__str__ = lambda : "hello!"
+>>> print c #doctest: +ELLIPSIS
+<__main__.C object at ...>
+
+whereas for old-style classes
+
+>>> class C: pass
+>>> c = C()
+>>> c.__str__ = lambda : "hello!"
+>>> print c
+hello!
+
+the special method is looked for in the instance dictionary too.
+
+(*) modulo a very subtle difference for __getattr__-delegated special methods,
+see later.
+
+Converting functions into methods
+-------------------------------------
+
+It is possible to convert a function into a bound or unbound method
+by invoking the ``__get__`` special method:
+
+>>> def f(x): pass
+>>> f.__get__ #doctest: +ELLIPSIS
+<method-wrapper object at 0x...>
+
+>>> class C(object): pass
+...
+
+>>> def f(self): pass
+...
+>>> f.__get__(C(), C) #doctest: +ELLIPSIS
+<bound method C.f of <__main__.C object at 0x...>>
+
+>>> f.__get__(None, C)
+<unbound method C.f>
+
+Functions are the simplest example of *descriptors*.
+
+Access to methods works since internally Python transforms
+
+ ``c.__init__ -> type(c).__dict__['__init__'].__get__(c, type(c))``
+
+
+Note: not *all* functions are descriptors:
+
+>>> from operator import add
+>>> add.__get__
+Traceback (most recent call last):
+ ...
+AttributeError: 'builtin_function_or_method' object has no attribute '__get__'
+
+Hack: a very slick adder
+-----------------------------
+
+The descriptor protocol can be (ab)used as a way to avoid the late binding
+issue in for loops:
+
+>>> def add(x,y):
+... return x + y
+>>> closures = [add.__get__(i) for i in range(10)]
+>>> closures[5](1000)
+1005
+
+Notice: operator.add will not work.
+
+Descriptor Protocol
+----------------------
+
+Everything at http://users.rcn.com/python/download/Descriptor.htm
+
+Formally::
+
+ descr.__get__(self, obj, type=None) --> value
+ descr.__set__(self, obj, value) --> None
+ descr.__delete__(self, obj) --> None
+
+Examples of custom descriptors::
+
+ #<descriptor.py>
+
+
+ class AttributeDescriptor(object):
+ def __get__(self, obj, cls=None):
+ if obj is None and cls is None:
+ raise TypeError("__get__(None, None) is invalid")
+ elif obj is None:
+ return self.get_from_class(cls)
+ else:
+ return self.get_from_obj(obj)
+ def get_from_class(self, cls):
+ print "Getting %s from %s" % (self, cls)
+ def get_from_obj(self, obj):
+ print "Getting %s from %s" % (self, obj)
+
+
+ class Staticmethod(AttributeDescriptor):
+ def __init__(self, func):
+ self.func = func
+ def get_from_class(self, cls):
+ return self.func
+ get_from_obj = get_from_class
+
+
+ class Classmethod(AttributeDescriptor):
+ def __init__(self, func):
+ self.func = func
+ def get_from_class(self, cls):
+ return self.func.__get__(cls, type(cls))
+ def get_from_obj(self, obj):
+ return self.get_from_class(obj.__class__)
+
+ class C(object):
+ s = Staticmethod(lambda : 1)
+ c = Classmethod(lambda cls : cls.__name__)
+
+ c = C()
+
+ assert C.s() == c.s() == 1
+ assert C.c() == c.c() == "C"
+
+ #</descriptor.py>
+
+Multilingual attribute
+----------------------
+
+Inspirated by a question in the Italian Newsgroup::
+
+ #<multilingual.py>
+
+ import sys
+ from descriptor import AttributeDescriptor
+
+ class MultilingualAttribute(AttributeDescriptor):
+ """When a MultilingualAttribute is accessed, you get the translation
+ corresponding to the currently selected language.
+ """
+ def __init__(self, **translations):
+ self.trans = translations
+ def get_from_class(self, cls):
+ return self.trans[getattr(cls, "language", None) or
+ sys.modules[cls.__module__].language]
+ def get_from_obj(self, obj):
+ return self.trans[getattr(obj, "language", None) or
+ sys.modules[obj.__class__.__module__].language]
+
+
+ language = "en"
+
+ # a dummy User class
+ class DefaultUser(object):
+ def has_permission(self):
+ return False
+
+ class WebApplication(object):
+ error_msg = MultilingualAttribute(
+ en="You cannot access this page",
+ it="Questa pagina non e' accessibile",
+ fr="Vous ne pouvez pas acceder cette page",)
+ user = DefaultUser()
+ def __init__(self, language=None):
+ self.language = language or getattr(self.__class__, "language", None)
+ def show_page(self):
+ if not self.user.has_permission():
+ return self.error_msg
+
+
+ app = WebApplication()
+ assert app.show_page() == "You cannot access this page"
+
+ app.language = "fr"
+ assert app.show_page() == "Vous ne pouvez pas acceder cette page"
+
+ app.language = "it"
+ assert app.show_page() == "Questa pagina non e' accessibile"
+
+ app.language = "en"
+ assert app.show_page() == "You cannot access this page"
+
+ #</multilingual.py>
+
+The same can be done with properties::
+
+ #<multilingualprop.py>
+
+ language = "en"
+
+ # a dummy User class
+ class DefaultUser(object):
+ def has_permission(self):
+ return False
+
+ def multilingualProperty(**trans):
+ def get(self):
+ return trans[self.language]
+ def set(self, value):
+ trans[self.language] = value
+ return property(get, set)
+
+ class WebApplication(object):
+ language = language
+ error_msg = multilingualProperty(
+ en="You cannot access this page",
+ it="Questa pagina non e' accessibile",
+ fr="Vous ne pouvez pas acceder cette page",)
+ user = DefaultUser()
+ def __init__(self, language=None):
+ if language: self.language = self.language
+ def show_page(self):
+ if not self.user.has_permission():
+ return self.error_msg
+
+ #</multilingualprop.py>
+
+This also gives the possibility to set the error messages.
+
+The difference with the descriptor approach
+
+>>> from multilingual import WebApplication
+>>> app = WebApplication()
+>>> print app.error_msg
+You cannot access this page
+>>> print WebApplication.error_msg
+You cannot access this page
+
+is that with properties there is no nice access from the class:
+
+>>> from multilingualprop import WebApplication
+>>> WebApplication.error_msg #doctest: +ELLIPSIS
+<property object at ...>
+
+Another use case for properties: storing users
+------------------------------------------------------------
+
+Consider a library providing a simple User class::
+
+ #<crypt_user.py>
+
+ class User(object):
+ def __init__(self, username, password):
+ self.username, self.password = username, password
+
+ #</crypt_user.py>
+
+The User objects are stored in a database as they are.
+For security purpose, in a second version of the library it is
+decided to crypt the password, so that only crypted passwords
+are stored in the database. With properties, it is possible to
+implement this functionality without changing the source code for
+the User class::
+
+ #<crypt_user.py>
+
+ from crypt import crypt
+
+ def cryptedAttribute(seed="x"):
+ def get(self):
+ return getattr(self, "_pw", None)
+ def set(self, value):
+ self._pw = crypt(value, seed)
+ return property(get, set)
+
+ User.password = cryptedAttribute()
+
+#</crypt_user.py>
+
+>>> from crypt_user import User
+>>> u = User("michele", "secret")
+>>> print u.password
+xxZREZpkHZpkI
+
+Notice the property factory approach used here.
+
+Low-level delegation via __getattribute__
+------------------------------------------------------------------
+
+Attribute access is managed by the__getattribute__ special method::
+
+ #<tracedaccess.py>
+
+ class TracedAccess(object):
+ def __getattribute__(self, name):
+ print "Accessing %s" % name
+ return object.__getattribute__(self, name)
+
+
+ class C(TracedAccess):
+ s = staticmethod(lambda : 'staticmethod')
+ c = classmethod(lambda cls: 'classmethod')
+ m = lambda self: 'method'
+ a = "hello"
+
+ #</tracedaccess.py>
+
+>>> from tracedaccess import C
+>>> c = C()
+>>> print c.s()
+Accessing s
+staticmethod
+>>> print c.c()
+Accessing c
+classmethod
+>>> print c.m()
+Accessing m
+method
+>>> print c.a
+Accessing a
+hello
+>>> print c.__init__ #doctest: +ELLIPSIS
+Accessing __init__
+<method-wrapper object at 0x...>
+>>> try: c.x
+... except AttributeError, e: print e
+...
+Accessing x
+'C' object has no attribute 'x'
+
+>>> c.y = 'y'
+>>> c.y
+Accessing y
+'y'
+
+You are probably familiar with ``__getattr__`` which is similar
+to ``__getattribute__``, but it is called *only for missing attributes*.
+
+Traditional delegation via __getattr__
+--------------------------------------------------------
+
+Realistic use case in "object publishing"::
+
+ #<webapp.py>
+
+ class WebApplication(object):
+ def __getattr__(self, name):
+ return name.capitalize()
+
+
+ app = WebApplication()
+
+ assert app.page1 == 'Page1'
+ assert app.page2 == 'Page2'
+
+ #</webapp.py>
+
+Here is another use case in HTML generation::
+
+ #<XMLtag.py>
+
+ def makeattr(dict_or_list_of_pairs):
+ dic = dict(dict_or_list_of_pairs)
+ return " ".join('%s="%s"' % (k, dic[k]) for k in dic) # simplistic
+
+ class XMLTag(object):
+ def __getattr__(self, name):
+ def tag(value, **attr):
+ """value can be a string or a sequence of strings."""
+ if hasattr(value, "__iter__"): # is iterable
+ value = " ".join(value)
+ return "<%s %s>%s</%s>" % (name, makeattr(attr), value, name)
+ return tag
+
+ class XMLShortTag(object):
+ def __getattr__(self, name):
+ def tag(**attr):
+ return "<%s %s />" % (name, makeattr(attr))
+ return tag
+
+ tag = XMLTag()
+ tg = XMLShortTag()
+
+ #</XMLtag.py>
+
+>>> from XMLtag import tag, tg
+>>> print tag.a("example.com", href="http://www.example.com")
+<a href="http://www.example.com">example.com</a>
+>>> print tg.br(**{'class':"br_style"})
+<br class="br_style" />
+
+Keyword dictionaries with __getattr__/__setattr__
+---------------------------------------------------
+::
+
+ #<kwdict.py>
+
+ class kwdict(dict): # or UserDict, to make it to work with Zope
+ """A typing shortcut used in place of a keyword dictionary."""
+ def __getattr__(self, name):
+ return self[name]
+ def __setattr__(self, name, value):
+ self[name] = value
+
+ #</kwdict.py>
+
+And now for a completely different solution::
+
+ #<dictwrapper.py>
+
+ class DictWrapper(object):
+ def __init__(self, **kw):
+ self.__dict__.update(kw)
+
+ #</dictwrapper.py>
+
+
+Delegation to special methods caveat
+--------------------------------------
+
+>>> class ListWrapper(object):
+... def __init__(self, ls):
+... self._list = ls
+... def __getattr__(self, name):
+... if name == "__getitem__": # special method
+... return self._list.__getitem__
+... elif name == "reverse": # regular method
+... return self._list.reverse
+... else:
+... raise AttributeError("%r is not defined" % name)
+...
+>>> lw = ListWrapper([0,1,2])
+>>> print lw.x
+Traceback (most recent call last):
+ ...
+AttributeError: 'x' is not defined
+
+>>> lw.reverse()
+>>> print lw.__getitem__(0)
+2
+>>> print lw.__getitem__(1)
+1
+>>> print lw.__getitem__(2)
+0
+>>> print lw[0]
+Traceback (most recent call last):
+ ...
+TypeError: unindexable object
+
+
+Part II: Inheritance
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+The major changes in inheritance from Python 2.1 to 2.2+ are:
+
+1. you can subclass built-in types (as a consequence the constructor__new__
+ has been exposed to the user, to help subclassing immutable types);
+2. the Method Resolution Order (MRO) has changed;
+3. now Python allows *cooperative method calls*, i.e. we have *super*.
+
+Why you need to know about MI even if you do not use it
+-----------------------------------------------------------
+
+In principle, the last two changes are relevant only if you use multiple
+inheritance. If you use single inheritance only, you don't need ``super``:
+you can just name the superclass.
+However, somebody else may want to use your class in a MI hierarchy,
+and you would make her life difficult if you don't use ``super``.
+
+My SI hierarchy::
+
+ #<why_super.py>
+
+ class Base(object):
+ def __init__(self):
+ print "B.__init__"
+
+ class MyClass(Base):
+ "I do not cooperate with others"
+ def __init__(self):
+ print "MyClass.__init__"
+ Base.__init__(self) #instead of super(MyClass, self).__init__()
+
+ #</why_super.py>
+
+Her MI hierarchy::
+
+ #<why_super.py>
+
+ class Mixin(Base):
+ "I am cooperative with others"
+ def __init__(self):
+ print "Mixin.__init__"
+ super(Mixin, self).__init__()
+
+ class HerClass(MyClass, Mixin):
+ "I am supposed to be cooperative too"
+ def __init__(self):
+ print "HerClass.__init__"
+ super(HerClass, self).__init__()
+
+ #</why_super.py>
+
+>>> from why_super import HerClass
+>>> h = HerClass() # Mixin.__init__ is not called!
+HerClass.__init__
+MyClass.__init__
+B.__init__
+
+ ::
+
+ 4 object
+ |
+ 3 Base
+ / \
+ 1 MyClass 2 Mixin
+ \ /
+ 0 HerClass
+
+>>> [ancestor.__name__ for ancestor in HerClass.mro()]
+['HerClass', 'MyClass', 'Mixin', 'Base', 'object']
+
+In order to be polite versus your future users, you should use ``super``
+always. This adds a cognitive burden even for people not using MI :-(
+
+Notice that there is no good comprehensive reference on ``super`` (yet)
+Your best bet is still http://www.python.org/2.2.3/descrintro.html#cooperation
+
+The MRO instead is explained here: http://www.python.org/2.3/mro.html
+
+Notice that I DO NOT recommand Multiple Inheritance.
+
+More often than not you are better off using composition/delegation/wrapping,
+etc.
+
+See Zope 2 -> Zope 3 experience.
+
+A few details about ``super`` (not the whole truth)
+------------------------------------------------------------------------------
+
+>>> class B(object):
+... def __init__(self): print "B.__init__"
+...
+>>> class C(B):
+... def __init__(self): print "C.__init__"
+...
+>>> c = C()
+C.__init__
+
+``super(cls, instance)``, where ``instance`` is an instance of ``cls`` or of
+a subclass of ``cls``, retrieves the right method in the MRO:
+
+>>> super(C, c).__init__ #doctest: +ELLIPSIS
+<bound method C.__init__ of <__main__.C object at 0x...>>
+
+>>> super(C, c).__init__.im_func is B.__init__.im_func
+True
+
+>>> super(C, c).__init__()
+B.__init__
+
+``super(cls, subclass)`` works for unbound methods:
+
+>>> super(C, C).__init__
+<unbound method C.__init__>
+
+>>> super(C, C).__init__.im_func is B.__init__.im_func
+True
+>>> super(C, C).__init__(c)
+B.__init__
+
+``super(cls, subclass)`` is also necessary for classmethods and staticmethods.
+Properties and custom descriptorsw works too::
+
+ #<super_ex.py>
+
+ from descriptor import AttributeDescriptor
+
+ class B(object):
+ @staticmethod
+ def sm(): return "staticmethod"
+
+ @classmethod
+ def cm(cls): return cls.__name__
+
+ p = property()
+ a = AttributeDescriptor()
+
+ class C(B): pass
+
+ #</super_ex.py>
+
+>>> from super_ex import C
+
+Staticmethod usage:
+
+>>> super(C, C).sm #doctest: +ELLIPSIS
+<function sm at 0x...>
+>>> super(C, C).sm()
+'staticmethod'
+
+Classmethod usage:
+
+>>> super(C, C()).cm
+<bound method type.cm of <class 'super_ex.C'>>
+>>> super(C, C).cm() # C is automatically passed
+'C'
+
+Property usage:
+
+>>> print super(C, C).p #doctest: +ELLIPSIS
+<property object at 0x...>
+>>> super(C, C).a #doctest: +ELLIPSIS
+Getting <descriptor.AttributeDescriptor object at 0x...> from <class 'super_ex.C'>
+
+``super`` does not work with old-style classes, however you can use the
+following trick::
+
+ #<super_old_new.py>
+ class O:
+ def __init__(self):
+ print "O.__init__"
+
+ class N(O, object):
+ def __init__(self):
+ print "N.__init__"
+ super(N, self).__init__()
+
+ #</super_old_new.py>
+
+>>> from super_old_new import N
+>>> new = N()
+N.__init__
+O.__init__
+
+There are dozens of tricky points concerning ``super``, be warned!
+
+Subclassing built-in types; __new__ vs. __init__
+-----------------------------------------------------
+
+::
+
+ #<point.py>
+
+ class NotWorkingPoint(tuple):
+ def __init__(self, x, y):
+ super(NotWorkingPoint, self).__init__((x,y))
+ self.x, self.y = x, y
+
+ #</point.py>
+
+>>> from point import NotWorkingPoint
+>>> p = NotWorkingPoint(2,3)
+Traceback (most recent call last):
+ ...
+TypeError: tuple() takes at most 1 argument (2 given)
+
+::
+
+ #<point.py>
+
+ class Point(tuple):
+ def __new__(cls, x, y):
+ return super(Point, cls).__new__(cls, (x,y))
+ def __init__(self, x, y):
+ super(Point, self).__init__((x, y))
+ self.x, self.y = x, y
+
+ #</point.py>
+
+Notice that__new__ is a staticmethod, not a classmethod, so one needs
+to pass the class explicitely.
+
+>>> from point import Point
+>>> p = Point(2,3)
+>>> print p, p.x, p.y
+(2, 3) 2 3
+
+Be careful when using __new__ with mutable types
+------------------------------------------------
+
+>>> class ListWithDefault(list):
+... def __new__(cls):
+... return super(ListWithDefault, cls).__new__(cls, ["hello"])
+...
+>>> print ListWithDefault() # beware! NOT ["hello"]!
+[]
+
+Reason: lists are re-initialized to empty lists in list.__init__!
+
+Instead
+
+>>> class ListWithDefault(list):
+... def __init__(self):
+... super(ListWithDefault, self).__init__(["hello"])
+...
+>>> print ListWithDefault() # works!
+['hello']
+
+
+
+Lecture 3: Magic (i.e. decorators and metaclasses)
+================================================================
+
+Part I: decorators
++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+Decorators are just sugar: their functionality was already in the language
+
+>>> def s(): pass
+>>> s = staticmethod(s)
+
+>>> @staticmethod
+... def s(): pass
+...
+
+However sugar *does* matter.
+
+A typical decorator: traced
+-----------------------------
+::
+
+ #<traced.py>
+
+ def traced(func):
+ def tracedfunc(*args, **kw):
+ print "calling %s.%s" % (func.__module__, func.__name__)
+ return func(*args, **kw)
+ tracedfunc.__name__ = func.__name__
+ return tracedfunc
+
+ @traced
+ def f(): pass
+
+ #</traced.py>
+
+>>> from traced import f
+>>> f()
+calling traced.f
+
+A decorator factory: Timed
+------------------------------------------
+
+::
+
+ #<timed.py>
+
+ import sys, time
+
+ class Timed(object):
+ """Decorator factory: each decorator object wraps a function and
+ executes it many times (default 100 times).
+ The average time spent in one iteration, expressed in milliseconds,
+ is stored in the attributes wrappedfunc.time and wrappedfunc.clocktime,
+ and displayed into a log file which defaults to stdout.
+ """
+ def __init__(self, repeat=100, logfile=sys.stdout):
+ self.repeat = repeat
+ self.logfile = logfile
+ def __call__(self, func):
+ def wrappedfunc(*args, **kw):
+ fullname = "%s.%s ..." % (func.__module__, func.func_name)
+ print >> self.logfile, 'Executing %s' % fullname.ljust(30),
+ time1 = time.time()
+ clocktime1 = time.clock()
+ for i in xrange(self.repeat):
+ res = func(*args,**kw) # executes func self.repeat times
+ time2 = time.time()
+ clocktime2 = time.clock()
+ wrappedfunc.time = 1000*(time2-time1)/self.repeat
+ wrappedfunc.clocktime = 1000*(clocktime2 - clocktime1)/self.repeat
+ print >> self.logfile, \
+ 'Real time: %s ms;' % self.rounding(wrappedfunc.time),
+ print >> self.logfile, \
+ 'Clock time: %s ms' % self.rounding(wrappedfunc.clocktime)
+ return res
+ wrappedfunc.func_name = func.func_name
+ wrappedfunc.__module__ = func.__module__
+ return wrappedfunc
+ @staticmethod
+ def rounding(float_):
+ "Three digits rounding for small numbers, 1 digit rounding otherwise."
+ if float_ < 10.:
+ return "%5.3f" % float_
+ else:
+ return "%5.1f" % float_
+
+ #</timed.py>
+
+>>> from timed import Timed
+>>> from random import sample
+>>> example_ls = sample(xrange(1000000), 1000)
+>>> @Timed()
+... def list_sort(ls):
+... ls.sort()
+...
+>>> list_sort(example_ls) #doctest: +ELLIPSIS
+Executing __main__.list_sort ... Real time: 0... ms; Clock time: 0... ms
+
+
+A powerful decorator pattern
+--------------------------------
+::
+
+ #<traced_function2.py>
+
+ from decorators import decorator
+
+ def trace(f, *args, **kw):
+ print "calling %s with args %s, %s" % (f.func_name, args, kw)
+ return f(*args, **kw)
+
+ traced_function = decorator(trace)
+
+ @traced_function
+ def f1(x):
+ pass
+
+ @traced_function
+ def f2(x, y):
+ pass
+
+ #</traced_function2.py>
+
+>>> from traced_function2 import traced_function, f1, f2
+>>> f1(1)
+calling f1 with args (1,), {}
+>>> f2(1,2)
+calling f2 with args (1, 2), {}
+
+works with pydoc::
+
+ $ pydoc2.4 traced_function2.f2
+ Help on function f1 in traced_function2:
+
+ traced_function2.f1 = f1(x)
+
+ $ pydoc2.4 traced_function2.f2
+ Help on function f2 in traced_function2:
+
+ traced_function2.f2 = f2(x, y)
+
+Here is the source code::
+
+ #<decorators.py>
+
+ import inspect, itertools
+
+ def getinfo(func):
+ """Return an info dictionary containing:
+ - name (the name of the function : str)
+ - argnames (the names of the arguments : list)
+ - defarg (the values of the default arguments : list)
+ - fullsign (the full signature : str)
+ - shortsign (the short signature : str)
+ - arg0 ... argn (shortcuts for the names of the arguments)
+
+ >> def f(self, x=1, y=2, *args, **kw): pass
+
+ >> info = getinfo(f)
+
+ >> info["name"]
+ 'f'
+ >> info["argnames"]
+ ['self', 'x', 'y', 'args', 'kw']
+
+ >> info["defarg"]
+ (1, 2)
+
+ >> info["shortsign"]
+ 'self, x, y, *args, **kw'
+
+ >> info["fullsign"]
+ 'self, x=defarg[0], y=defarg[1], *args, **kw'
+
+ >> info["arg0"], info["arg1"], info["arg2"], info["arg3"], info["arg4"]
+ ('self', 'x', 'y', 'args', 'kw')
+ """
+ assert inspect.ismethod(func) or inspect.isfunction(func)
+ regargs, varargs, varkwargs, defaults = inspect.getargspec(func)
+ argnames = list(regargs)
+ if varargs: argnames.append(varargs)
+ if varkwargs: argnames.append(varkwargs)
+ counter = itertools.count()
+ fullsign = inspect.formatargspec(
+ regargs, varargs, varkwargs, defaults,
+ formatvalue=lambda value: "=defarg[%i]" % counter.next())[1:-1]
+ shortsign = inspect.formatargspec(
+ regargs, varargs, varkwargs, defaults,
+ formatvalue=lambda value: "")[1:-1]
+ dic = dict(("arg%s" % n, name) for n, name in enumerate(argnames))
+ dic.update(name=func.__name__, argnames=argnames, shortsign=shortsign,
+ fullsign = fullsign, defarg = func.func_defaults or ())
+ return dic
+
+ def _contains_reserved_names(dic): # helper
+ return "_call_" in dic or "_func_" in dic
+
+ def _decorate(func, caller):
+ """Takes a function and a caller and returns the function
+ decorated with that caller. The decorated function is obtained
+ by evaluating a lambda function with the correct signature.
+ """
+ infodict = getinfo(func)
+ assert not _contains_reserved_names(infodict["argnames"]), \
+ "You cannot use _call_ or _func_ as argument names!"
+ execdict=dict(_func_=func, _call_=caller, defarg=func.func_defaults or ())
+ if func.__name__ == "<lambda>":
+ lambda_src = "lambda %(fullsign)s: _call_(_func_, %(shortsign)s)" \
+ % infodict
+ dec_func = eval(lambda_src, execdict)
+ else:
+ func_src = """def %(name)s(%(fullsign)s):
+ return _call_(_func_, %(shortsign)s)""" % infodict
+ exec func_src in execdict
+ dec_func = execdict[func.__name__]
+ dec_func.__doc__ = func.__doc__
+ dec_func.__dict__ = func.__dict__
+ return dec_func
+
+ class decorator(object):
+ """General purpose decorator factory: takes a caller function as
+ input and returns a decorator. A caller function is any function like this::
+
+ def caller(func, *args, **kw):
+ # do something
+ return func(*args, **kw)
+
+ Here is an example of usage:
+
+ >> @decorator
+ .. def chatty(f, *args, **kw):
+ .. print "Calling %r" % f.__name__
+ .. return f(*args, **kw)
+
+ >> @chatty
+ .. def f(): pass
+ ..
+ >> f()
+ Calling 'f'
+ """
+ def __init__(self, caller):
+ self.caller = caller
+ def __call__(self, func):
+ return _decorate(func, self.caller)
+
+
+ #</decorators.py>
+
+The possibilities of this pattern are endless.
+
+A deferred decorator
+-----------------------
+
+You want to execute a procedure only after a certain time delay (for instance
+for use within an asyncronous Web framework)::
+
+
+ #<deferred.py>
+ "Deferring the execution of a procedure (function returning None)"
+
+ import threading
+ from decorators import decorator
+
+ def deferred(nsec):
+ def call_later(func, *args, **kw):
+ return threading.Timer(nsec, func, args, kw).start()
+ return decorator(call_later)
+
+ @deferred(2)
+ def hello():
+ print "hello"
+
+ if __name__ == "__main__":
+ hello()
+ print "Calling hello() ..."
+
+
+ #</deferred.py>
+
+ $ python deferred.py
+
+Show an example of an experimental decorator based web framework
+(doctester_frontend).
+
+Part II: metaclasses
+++++++++++++++++++++++++++++++++++++++++++++++++++
+
+Metaclasses are there! Consider this example from a recent post on c.l.py::
+
+ #<BaseClass.py>
+
+ class BaseClass(object):
+ "Do something"
+
+ #</BaseClass.py>
+
+>>> import BaseClass # instead of 'from BaseClass import BaseClass'
+>>> class C(BaseClass): pass
+...
+Traceback (most recent call last):
+ ...
+TypeError: Error when calling the metaclass bases
+ module.__init__() takes at most 2 arguments (3 given)
+
+The reason for the error is that class ``C(BaseClass): pass`` is
+actually calling the ``type`` metaclass with three arguments::
+
+ C = type("C", (BaseClass,), {})
+
+``type.__new__`` tries to use ``type(BaseClass)`` as metaclass,
+but since BaseClass here is a module, and ``ModuleType`` is not
+a metaclass, it cannot work. The error message reflects a conflict with
+the signature of ModuleType which requires two parameters and not three.
+
+So even if you don't use them, you may want to know they exist.
+
+Rejuvenating old-style classes
+--------------------------------------------
+
+>>> class Old: pass
+>>> print type(Old)
+<type 'classobj'>
+
+>>> __metaclass__ = type # to rejuvenate class
+>>> class NotOld: pass
+...
+>>> print NotOld.__class__
+<type 'type'>
+
+A typical metaclass example: MetaTracer
+----------------------------------------
+
+::
+
+ #<metatracer.py>
+
+ import inspect
+ from decorators import decorator
+
+ @decorator
+ def traced(meth, *args, **kw):
+ cls = meth.__cls__
+ modname = meth.__module__ or cls.__module__
+ print "calling %s.%s.%s" % (modname, cls.__name__, meth.__name__)
+ return meth(*args, **kw)
+
+ class MetaTracer(type):
+ def __init__(cls, name, bases, dic):
+ super(MetaTracer, cls).__init__(name, bases, dic)
+ for k, v in dic.iteritems():
+ if inspect.isfunction(v):
+ v.__cls__ = cls # so we know in which class v was defined
+ setattr(cls, k, traced(v))
+
+ #</metatracer.py>
+
+Usage: exploring classes in the standard library
+
+::
+
+ #<dictmixin.py>
+
+ from metatracer import MetaTracer
+ from UserDict import DictMixin
+
+ class TracedDM(DictMixin, object):
+ __metaclass__ = MetaTracer
+ def __getitem__(self, item):
+ return item
+ def keys(self):
+ return [1,2,3]
+
+ #</dictmixin.py>
+
+>>> from dictmixin import TracedDM
+>>> print TracedDM()
+calling dictmixin.TracedDM.keys
+calling dictmixin.TracedDM.__getitem__
+calling dictmixin.TracedDM.__getitem__
+calling dictmixin.TracedDM.__getitem__
+{1: 1, 2: 2, 3: 3}
+
+Real life example: check overriding
+-------------------------------------
+::
+
+ #<check_overriding.py>
+
+ class Base(object):
+ a = 0
+
+ class CheckOverriding(type):
+ "Prints a message if we are overriding a name."
+ def __new__(mcl, name, bases, dic):
+ for name, val in dic.iteritems():
+ if name.startswith("__") and name.endswith("__"):
+ continue # ignore special names
+ a_base_has_name = True in (hasattr(base, name) for base in bases)
+ if a_base_has_name:
+ print "AlreadyDefinedNameWarning: " + name
+ return super(CheckOverriding, mcl).__new__(mcl, name, bases, dic)
+
+ class MyClass(Base):
+ __metaclass__ = CheckOverriding
+ a = 1
+
+ class ChildClass(MyClass):
+ a = 2
+
+#</check_overriding.py>
+
+>>> import check_overriding
+AlreadyDefinedNameWarning: a
+AlreadyDefinedNameWarning: a
+
+LogFile
+---------------------------------------------
+::
+
+ #<logfile.py>
+
+ import subprocess
+
+ def memoize(func):
+ memoize_dic = {}
+ def wrapped_func(*args):
+ if args in memoize_dic:
+ return memoize_dic[args]
+ else:
+ result = func(*args)
+ memoize_dic[args] = result
+ return result
+ wrapped_func.__name__ = func.__name__
+ wrapped_func.__doc__ = func.__doc__
+ wrapped_func.__dict__ = func.__dict__
+ return wrapped_func
+
+ class Memoize(type): # Singleton is a special case of Memoize
+ @memoize
+ def __call__(cls, *args):
+ return super(Memoize, cls).__call__(*args)
+
+ class LogFile(file):
+ """Open a file for append."""
+ __metaclass__ = Memoize
+ def __init__(self, name = "/tmp/err.log"):
+ self.viewer_cmd = 'xterm -e less'.split()
+ super(LogFile, self).__init__(name, "a")
+
+ def display(self, *ls):
+ "Use 'less' to display the log file in a separate xterm."
+ print >> self, "\n".join(map(str, ls)); self.flush()
+ subprocess.call(self.viewer_cmd + [self.name])
+
+ def reset(self):
+ "Erase the log file."
+ print >> file(self.name, "w")
+
+ if __name__ == "__main__": # test
+ print >> LogFile(), "hello"
+ print >> LogFile(), "world"
+ LogFile().display()
+
+ #</logfile.py>
+
+ $ python logfile.py
+
+Cooperative hierarchies
+------------------------------
+
+::
+
+ #<cooperative_init.py>
+
+ """Given a hierarchy, makes __init__ cooperative.
+ The only change needed is to add a line
+
+ __metaclass__ = CooperativeInit
+
+ to the base class of your hierarchy."""
+
+ from decorators import decorator
+
+ class CooperativeInit(type):
+ def __init__(cls, name, bases, dic):
+
+ @decorator
+ def make_cooperative(__init__, self, *args, **kw):
+ super(cls, self).__init__(*args, **kw)
+ __init__(self, *args, **kw)
+
+ __init__ = dic.get("__init__")
+ if __init__:
+ cls.__init__ = make_cooperative(__init__)
+
+ class Base:
+ __metaclass__ = CooperativeInit
+ def __init__(self):
+ print "B.__init__"
+
+ class C1(Base):
+ def __init__(self):
+ print "C1.__init__"
+
+ class C2(Base):
+ def __init__(self):
+ print "C2.__init__"
+
+ class D(C1, C2):
+ def __init__(self):
+ print "D.__init__"
+
+ #</cooperative_init.py>
+
+>>> from cooperative_init import D
+>>> d = D()
+B.__init__
+C2.__init__
+C1.__init__
+D.__init__
+
+Metaclass-enhanced modules
+----------------------------------------------------------------
+
+::
+
+ #<import_with_metaclass.py>
+ """
+ ``import_with_metaclass(metaclass, modulepath)`` generates
+ a new module from and old module, by enhancing all of its classes.
+ This is not perfect, but it should give you a start."""
+
+ import os, sys, inspect, types
+
+ def import_with_metaclass(metaclass, modulepath):
+ modname = os.path.basename(modulepath)[:-3] # simplistic
+ mod = types.ModuleType(modname)
+ locs = dict(
+ __module__ = modname,
+ __metaclass__ = metaclass,
+ object = metaclass("object", (), {}))
+ execfile(modulepath, locs)
+ for k, v in locs.iteritems():
+ if inspect.isclass(v): # otherwise it would be "__builtin__"
+ v.__module__ = "__dynamic__"
+ setattr(mod, k, v)
+ return mod
+
+#</import_with_metaclass.py>
+
+>>> from import_with_metaclass import import_with_metaclass
+>>> from metatracer import MetaTracer
+>>> traced_optparse = import_with_metaclass(MetaTracer,
+... "/usr/lib/python2.4/optparse.py")
+>>> op = traced_optparse.OptionParser()
+calling __dynamic__.OptionParser.__init__
+calling __dynamic__.OptionContainer.__init__
+calling __dynamic__.OptionParser._create_option_list
+calling __dynamic__.OptionContainer._create_option_mappings
+calling __dynamic__.OptionContainer.set_conflict_handler
+calling __dynamic__.OptionContainer.set_description
+calling __dynamic__.OptionParser.set_usage
+calling __dynamic__.IndentedHelpFormatter.__init__
+calling __dynamic__.HelpFormatter.__init__
+calling __dynamic__.HelpFormatter.set_parser
+calling __dynamic__.OptionParser._populate_option_list
+calling __dynamic__.OptionParser._add_help_option
+calling __dynamic__.OptionContainer.add_option
+calling __dynamic__.Option.__init__
+calling __dynamic__.Option._check_opt_strings
+calling __dynamic__.Option._set_opt_strings
+calling __dynamic__.Option._set_attrs
+calling __dynamic__.OptionContainer._check_conflict
+calling __dynamic__.OptionParser._init_parsing_state
+
+traced_optparse is a dynamically generated module not leaving in the
+file system.
+
+Magic properties
+--------------------
+::
+
+ #<magicprop.py>
+
+ class MagicProperties(type):
+ def __init__(cls, name, bases, dic):
+ prop_names = set(name[3:] for name in dic
+ if name.startswith("get")
+ or name.startswith("set"))
+ for name in prop_names:
+ getter = getattr(cls, "get" + name, None)
+ setter = getattr(cls, "set" + name, None)
+ setattr(cls, name, property(getter, setter))
+
+ class Base(object):
+ __metaclass__ = MagicProperties
+ def getx(self):
+ return self._x
+ def setx(self, value):
+ self._x = value
+
+ class Child(Base):
+ def getx(self):
+ print "getting x"
+ return super(Child, self).getx()
+ def setx(self, value):
+ print "setting x"
+ super(Child, self).setx(value)
+
+ #</magicprop.py>
+
+>>> from magicprop import Child
+>>> c = Child()
+>>> c.x = 1
+setting x
+>>> print c.x
+getting x
+1
+
+Hack: evil properties
+------------------------------------
+
+::
+
+ #<evilprop.py>
+
+ def convert2property(name, bases, d):
+ return property(d.get('get'), d.get('set'),
+ d.get('del'),d.get('__doc__'))
+
+ class C(object):
+ class x:
+ """An evil test property"""
+ __metaclass__ = convert2property
+ def get(self):
+ print 'Getting %s' % self._x
+ return self._x
+ def set(self, value):
+ self._x = value
+ print 'Setting to', value
+
+ #</evilprop.py>
+
+>>> from evilprop import C
+>>> c = C()
+>>> c.x = 5
+Setting to 5
+>>> c.x
+Getting 5
+5
+>>> print C.x.__doc__
+An evil test property
+
+Why I suggest *not* to use metaclasses in production code
+---------------------------------------------------------
+
+ + there are very few good use case for metaclasses in production code
+ (i.e. 99% of time you don't need them)
+
+ + they put a cognitive burden on the developer;
+
+ + a design without metaclasses is less magic and likely more robust;
+
+ + a design with metaclasses makes it difficult to use other metaclasses
+ for debugging.
+
+As far as I know, string.Template is the only metaclass-enhanced class
+in the standard library; the metaclass is used to give the possibility to
+change the defaults::
+
+ delimiter = '$'
+ idpattern = r'[_a-z][_a-z0-9]*'
+
+in subclasses of Template.
+
+>>> from string import Template
+>>> from metatracer import MetaTracer
+>>> class TracedTemplate(Template):
+... __metaclass__ = MetaTracer
+...
+Traceback (most recent call last):
+ ...
+TypeError: Error when calling the metaclass bases
+ metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
+
+Solution: use a consistent metaclass
+
+>>> class GoodMeta(MetaTracer, type(Template)): pass
+...
+>>> class TracedTemplate(Template):
+... __metaclass__ = GoodMeta
+
+
+Is there an automatic way of solving the conflict?
+---------------------------------------------------------------------
+
+Yes, but you really need to be a metaclass wizard.
+
+http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/204197
+
+>>> from noconflict import classmaker
+>>> class TracedTemplate(Template):
+... __metaclass__ = classmaker((MetaTracer,))
+>>> print type(TracedTemplate)
+<class 'noconflict._MetaTracer_TemplateMetaclass'>
+
+::
+
+ #<noconflict.py>
+
+ import inspect, types, __builtin__
+ from skip_redundant import skip_redundant
+
+ memoized_metaclasses_map = {}
+
+ # utility function
+ def remove_redundant(metaclasses):
+ skipset = set([types.ClassType])
+ for meta in metaclasses: # determines the metaclasses to be skipped
+ skipset.update(inspect.getmro(meta)[1:])
+ return tuple(skip_redundant(metaclasses, skipset))
+
+ ##################################################################
+ ## now the core of the module: two mutually recursive functions ##
+ ##################################################################
+
+ def get_noconflict_metaclass(bases, left_metas, right_metas):
+ """Not intended to be used outside of this module, unless you know
+ what you are doing."""
+ # make tuple of needed metaclasses in specified priority order
+ metas = left_metas + tuple(map(type, bases)) + right_metas
+ needed_metas = remove_redundant(metas)
+
+ # return existing confict-solving meta, if any
+ if needed_metas in memoized_metaclasses_map:
+ return memoized_metaclasses_map[needed_metas]
+ # nope: compute, memoize and return needed conflict-solving meta
+ elif not needed_metas: # wee, a trivial case, happy us
+ meta = type
+ elif len(needed_metas) == 1: # another trivial case
+ meta = needed_metas[0]
+ # check for recursion, can happen i.e. for Zope ExtensionClasses
+ elif needed_metas == bases:
+ raise TypeError("Incompatible root metatypes", needed_metas)
+ else: # gotta work ...
+ metaname = '_' + ''.join([m.__name__ for m in needed_metas])
+ meta = classmaker()(metaname, needed_metas, {})
+ memoized_metaclasses_map[needed_metas] = meta
+ return meta
+
+ def classmaker(left_metas=(), right_metas=()):
+ def make_class(name, bases, adict):
+ metaclass = get_noconflict_metaclass(bases, left_metas, right_metas)
+ return metaclass(name, bases, adict)
+ return make_class
+
+ #################################################################
+ ## and now a conflict-safe replacement for 'type' ##
+ #################################################################
+
+ __type__=__builtin__.type # the aboriginal 'type'
+ # left available in case you decide to rebind __builtin__.type
+
+ class safetype(__type__):
+ # this is REALLY DEEP MAGIC
+ """Overrides the ``__new__`` method of the ``type`` metaclass, making the
+ generation of classes conflict-proof."""
+ def __new__(mcl, *args):
+ nargs = len(args)
+ if nargs == 1: # works as __builtin__.type
+ return __type__(args[0])
+ elif nargs == 3: # creates the class using the appropriate metaclass
+ n, b, d = args # name, bases and dictionary
+ meta = get_noconflict_metaclass(b, (mcl,), ())
+ if meta is mcl: # meta is trivial, dispatch to the default __new__
+ return super(safetype, mcl).__new__(mcl, n, b, d)
+ else: # non-trivial metaclass, dispatch to the right __new__
+ # (it will take a second round) # print mcl, meta
+ return super(mcl, meta).__new__(meta, n, b, d)
+ else:
+ raise TypeError('%s() takes 1 or 3 arguments' % mcl.__name__)
+
+ #</noconflict.py>
diff --git a/pypers/oxford/all.tex b/pypers/oxford/all.tex new file mode 100755 index 0000000..c6be548 --- /dev/null +++ b/pypers/oxford/all.tex @@ -0,0 +1,2395 @@ +\documentclass[10pt,a4paper,english]{article} +\usepackage{babel} +\usepackage{ae} +\usepackage{aeguill} +\usepackage{shortvrb} +\usepackage[latin1]{inputenc} +\usepackage{tabularx} +\usepackage{longtable} +\setlength{\extrarowheight}{2pt} +\usepackage{amsmath} +\usepackage{graphicx} +\usepackage{color} +\usepackage{multirow} +\usepackage{ifthen} +\usepackage[colorlinks=true,linkcolor=blue,urlcolor=blue]{hyperref} +\usepackage[DIV12]{typearea} +%% generator Docutils: http://docutils.sourceforge.net/ +\newlength{\admonitionwidth} +\setlength{\admonitionwidth}{0.9\textwidth} +\newlength{\docinfowidth} +\setlength{\docinfowidth}{0.9\textwidth} +\newlength{\locallinewidth} +\newcommand{\optionlistlabel}[1]{\bf #1 \hfill} +\newenvironment{optionlist}[1] +{\begin{list}{} + {\setlength{\labelwidth}{#1} + \setlength{\rightmargin}{1cm} + \setlength{\leftmargin}{\rightmargin} + \addtolength{\leftmargin}{\labelwidth} + \addtolength{\leftmargin}{\labelsep} + \renewcommand{\makelabel}{\optionlistlabel}} +}{\end{list}} +\newlength{\lineblockindentation} +\setlength{\lineblockindentation}{2.5em} +\newenvironment{lineblock}[1] +{\begin{list}{} + {\setlength{\partopsep}{\parskip} + \addtolength{\partopsep}{\baselineskip} + \topsep0pt\itemsep0.15\baselineskip\parsep0pt + \leftmargin#1} + \raggedright} +{\end{list}} +% begin: floats for footnotes tweaking. +\setlength{\floatsep}{0.5em} +\setlength{\textfloatsep}{\fill} +\addtolength{\textfloatsep}{3em} +\renewcommand{\textfraction}{0.5} +\renewcommand{\topfraction}{0.5} +\renewcommand{\bottomfraction}{0.5} +\setcounter{totalnumber}{50} +\setcounter{topnumber}{50} +\setcounter{bottomnumber}{50} +% end floats for footnotes +% some commands, that could be overwritten in the style file. +\newcommand{\rubric}[1]{\subsection*{~\hfill {\it #1} \hfill ~}} +\newcommand{\titlereference}[1]{\textsl{#1}} +% end of "some commands" +\input{style.tex} +\title{Lectures on Advanced Python Programming} +\author{} +\date{} +\hypersetup{ +pdftitle={Lectures on Advanced Python Programming} +} +\raggedbottom +\begin{document} +\maketitle + + +\setlength{\locallinewidth}{\linewidth} + +\includegraphics{accu2005.ps} +\begin{quote} +\begin{description} +\item [Author:] +Michele Simionato + + +\item [Given:] +19 April 2005 + + +\item [Revised:] +7 September 2005 + + +\end{description} +\end{quote} +\hypertarget{contents}{} +\pdfbookmark[0]{Contents}{contents} +\subsubsection*{~\hfill Contents\hfill ~} +\begin{list}{}{} +\item {} \href{\#lecture-1-loops-i-e-iterators-generators}{Lecture 1: Loops (i.e. iterators {\&} generators)} +\begin{list}{}{} +\item {} \href{\#part-i-iterators}{Part I: iterators} +\begin{list}{}{} +\item {} \href{\#iterators-are-everywhere}{Iterators are everywhere} + +\item {} \href{\#iterables-and-iterators}{Iterables and iterators} + +\item {} \href{\#simpler-way-to-get-an-iterator}{Simpler way to get an iterator} + +\item {} \href{\#sentinel-syntax-iter-callable-sentinel}{Sentinel syntax iter(callable, sentinel)} + +\item {} \href{\#second-simpler-way-to-get-an-iterator-generator-expressions}{Second simpler way to get an iterator: generator-expressions} + +\item {} \href{\#iteration-caveats}{Iteration caveats} + +\end{list} + +\item {} \href{\#part-ii-generators}{Part II: generators} +\begin{list}{}{} +\item {} \href{\#a-simple-recipe-skip-redundant}{A simple recipe: skip redundant} + +\item {} \href{\#another-real-life-example-working-with-nested-structures}{Another real life example: working with nested structures} + +\item {} \href{\#another-typical-use-case-for-generators-parsers}{Another typical use case for generators: parsers} + +\item {} \href{\#other-kinds-of-iterables}{Other kinds of iterables} + +\item {} \href{\#the-itertools-module}{The itertools module} + +\item {} \href{\#anytrue}{anyTrue} + +\item {} \href{\#chop}{chop} + +\item {} \href{\#tee}{tee} + +\item {} \href{\#grouping-and-sorting}{Grouping and sorting} + +\end{list} + +\end{list} + +\item {} \href{\#lecture-2-objects-delegation-inheritance}{Lecture 2: Objects (delegation {\&} inheritance)} +\begin{list}{}{} +\item {} \href{\#part-i-delegation}{Part I: delegation} +\begin{list}{}{} +\item {} \href{\#accessing-simple-attributes}{Accessing simple attributes} + +\item {} \href{\#accessing-methods}{Accessing methods} + +\item {} \href{\#converting-functions-into-methods}{Converting functions into methods} + +\item {} \href{\#hack-a-very-slick-adder}{Hack: a very slick adder} + +\item {} \href{\#descriptor-protocol}{Descriptor Protocol} + +\item {} \href{\#multilingual-attribute}{Multilingual attribute} + +\item {} \href{\#another-use-case-for-properties-storing-users}{Another use case for properties: storing users} + +\item {} \href{\#low-level-delegation-via-getattribute}{Low-level delegation via {\_}{\_}getattribute{\_}{\_}} + +\item {} \href{\#traditional-delegation-via-getattr}{Traditional delegation via {\_}{\_}getattr{\_}{\_}} + +\item {} \href{\#keyword-dictionaries-with-getattr-setattr}{Keyword dictionaries with {\_}{\_}getattr{\_}{\_}/{\_}{\_}setattr{\_}{\_}} + +\item {} \href{\#delegation-to-special-methods-caveat}{Delegation to special methods caveat} + +\end{list} + +\item {} \href{\#part-ii-inheritance}{Part II: Inheritance} +\begin{list}{}{} +\item {} \href{\#why-you-need-to-know-about-mi-even-if-you-do-not-use-it}{Why you need to know about MI even if you do not use it} + +\item {} \href{\#a-few-details-about-super-not-the-whole-truth}{A few details about \texttt{super} (not the whole truth)} + +\item {} \href{\#subclassing-built-in-types-new-vs-init}{Subclassing built-in types; {\_}{\_}new{\_}{\_} vs. {\_}{\_}init{\_}{\_}} + +\item {} \href{\#be-careful-when-using-new-with-mutable-types}{Be careful when using {\_}{\_}new{\_}{\_} with mutable types} + +\end{list} + +\end{list} + +\item {} \href{\#lecture-3-magic-i-e-decorators-and-metaclasses}{Lecture 3: Magic (i.e. decorators and metaclasses)} +\begin{list}{}{} +\item {} \href{\#part-i-decorators}{Part I: decorators} +\begin{list}{}{} +\item {} \href{\#a-typical-decorator-traced}{A typical decorator: traced} + +\item {} \href{\#a-decorator-factory-timed}{A decorator factory: Timed} + +\item {} \href{\#a-powerful-decorator-pattern}{A powerful decorator pattern} + +\item {} \href{\#a-deferred-decorator}{A deferred decorator} + +\end{list} + +\item {} \href{\#part-ii-metaclasses}{Part II: metaclasses} +\begin{list}{}{} +\item {} \href{\#rejuvenating-old-style-classes}{Rejuvenating old-style classes} + +\item {} \href{\#a-typical-metaclass-example-metatracer}{A typical metaclass example: MetaTracer} + +\item {} \href{\#real-life-example-check-overriding}{Real life example: check overriding} + +\item {} \href{\#logfile}{LogFile} + +\item {} \href{\#cooperative-hierarchies}{Cooperative hierarchies} + +\item {} \href{\#metaclass-enhanced-modules}{Metaclass-enhanced modules} + +\item {} \href{\#magic-properties}{Magic properties} + +\item {} \href{\#hack-evil-properties}{Hack: evil properties} + +\item {} \href{\#why-i-suggest-not-to-use-metaclasses-in-production-code}{Why I suggest \emph{not} to use metaclasses in production code} + +\item {} \href{\#is-there-an-automatic-way-of-solving-the-conflict}{Is there an automatic way of solving the conflict?} + +\end{list} + +\end{list} + +\end{list} + + + +%___________________________________________________________________________ + +\hypertarget{lecture-1-loops-i-e-iterators-generators}{} +\pdfbookmark[0]{Lecture 1: Loops (i.e. iterators {\&} generators)}{lecture-1-loops-i-e-iterators-generators} +\section*{Lecture 1: Loops (i.e. iterators {\&} generators)} + + +%___________________________________________________________________________ + +\hypertarget{part-i-iterators}{} +\pdfbookmark[1]{Part I: iterators}{part-i-iterators} +\subsection*{Part I: iterators} + + +%___________________________________________________________________________ + +\hypertarget{iterators-are-everywhere}{} +\pdfbookmark[2]{Iterators are everywhere}{iterators-are-everywhere} +\subsubsection*{Iterators are everywhere} +\begin{verbatim}>>> for i in 1, 2, 3: +... print i +1 +2 +3\end{verbatim} + +The 'for' loop is using \emph{iterators} internally: +\begin{quote}{\ttfamily \raggedright \noindent +it~=~iter((1,2,3))~\\ +while~True:~\\ +~~~~try:~\\ +~~~~~~~~print~it.next()~\\ +~~~~except~StopIteration:~\\ +~~~~~~~~break +}\end{quote} + + +%___________________________________________________________________________ + +\hypertarget{iterables-and-iterators}{} +\pdfbookmark[2]{Iterables and iterators}{iterables-and-iterators} +\subsubsection*{Iterables and iterators} + +\emph{Iterable} = anything you can loop over = any sequence + any object with an {\_}{\_}iter{\_}{\_} method; + +Not every sequence has an {\_}{\_}iter{\_}{\_} method: +\begin{verbatim}>>> "hello".__iter__() +Traceback (most recent call last): + ... +AttributeError: 'str' object has no attribute '__iter__'\end{verbatim} + +\emph{Iterator} = any object with a .next method and an {\_}{\_}iter{\_}{\_} method returning self + + +%___________________________________________________________________________ + +\hypertarget{simpler-way-to-get-an-iterator}{} +\pdfbookmark[2]{Simpler way to get an iterator}{simpler-way-to-get-an-iterator} +\subsubsection*{Simpler way to get an iterator} +\begin{verbatim}>>> it = iter("hello") +>>> it.next() +'h' +>>> it.next() +'e' +>>> it.next() +'l' +>>> it.next() +'l' +>>> it.next() +'o' +>>> it.next() +Traceback (most recent call last): + ... +StopIteration\end{verbatim} + + +%___________________________________________________________________________ + +\hypertarget{sentinel-syntax-iter-callable-sentinel}{} +\pdfbookmark[2]{Sentinel syntax iter(callable, sentinel)}{sentinel-syntax-iter-callable-sentinel} +\subsubsection*{Sentinel syntax iter(callable, sentinel)} + +Example: +\begin{quote}{\ttfamily \raggedright \noindent +{\$}~echo~-e~"value1{\textbackslash}nvalue2{\textbackslash}nEND{\textbackslash}n"~>~data.txt~\\ +{\$}~python~-c~"print~list(iter(file('data.txt').readline,~'END{\textbackslash}n'))"~\\ +{[}'value1{\textbackslash}n',~'value2{\textbackslash}n'] +}\end{quote} + +Beware of infinite iterators: +\begin{verbatim}>>> repeat = iter(lambda : "some value", "") +>>> repeat.next() +'some value'\end{verbatim} + + +%___________________________________________________________________________ + +\hypertarget{second-simpler-way-to-get-an-iterator-generator-expressions}{} +\pdfbookmark[2]{Second simpler way to get an iterator: generator-expressions}{second-simpler-way-to-get-an-iterator-generator-expressions} +\subsubsection*{Second simpler way to get an iterator: generator-expressions} +\begin{verbatim}>>> squares = (i*i for i in range(1,11)) +>>> list(squares) +[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]\end{verbatim} + +Excessive parenthesis can be skipped, so use +\begin{verbatim}>>> dict((i, i*i) for i in range(1,11)) +{1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81, 10: 100}\end{verbatim} + +instead of +\begin{verbatim}>>> dict([(i, i*i) for i in range(1,11)]) +{1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81, 10: 100}\end{verbatim} + +(as usual, the most elegant version is the most efficient). + + +%___________________________________________________________________________ + +\hypertarget{iteration-caveats}{} +\pdfbookmark[2]{Iteration caveats}{iteration-caveats} +\subsubsection*{Iteration caveats} +\begin{verbatim}>>> ls = [i for i in (1,2,3)] +>>> i +3\end{verbatim} +\begin{verbatim}>>> it = (j for j in (1,2,3)) +>>> j +Traceback (most recent call last): + ... +NameError: name 'j' is not defined\end{verbatim} + +A subtler example: +\begin{verbatim}>>> ls = [lambda :i for i in (1,2,3)] +>>> ls[0]() +3\end{verbatim} + +instead +\begin{verbatim}>>> it = (lambda :i for i in (1,2,3)) +>>> it.next()() +1 +>>> it.next()() +2 +>>> it.next()() +3\end{verbatim} + +\emph{seems} to be working but it is not really the case: +\begin{verbatim}>>> it = (lambda :i for i in (1,2,3)) +>>> f1 = it.next() +>>> f2 = it.next() +>>> f3 = it.next() +>>> f1() +3\end{verbatim} + +The reason is that Python does LATE binding \emph{always}. The solution is ugly: +\begin{verbatim}>>> it = list(lambda i=i:i for i in (1,2,3)) +>>> it[0]() +1 +>>> it[1]() +2 +>>> it[2]() +3\end{verbatim} + + +%___________________________________________________________________________ + +\hypertarget{part-ii-generators}{} +\pdfbookmark[1]{Part II: generators}{part-ii-generators} +\subsection*{Part II: generators} + +Trivial example: +\begin{verbatim}>>> def gen123(): # "function" which returns an iterator over the values 1, 2, 3 +... yield 1 +... yield 2 +... yield 3 +... +>>> it = gen123() +>>> it.next() +1 +>>> it.next() +2 +>>> it.next() +3 +>>> it.next() +Traceback (most recent call last): + ... +StopIteration\end{verbatim} + +Real life example: using generators to generate HTML tables +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<htmltable.py>~\\ +~\\ +def~HTMLTablegen(table):~\\ +~~~~yield~"<table>"~\\ +~~~~for~row~in~table:~\\ +~~~~~~~yield~"<tr>"~\\ +~~~~~~~for~col~in~row:~\\ +~~~~~~~~~~~yield~"<td>{\%}s</td>"~{\%}~col~\\ +~~~~~~~yield~"</tr>"~\\ +~~~~yield~"</table>"~\\ +~\\ +def~test():~\\ +~~~~return~"{\textbackslash}n".join(HTMLTablegen({[}{[}"Row",~"City"],~~\\ +~~~~~~~~~~~~~~~~~~~~~~~{[}1,'London'],~{[}2,~'Oxford']]))~\\ +~\\ +if~{\_}{\_}name{\_}{\_}~==~"{\_}{\_}main{\_}{\_}":~{\#}~example~\\ +~~~~print~test()~\\ +~\\ +{\#}</htmltable.py> +}\end{quote} +\begin{verbatim}>>> from htmltable import test +>>> print test() +<table> +<tr> +<td>Row</td> +<td>City</td> +</tr> +<tr> +<td>1</td> +<td>London</td> +</tr> +<tr> +<td>2</td> +<td>Oxford</td> +</tr> +</table>\end{verbatim} + + +%___________________________________________________________________________ + +\hypertarget{a-simple-recipe-skip-redundant}{} +\pdfbookmark[2]{A simple recipe: skip redundant}{a-simple-recipe-skip-redundant} +\subsubsection*{A simple recipe: skip redundant} + +How to remove duplicates by keeping the order: +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<skip{\_}redundant.py>~\\ +~\\ +def~skip{\_}redundant(iterable,~skipset=None):~\\ +~~~"Redundant~items~are~repeated~items~or~items~in~the~original~skipset."~\\ +~~~if~skipset~is~None:~skipset~=~set()~\\ +~~~for~item~in~iterable:~\\ +~~~~~~~if~item~not~in~skipset:~\\ +~~~~~~~~~~~skipset.add(item)~\\ +~~~~~~~~~~~yield~item~\\ +~~~~~~~~~~\\ +{\#}</skip{\_}redundant.py> +}\end{quote} +\begin{verbatim}>>> from skip_redundant import skip_redundant +>>> print list(skip_redundant("<hello, world>", skipset=set("<>"))) +['h', 'e', 'l', 'o', ',', ' ', 'w', 'r', 'd']\end{verbatim} + + +%___________________________________________________________________________ + +\hypertarget{another-real-life-example-working-with-nested-structures}{} +\pdfbookmark[2]{Another real life example: working with nested structures}{another-real-life-example-working-with-nested-structures} +\subsubsection*{Another real life example: working with nested structures} +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<walk.py>~\\ +~\\ +def~walk(iterable,~level=0):~\\ +~~~~for~obj~in~iterable:~\\ +~~~~~~~~if~not~hasattr(obj,~"{\_}{\_}iter{\_}{\_}"):~{\#}~atomic~object~\\ +~~~~~~~~~~~~yield~obj,~level~\\ +~~~~~~~~else:~{\#}~composed~object:~iterate~again~\\ +~~~~~~~~~~~~for~subobj,~lvl~in~walk(obj,~level~+~1):~\\ +~~~~~~~~~~~~~~~~yield~subobj,~lvl~\\ +~\\ +def~flatten(iterable):~\\ +~~~~return~(obj~for~obj,~level~in~walk(iterable))~\\ +~~~~~~~~\\ +def~pprint(iterable):~\\ +~~~~for~obj,~level~in~walk(iterable):~\\ +~~~~~~~~print~"~"*level,~obj~\\ +~~~~~~~~\\ +{\#}</walk.py> +}\end{quote} +\begin{verbatim}>>> from walk import flatten, pprint +>>> nested_ls = [1,[2,[3,[[[4,5],6]]]],7] +>>> pprint(nested_ls) + 1 + 2 + 3 + 4 + 5 + 6 + 7 +>>> pprint(flatten(nested_ls)) + 1 + 2 + 3 + 4 + 5 + 6 + 7\end{verbatim} + + +%___________________________________________________________________________ + +\hypertarget{another-typical-use-case-for-generators-parsers}{} +\pdfbookmark[2]{Another typical use case for generators: parsers}{another-typical-use-case-for-generators-parsers} +\subsubsection*{Another typical use case for generators: parsers} + +A very stripped down parser for nested expressions +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<sexpr2indent.py>~\\ +"{}"{}"A~simple~s-expression~formatter."{}"{}"~\\ +~\\ +import~re~\\ +~\\ +def~parse(sexpr):~\\ +~~~~position~=~0~\\ +~~~~nesting{\_}level~=~0~\\ +~~~~paren~=~re.compile(r"(?P<paren{\_}beg>{\textbackslash}()|(?P<paren{\_}end>{\textbackslash}))")~\\ +~~~~while~True:~\\ +~~~~~~~~match~=~paren.search(sexpr,~position)~\\ +~~~~~~~~if~match:~\\ +~~~~~~~~~~~~yield~nesting{\_}level,~sexpr{[}position:~match.start()]~\\ +~~~~~~~~~~~~if~match.lastgroup~==~"paren{\_}beg":~\\ +~~~~~~~~~~~~~~~~nesting{\_}level~+=~1~\\ +~~~~~~~~~~~~elif~match.lastgroup~==~"paren{\_}end":~\\ +~~~~~~~~~~~~~~~~nesting{\_}level~-=~1~\\ +~~~~~~~~~~~~position~=~match.end()~\\ +~~~~~~~~else:~\\ +~~~~~~~~~~~~break~\\ +~\\ +def~sexpr{\_}indent(sexpr):~\\ +~~~~for~nesting,~text~in~parse(sexpr.replace("{\textbackslash}n",~"{}")):~\\ +~~~~~~~~if~text.strip():~~print~"~"*nesting,~text~\\ +~\\ +{\#}</sexpr2indent.py> +}\end{quote} +\begin{verbatim}>>> from sexpr2indent import sexpr_indent +>>> sexpr_indent("""\ +... (html (head (title Example)) (body (h1 s-expr formatter example) +... (a (@ (href http://www.example.com)) A link)))""") +... #doctest: +NORMALIZE_WHITESPACE + html + head + title Example + body + h1 s-expr formatter example + a + @ + href http://www.example.com + A link\end{verbatim} + + +%___________________________________________________________________________ + +\hypertarget{other-kinds-of-iterables}{} +\pdfbookmark[2]{Other kinds of iterables}{other-kinds-of-iterables} +\subsubsection*{Other kinds of iterables} + +The following class generates iterable which are not iterators: +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<reiterable.py>~\\ +~\\ +class~ReIter(object):~\\ +~~~~"A~re-iterable~object."~\\ +~~~~def~{\_}{\_}iter{\_}{\_}(self):~\\ +~~~~~~~~yield~1~\\ +~~~~~~~~yield~2~\\ +~~~~~~~~yield~3~\\ +~\\ +{\#}</reiterable.py> +}\end{quote} +\begin{verbatim}>>> from reiterable import ReIter +>>> rit = ReIter() +>>> list(rit) +[1, 2, 3] +>>> list(rit) # it is reiterable! +[1, 2, 3]\end{verbatim} + + +%___________________________________________________________________________ + +\hypertarget{the-itertools-module}{} +\pdfbookmark[2]{The itertools module}{the-itertools-module} +\subsubsection*{The itertools module} +\begin{quote} +\begin{itemize} +\item {} +count({[}n]) -{}-{\textgreater} n, n+1, n+2, ... + +\item {} +cycle(p) -{}-{\textgreater} p0, p1, ... plast, p0, p1, ... + +\item {} +repeat(elem {[},n]) -{}-{\textgreater} elem, elem, elem, ... endlessly or up to n times + +\item {} +izip(p, q, ...) -{}-{\textgreater} (p{[}0], q{[}0]), (p{[}1], q{[}1]), ... + +\item {} +ifilter(pred, seq) -{}-{\textgreater} elements of seq where pred(elem) is True + +\item {} +ifilterfalse(pred, seq) -{}-{\textgreater} elements of seq where pred(elem) is False + +\item {} +islice(seq, {[}start,] stop {[}, step]) -{}-{\textgreater} elements from seq{[}start:stop:step] + +\item {} +imap(fun, p, q, ...) -{}-{\textgreater} fun(p0, q0), fun(p1, q1), ... + +\item {} +starmap(fun, seq) -{}-{\textgreater} fun(*seq{[}0]), fun(*seq{[}1]), ... + +\item {} +tee(it, n=2) -{}-{\textgreater} (it1, it2 , ... itn) splits one iterator into n + +\item {} +chain(p, q, ...) -{}-{\textgreater} p0, p1, ... plast, q0, q1, ... + +\item {} +takewhile(pred, seq) -{}-{\textgreater} seq{[}0], seq{[}1], until pred fails + +\item {} +dropwhile(pred, seq) -{}-{\textgreater} seq{[}n], seq{[}n+1], starting when pred fails + +\item {} +groupby(iterable{[}, keyfunc]) -{}-{\textgreater} sub-iterators grouped by value of keyfunc(v) + +\end{itemize} +\end{quote} + + +%___________________________________________________________________________ + +\hypertarget{anytrue}{} +\pdfbookmark[2]{anyTrue}{anytrue} +\subsubsection*{anyTrue} +\begin{verbatim}>>> import itertools +>>> def anyTrue(predicate, iterable): +... return True in itertools.imap(predicate, iterable) +... +>>> fname = "picture.ps" +>>> anyTrue(fname.endswith, ".jpg .ps .ps".split()) +True\end{verbatim} + +AnyTrue does \emph{short-circuit}: +\begin{verbatim}>>> def is3(i): +... print "i=%s" % i +... return i == 3\end{verbatim} +\begin{verbatim}>>> anyTrue(is3, range(10)) +i=0 +i=1 +i=2 +i=3 +True\end{verbatim} + + +%___________________________________________________________________________ + +\hypertarget{chop}{} +\pdfbookmark[2]{chop}{chop} +\subsubsection*{chop} + +You want to chop an iterable in batches of a given size: +\begin{verbatim}>>> from chop import chop +>>> list(chop([1, 2, 3, 4], 2)) +[[1, 2], [3, 4]] +>>> list(chop([1, 2, 3, 4, 5, 6, 7],3)) +[[1, 2, 3], [4, 5, 6], [7]]\end{verbatim} + +Here is a possible implementation: +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<chop.py>~\\ +~\\ +{\#}~see~also~http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/303279~\\ +~\\ +import~itertools~\\ +~\\ +def~chop(iterable,~batchsize):~\\ +~~~~it~=~iter(iterable)~\\ +~~~~while~True:~\\ +~~~~~~~~batch~=~list(itertools.islice(it,~batchsize))~\\ +~~~~~~~~if~batch:~yield~batch~\\ +~~~~~~~~else:~break~\\ +~\\ +{\#}</chop.py> +}\end{quote} + +For people thinking Python is too readable, here is a one-liner: +\begin{verbatim}>>> chop = lambda it, n : itertools.izip(*(iter(it),)*n) +... +>>> list(chop([1,2,3,4], 2)) +[(1, 2), (3, 4)]\end{verbatim} + + +%___________________________________________________________________________ + +\hypertarget{tee}{} +\pdfbookmark[2]{tee}{tee} +\subsubsection*{tee} + +To make copies of iterables; typically used in parsers: +\begin{verbatim}>>> from itertools import tee, chain, izip +>>> chars, prevs = tee("abc") +>>> prevs = chain([None], prevs) +>>> for char, prev in izip(chars, prevs): +... print char, prev +... +a None +b a +c b\end{verbatim} + + +%___________________________________________________________________________ + +\hypertarget{grouping-and-sorting}{} +\pdfbookmark[2]{Grouping and sorting}{grouping-and-sorting} +\subsubsection*{Grouping and sorting} +\begin{verbatim}>>> from itertools import groupby +>>> from operator import itemgetter\end{verbatim} +\begin{verbatim}>>> NAME, AGE = 0, 1 +>>> query_result = ("Smith", 34), ("Donaldson", 34), ("Lee", 22), ("Orr", 22)\end{verbatim} + +Grouping together people of the same age: +\begin{verbatim}>>> for k, g in groupby(query_result, key=itemgetter(AGE)): +... print k, list(g) +... +34 [('Smith', 34), ('Donaldson', 34)] +22 [('Lee', 22), ('Orr', 22)]\end{verbatim} + +Sorting by name: +\begin{verbatim}>>> for tup in sorted(query_result, key=itemgetter(NAME)): +... print tup +('Donaldson', 34) +('Lee', 22) +('Orr', 22) +('Smith', 34)\end{verbatim} + + +%___________________________________________________________________________ + +\hypertarget{lecture-2-objects-delegation-inheritance}{} +\pdfbookmark[0]{Lecture 2: Objects (delegation {\&} inheritance)}{lecture-2-objects-delegation-inheritance} +\section*{Lecture 2: Objects (delegation {\&} inheritance)} + + +%___________________________________________________________________________ + +\hypertarget{part-i-delegation}{} +\pdfbookmark[1]{Part I: delegation}{part-i-delegation} +\subsection*{Part I: delegation} + +Understanding how attribute access works: internal delegation via \emph{descriptors} + + +%___________________________________________________________________________ + +\hypertarget{accessing-simple-attributes}{} +\pdfbookmark[2]{Accessing simple attributes}{accessing-simple-attributes} +\subsubsection*{Accessing simple attributes} +\begin{verbatim}>>> class C(object): +... a = 2 +... def __init__(self, x): +... self.x = x +...\end{verbatim} +\begin{verbatim}>>> c = C(1) +>>> c.x +1 +>>> c.a +2\end{verbatim} + +We are retrieving +\begin{verbatim}>>> c.__dict__["x"] +1\end{verbatim} + +If there is nothing in c.{\_}{\_}dict{\_}{\_}, Python looks at C.{\_}{\_}dict{\_}{\_}: +\begin{verbatim}>>> print c.__dict__.get("a") +None\end{verbatim} +\begin{verbatim}>>> C.__dict__["a"] +2\end{verbatim} + +If there is nothing in C.{\_}{\_}dict{\_}{\_}, Python looks at the superclasses according +to the MRO (see part II). + + +%___________________________________________________________________________ + +\hypertarget{accessing-methods}{} +\pdfbookmark[2]{Accessing methods}{accessing-methods} +\subsubsection*{Accessing methods} +\begin{verbatim}>>> c.__init__ #doctest: +ELLIPSIS +<bound method C.__init__ of <__main__.C object at 0x...>>\end{verbatim} + +since {\_}{\_}init{\_}{\_} is not in c.{\_}{\_}dict{\_}{\_} Python looks in the class dictionary +and finds +\begin{verbatim}>>> C.__dict__["__init__"] #doctest: +ELLIPSIS +<function __init__ at 0x...>\end{verbatim} + +Then it magically converts the function into a method bound to the instance +``c''. + +NOTE: this mechanism works for new-style classes only. + +The old-style mechanism is less consistent and the attribute lookup of special +methods is special: (*) +\begin{verbatim}>>> class C(object): pass +>>> c = C() +>>> c.__str__ = lambda : "hello!" +>>> print c #doctest: +ELLIPSIS +<__main__.C object at ...>\end{verbatim} + +whereas for old-style classes +\begin{verbatim}>>> class C: pass +>>> c = C() +>>> c.__str__ = lambda : "hello!" +>>> print c +hello!\end{verbatim} + +the special method is looked for in the instance dictionary too. + +(*) modulo a very subtle difference for {\_}{\_}getattr{\_}{\_}-delegated special methods, +see later. + + +%___________________________________________________________________________ + +\hypertarget{converting-functions-into-methods}{} +\pdfbookmark[2]{Converting functions into methods}{converting-functions-into-methods} +\subsubsection*{Converting functions into methods} + +It is possible to convert a function into a bound or unbound method +by invoking the \texttt{{\_}{\_}get{\_}{\_}} special method: +\begin{verbatim}>>> def f(x): pass +>>> f.__get__ #doctest: +ELLIPSIS +<method-wrapper object at 0x...>\end{verbatim} +\begin{verbatim}>>> class C(object): pass +...\end{verbatim} +\begin{verbatim}>>> def f(self): pass +... +>>> f.__get__(C(), C) #doctest: +ELLIPSIS +<bound method C.f of <__main__.C object at 0x...>>\end{verbatim} +\begin{verbatim}>>> f.__get__(None, C) +<unbound method C.f>\end{verbatim} + +Functions are the simplest example of \emph{descriptors}. + +Access to methods works since internally Python transforms +\begin{quote} + +\texttt{c.{\_}{\_}init{\_}{\_} -> type(c).{\_}{\_}dict{\_}{\_}{[}'{\_}{\_}init{\_}{\_}'].{\_}{\_}get{\_}{\_}(c, type(c))} +\end{quote} + +Note: not \emph{all} functions are descriptors: +\begin{verbatim}>>> from operator import add +>>> add.__get__ +Traceback (most recent call last): + ... +AttributeError: 'builtin_function_or_method' object has no attribute '__get__'\end{verbatim} + + +%___________________________________________________________________________ + +\hypertarget{hack-a-very-slick-adder}{} +\pdfbookmark[2]{Hack: a very slick adder}{hack-a-very-slick-adder} +\subsubsection*{Hack: a very slick adder} + +The descriptor protocol can be (ab)used as a way to avoid the late binding +issue in for loops: +\begin{verbatim}>>> def add(x,y): +... return x + y +>>> closures = [add.__get__(i) for i in range(10)] +>>> closures[5](1000) +1005\end{verbatim} + +Notice: operator.add will not work. + + +%___________________________________________________________________________ + +\hypertarget{descriptor-protocol}{} +\pdfbookmark[2]{Descriptor Protocol}{descriptor-protocol} +\subsubsection*{Descriptor Protocol} + +Everything at \href{http://users.rcn.com/python/download/Descriptor.htm}{http://users.rcn.com/python/download/Descriptor.htm} + +Formally: +\begin{quote}{\ttfamily \raggedright \noindent +descr.{\_}{\_}get{\_}{\_}(self,~obj,~type=None)~-{}->~value~\\ +descr.{\_}{\_}set{\_}{\_}(self,~obj,~value)~-{}->~None~\\ +descr.{\_}{\_}delete{\_}{\_}(self,~obj)~-{}->~None +}\end{quote} + +Examples of custom descriptors: +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<descriptor.py>~\\ +~\\ +~\\ +class~AttributeDescriptor(object):~\\ +~~~def~{\_}{\_}get{\_}{\_}(self,~obj,~cls=None):~\\ +~~~~~~~if~obj~is~None~and~cls~is~None:~\\ +~~~~~~~~~~~raise~TypeError("{\_}{\_}get{\_}{\_}(None,~None)~is~invalid")~\\ +~~~~~~~elif~obj~is~None:~\\ +~~~~~~~~~~~return~self.get{\_}from{\_}class(cls)~\\ +~~~~~~~else:~\\ +~~~~~~~~~~~return~self.get{\_}from{\_}obj(obj)~\\ +~~~def~get{\_}from{\_}class(self,~cls):~\\ +~~~~~~~print~"Getting~{\%}s~from~{\%}s"~{\%}~(self,~cls)~\\ +~~~def~get{\_}from{\_}obj(self,~obj):~\\ +~~~~~~~print~"Getting~{\%}s~from~{\%}s"~{\%}~(self,~obj)~\\ +~\\ +~\\ +class~Staticmethod(AttributeDescriptor):~\\ +~~~def~{\_}{\_}init{\_}{\_}(self,~func):~\\ +~~~~~~~self.func~=~func~\\ +~~~def~get{\_}from{\_}class(self,~cls):~\\ +~~~~~~~return~self.func~\\ +~~~get{\_}from{\_}obj~=~get{\_}from{\_}class~\\ +~\\ +~\\ +class~Classmethod(AttributeDescriptor):~\\ +~~~def~{\_}{\_}init{\_}{\_}(self,~func):~\\ +~~~~~~~self.func~=~func~\\ +~~~def~get{\_}from{\_}class(self,~cls):~\\ +~~~~~~~return~self.func.{\_}{\_}get{\_}{\_}(cls,~type(cls))~\\ +~~~def~get{\_}from{\_}obj(self,~obj):~\\ +~~~~~~~return~self.get{\_}from{\_}class(obj.{\_}{\_}class{\_}{\_})~\\ +~\\ +class~C(object):~\\ +~~~s~=~Staticmethod(lambda~:~1)~\\ +~~~c~=~Classmethod(lambda~cls~:~cls.{\_}{\_}name{\_}{\_})~\\ +~\\ +c~=~C()~\\ +~\\ +assert~C.s()~==~c.s()~==~1~\\ +assert~C.c()~==~c.c()~==~"C"~\\ +~\\ +{\#}</descriptor.py> +}\end{quote} + + +%___________________________________________________________________________ + +\hypertarget{multilingual-attribute}{} +\pdfbookmark[2]{Multilingual attribute}{multilingual-attribute} +\subsubsection*{Multilingual attribute} + +Inspirated by a question in the Italian Newsgroup: +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<multilingual.py>~\\ +~\\ +import~sys~\\ +from~descriptor~import~AttributeDescriptor~\\ +~\\ +class~MultilingualAttribute(AttributeDescriptor):~\\ +~~~~"{}"{}"When~a~MultilingualAttribute~is~accessed,~you~get~the~translation~~\\ +~~~~corresponding~to~the~currently~selected~language.~\\ +~~~~"{}"{}"~\\ +~~~~def~{\_}{\_}init{\_}{\_}(self,~**translations):~\\ +~~~~~~~~self.trans~=~translations~\\ +~~~~def~get{\_}from{\_}class(self,~cls):~\\ +~~~~~~~~return~self.trans{[}getattr(cls,~"language",~None)~or~\\ +~~~~~~~~~~~~~~~~~~~~~~~~~sys.modules{[}cls.{\_}{\_}module{\_}{\_}].language]~\\ +~~~~def~get{\_}from{\_}obj(self,~obj):~\\ +~~~~~~~~return~self.trans{[}getattr(obj,~"language",~None)~or~\\ +~~~~~~~~~~~~~~~~~~~~~~~~~sys.modules{[}obj.{\_}{\_}class{\_}{\_}.{\_}{\_}module{\_}{\_}].language]~\\ +~~~~~~\\ +~\\ +language~=~"en"~\\ +~\\ +{\#}~a~dummy~User~class~\\ +class~DefaultUser(object):~\\ +~~~~def~has{\_}permission(self):~\\ +~~~~~~~~return~False~\\ +~~~~\\ +class~WebApplication(object):~\\ +~~~~error{\_}msg~=~MultilingualAttribute(~\\ +~~~~~~~~en="You~cannot~access~this~page",~\\ +~~~~~~~~it="Questa~pagina~non~e'~accessibile",~\\ +~~~~~~~~fr="Vous~ne~pouvez~pas~acceder~cette~page",)~\\ +~~~~user~=~DefaultUser()~\\ +~~~~def~{\_}{\_}init{\_}{\_}(self,~language=None):~\\ +~~~~~~~~self.language~=~language~or~getattr(self.{\_}{\_}class{\_}{\_},~"language",~None)~\\ +~~~~def~show{\_}page(self):~\\ +~~~~~~~~if~not~self.user.has{\_}permission():~\\ +~~~~~~~~~~~~return~self.error{\_}msg~\\ +~\\ +~\\ +app~=~WebApplication()~\\ +assert~app.show{\_}page()~==~"You~cannot~access~this~page"~\\ +~\\ +app.language~=~"fr"~\\ +assert~app.show{\_}page()~==~"Vous~ne~pouvez~pas~acceder~cette~page"~\\ +~\\ +app.language~=~"it"~\\ +assert~app.show{\_}page()~==~"Questa~pagina~non~e'~accessibile"~\\ +~\\ +app.language~=~"en"~\\ +assert~app.show{\_}page()~==~"You~cannot~access~this~page"~\\ +~\\ +{\#}</multilingual.py> +}\end{quote} + +The same can be done with properties: +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<multilingualprop.py>~\\ +~\\ +language~=~"en"~\\ +~\\ +{\#}~a~dummy~User~class~\\ +class~DefaultUser(object):~\\ +~~~~def~has{\_}permission(self):~\\ +~~~~~~~~return~False~\\ +~~~~\\ +def~multilingualProperty(**trans):~\\ +~~~~def~get(self):~\\ +~~~~~~~~return~trans{[}self.language]~\\ +~~~~def~set(self,~value):~\\ +~~~~~~~~trans{[}self.language]~=~value~~\\ +~~~~return~property(get,~set)~\\ +~\\ +class~WebApplication(object):~\\ +~~~~language~=~language~\\ +~~~~error{\_}msg~=~multilingualProperty(~\\ +~~~~~~~~en="You~cannot~access~this~page",~\\ +~~~~~~~~it="Questa~pagina~non~e'~accessibile",~\\ +~~~~~~~~fr="Vous~ne~pouvez~pas~acceder~cette~page",)~\\ +~~~~user~=~DefaultUser()~\\ +~~~~def~{\_}{\_}init{\_}{\_}(self,~language=None):~\\ +~~~~~~~~if~language:~self.language~=~self.language~\\ +~~~~def~show{\_}page(self):~\\ +~~~~~~~~if~not~self.user.has{\_}permission():~\\ +~~~~~~~~~~~~return~self.error{\_}msg~\\ +~\\ +{\#}</multilingualprop.py> +}\end{quote} + +This also gives the possibility to set the error messages. + +The difference with the descriptor approach +\begin{verbatim}>>> from multilingual import WebApplication +>>> app = WebApplication() +>>> print app.error_msg +You cannot access this page +>>> print WebApplication.error_msg +You cannot access this page\end{verbatim} + +is that with properties there is no nice access from the class: +\begin{verbatim}>>> from multilingualprop import WebApplication +>>> WebApplication.error_msg #doctest: +ELLIPSIS +<property object at ...>\end{verbatim} + + +%___________________________________________________________________________ + +\hypertarget{another-use-case-for-properties-storing-users}{} +\pdfbookmark[2]{Another use case for properties: storing users}{another-use-case-for-properties-storing-users} +\subsubsection*{Another use case for properties: storing users} + +Consider a library providing a simple User class: +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<crypt{\_}user.py>~\\ +~\\ +class~User(object):~\\ +~~~~def~{\_}{\_}init{\_}{\_}(self,~username,~password):~\\ +~~~~~~~~self.username,~self.password~=~username,~password~\\ +~\\ +{\#}</crypt{\_}user.py> +}\end{quote} + +The User objects are stored in a database as they are. +For security purpose, in a second version of the library it is +decided to crypt the password, so that only crypted passwords +are stored in the database. With properties, it is possible to +implement this functionality without changing the source code for +the User class: +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<crypt{\_}user.py>~\\ +~\\ +from~crypt~import~crypt~\\ +~\\ +def~cryptedAttribute(seed="x"):~\\ +~~~~def~get(self):~\\ +~~~~~~~~return~getattr(self,~"{\_}pw",~None)~\\ +~~~~def~set(self,~value):~\\ +~~~~~~~~self.{\_}pw~=~crypt(value,~seed)~\\ +~~~~return~property(get,~set)~\\ +~~~~\\ +User.password~=~cryptedAttribute() +}\end{quote} + +{\#}{\textless}/crypt{\_}user.py{\textgreater} +\begin{verbatim}>>> from crypt_user import User +>>> u = User("michele", "secret") +>>> print u.password +xxZREZpkHZpkI\end{verbatim} + +Notice the property factory approach used here. + + +%___________________________________________________________________________ + +\hypertarget{low-level-delegation-via-getattribute}{} +\pdfbookmark[2]{Low-level delegation via {\_}{\_}getattribute{\_}{\_}}{low-level-delegation-via-getattribute} +\subsubsection*{Low-level delegation via {\_}{\_}getattribute{\_}{\_}} + +Attribute access is managed by the{\_}{\_}getattribute{\_}{\_} special method: +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<tracedaccess.py>~\\ +~\\ +class~TracedAccess(object):~\\ +~~~~def~{\_}{\_}getattribute{\_}{\_}(self,~name):~\\ +~~~~~~~~print~"Accessing~{\%}s"~{\%}~name~\\ +~~~~~~~~return~object.{\_}{\_}getattribute{\_}{\_}(self,~name)~\\ +~\\ +~\\ +class~C(TracedAccess):~\\ +~~~~s~=~staticmethod(lambda~:~'staticmethod')~\\ +~~~~c~=~classmethod(lambda~cls:~'classmethod')~\\ +~~~~m~=~lambda~self:~'method'~\\ +~~~~a~=~"hello"~\\ +~\\ +{\#}</tracedaccess.py> +}\end{quote} +\begin{verbatim}>>> from tracedaccess import C +>>> c = C() +>>> print c.s() +Accessing s +staticmethod +>>> print c.c() +Accessing c +classmethod +>>> print c.m() +Accessing m +method +>>> print c.a +Accessing a +hello +>>> print c.__init__ #doctest: +ELLIPSIS +Accessing __init__ +<method-wrapper object at 0x...> +>>> try: c.x +... except AttributeError, e: print e +... +Accessing x +'C' object has no attribute 'x'\end{verbatim} +\begin{verbatim}>>> c.y = 'y' +>>> c.y +Accessing y +'y'\end{verbatim} + +You are probably familiar with \texttt{{\_}{\_}getattr{\_}{\_}} which is similar +to \texttt{{\_}{\_}getattribute{\_}{\_}}, but it is called \emph{only for missing attributes}. + + +%___________________________________________________________________________ + +\hypertarget{traditional-delegation-via-getattr}{} +\pdfbookmark[2]{Traditional delegation via {\_}{\_}getattr{\_}{\_}}{traditional-delegation-via-getattr} +\subsubsection*{Traditional delegation via {\_}{\_}getattr{\_}{\_}} + +Realistic use case in ``object publishing'': +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<webapp.py>~\\ +~\\ +class~WebApplication(object):~\\ +~~~~def~{\_}{\_}getattr{\_}{\_}(self,~name):~\\ +~~~~~~~~return~name.capitalize()~\\ +~\\ +~\\ +app~=~WebApplication()~\\ +~\\ +assert~app.page1~==~'Page1'~\\ +assert~app.page2~==~'Page2'~\\ +~\\ +{\#}</webapp.py> +}\end{quote} + +Here is another use case in HTML generation: +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<XMLtag.py>~\\ +~\\ +def~makeattr(dict{\_}or{\_}list{\_}of{\_}pairs):~\\ +~~~~dic~=~dict(dict{\_}or{\_}list{\_}of{\_}pairs)~~\\ +~~~~return~"~".join('{\%}s="{\%}s"'~{\%}~(k,~dic{[}k])~for~k~in~dic)~{\#}~simplistic~\\ +~\\ +class~XMLTag(object):~\\ +~~~~def~{\_}{\_}getattr{\_}{\_}(self,~name):~\\ +~~~~~~~~def~tag(value,~**attr):~\\ +~~~~~~~~~~~~"{}"{}"value~can~be~a~string~or~a~sequence~of~strings."{}"{}"~\\ +~~~~~~~~~~~~if~hasattr(value,~"{\_}{\_}iter{\_}{\_}"):~{\#}~is~iterable~\\ +~~~~~~~~~~~~~~~~value~=~"~".join(value)~\\ +~~~~~~~~~~~~return~"<{\%}s~{\%}s>{\%}s</{\%}s>"~{\%}~(name,~makeattr(attr),~value,~name)~\\ +~~~~~~~~return~tag~\\ +~\\ +class~XMLShortTag(object):~\\ +~~~~def~{\_}{\_}getattr{\_}{\_}(self,~name):~\\ +~~~~~~~~def~tag(**attr):~\\ +~~~~~~~~~~~~return~"<{\%}s~{\%}s~/>"~{\%}~(name,~makeattr(attr))~\\ +~~~~~~~~return~tag~\\ +~\\ +tag~=~XMLTag()~\\ +tg~=~XMLShortTag()~\\ +~\\ +{\#}</XMLtag.py> +}\end{quote} +\begin{verbatim}>>> from XMLtag import tag, tg +>>> print tag.a("example.com", href="http://www.example.com") +<a href="http://www.example.com">example.com</a> +>>> print tg.br(**{'class':"br_style"}) +<br class="br_style" />\end{verbatim} + + +%___________________________________________________________________________ + +\hypertarget{keyword-dictionaries-with-getattr-setattr}{} +\pdfbookmark[2]{Keyword dictionaries with {\_}{\_}getattr{\_}{\_}/{\_}{\_}setattr{\_}{\_}}{keyword-dictionaries-with-getattr-setattr} +\subsubsection*{Keyword dictionaries with {\_}{\_}getattr{\_}{\_}/{\_}{\_}setattr{\_}{\_}} +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<kwdict.py>~\\ +~\\ +class~kwdict(dict):~{\#}~or~UserDict,~to~make~it~to~work~with~Zope~\\ +~~~~"{}"{}"A~typing~shortcut~used~in~place~of~a~keyword~dictionary."{}"{}"~\\ +~~~~def~{\_}{\_}getattr{\_}{\_}(self,~name):~\\ +~~~~~~~~return~self{[}name]~\\ +~~~~def~{\_}{\_}setattr{\_}{\_}(self,~name,~value):~\\ +~~~~~~~~self{[}name]~=~value~\\ +~\\ +{\#}</kwdict.py> +}\end{quote} + +And now for a completely different solution: +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<dictwrapper.py>~\\ +~\\ +class~DictWrapper(object):~~\\ +~~~~def~{\_}{\_}init{\_}{\_}(self,~**kw):~\\ +~~~~~~~~self.{\_}{\_}dict{\_}{\_}.update(kw)~\\ +~\\ +{\#}</dictwrapper.py> +}\end{quote} + + +%___________________________________________________________________________ + +\hypertarget{delegation-to-special-methods-caveat}{} +\pdfbookmark[2]{Delegation to special methods caveat}{delegation-to-special-methods-caveat} +\subsubsection*{Delegation to special methods caveat} +\begin{verbatim}>>> class ListWrapper(object): +... def __init__(self, ls): +... self._list = ls +... def __getattr__(self, name): +... if name == "__getitem__": # special method +... return self._list.__getitem__ +... elif name == "reverse": # regular method +... return self._list.reverse +... else: +... raise AttributeError("%r is not defined" % name) +... +>>> lw = ListWrapper([0,1,2]) +>>> print lw.x +Traceback (most recent call last): + ... +AttributeError: 'x' is not defined\end{verbatim} +\begin{verbatim}>>> lw.reverse() +>>> print lw.__getitem__(0) +2 +>>> print lw.__getitem__(1) +1 +>>> print lw.__getitem__(2) +0 +>>> print lw[0] +Traceback (most recent call last): + ... +TypeError: unindexable object\end{verbatim} + + +%___________________________________________________________________________ + +\hypertarget{part-ii-inheritance}{} +\pdfbookmark[1]{Part II: Inheritance}{part-ii-inheritance} +\subsection*{Part II: Inheritance} + +The major changes in inheritance from Python 2.1 to 2.2+ are: +\newcounter{listcnt1} +\begin{list}{\arabic{listcnt1}.} +{ +\usecounter{listcnt1} +\setlength{\rightmargin}{\leftmargin} +} +\item {} +you can subclass built-in types (as a consequence the constructor{\_}{\_}new{\_}{\_} +has been exposed to the user, to help subclassing immutable types); + +\item {} +the Method Resolution Order (MRO) has changed; + +\item {} +now Python allows \emph{cooperative method calls}, i.e. we have \emph{super}. + +\end{list} + + +%___________________________________________________________________________ + +\hypertarget{why-you-need-to-know-about-mi-even-if-you-do-not-use-it}{} +\pdfbookmark[2]{Why you need to know about MI even if you do not use it}{why-you-need-to-know-about-mi-even-if-you-do-not-use-it} +\subsubsection*{Why you need to know about MI even if you do not use it} + +In principle, the last two changes are relevant only if you use multiple +inheritance. If you use single inheritance only, you don't need \texttt{super}: +you can just name the superclass. +However, somebody else may want to use your class in a MI hierarchy, +and you would make her life difficult if you don't use \texttt{super}. + +My SI hierarchy: +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<why{\_}super.py>~\\ +~\\ +class~Base(object):~\\ +~~~~def~{\_}{\_}init{\_}{\_}(self):~\\ +~~~~~~~~print~"B.{\_}{\_}init{\_}{\_}"~\\ +~\\ +class~MyClass(Base):~\\ +~~~~"I~do~not~cooperate~with~others"~\\ +~~~~def~{\_}{\_}init{\_}{\_}(self):~\\ +~~~~~~~~print~"MyClass.{\_}{\_}init{\_}{\_}"~\\ +~~~~~~~~Base.{\_}{\_}init{\_}{\_}(self)~~{\#}instead~of~super(MyClass,~self).{\_}{\_}init{\_}{\_}()~\\ +~\\ +{\#}</why{\_}super.py> +}\end{quote} + +Her MI hierarchy: +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<why{\_}super.py>~\\ +~\\ +class~Mixin(Base):~\\ +~~~~"I~am~cooperative~with~others"~\\ +~~~~def~{\_}{\_}init{\_}{\_}(self):~\\ +~~~~~~~~print~"Mixin.{\_}{\_}init{\_}{\_}"~\\ +~~~~~~~~super(Mixin,~self).{\_}{\_}init{\_}{\_}()~\\ +~\\ +class~HerClass(MyClass,~Mixin):~\\ +~~~~"I~am~supposed~to~be~cooperative~too"~\\ +~~~~def~{\_}{\_}init{\_}{\_}(self):~\\ +~~~~~~~~print~"HerClass.{\_}{\_}init{\_}{\_}"~\\ +~~~~~~~~super(HerClass,~self).{\_}{\_}init{\_}{\_}()~\\ +~\\ +{\#}</why{\_}super.py> +}\end{quote} +\begin{verbatim}>>> from why_super import HerClass +>>> h = HerClass() # Mixin.__init__ is not called! +HerClass.__init__ +MyClass.__init__ +B.__init__\end{verbatim} +\begin{quote} +\begin{quote}{\ttfamily \raggedright \noindent +~~~~~~4~object~~~\\ +~~~~~~~~~~|~\\ +~~~~~~~3~Base~\\ +~~~~~~/~~~~~~{\textbackslash}~\\ +1~MyClass~~2~Mixin~\\ +~~~~~~~{\textbackslash}~~~~~/~\\ +~~~~~0~HerClass +}\end{quote} +\end{quote} +\begin{verbatim}>>> [ancestor.__name__ for ancestor in HerClass.mro()] +['HerClass', 'MyClass', 'Mixin', 'Base', 'object']\end{verbatim} + +In order to be polite versus your future users, you should use \texttt{super} +always. This adds a cognitive burden even for people not using MI :-( + +Notice that there is no good comprehensive reference on \texttt{super} (yet) +Your best bet is still \href{http://www.python.org/2.2.3/descrintro.html\#cooperation}{http://www.python.org/2.2.3/descrintro.html{\#}cooperation} + +The MRO instead is explained here: \href{http://www.python.org/2.3/mro.html}{http://www.python.org/2.3/mro.html} + +Notice that I DO NOT recommand Multiple Inheritance. + +More often than not you are better off using composition/delegation/wrapping, +etc. + +See Zope 2 -{\textgreater} Zope 3 experience. + + +%___________________________________________________________________________ + +\hypertarget{a-few-details-about-super-not-the-whole-truth}{} +\pdfbookmark[2]{A few details about super (not the whole truth)}{a-few-details-about-super-not-the-whole-truth} +\subsubsection*{A few details about \texttt{super} (not the whole truth)} +\begin{verbatim}>>> class B(object): +... def __init__(self): print "B.__init__" +... +>>> class C(B): +... def __init__(self): print "C.__init__" +... +>>> c = C() +C.__init__\end{verbatim} + +\texttt{super(cls, instance)}, where \texttt{instance} is an instance of \texttt{cls} or of +a subclass of \texttt{cls}, retrieves the right method in the MRO: +\begin{verbatim}>>> super(C, c).__init__ #doctest: +ELLIPSIS +<bound method C.__init__ of <__main__.C object at 0x...>>\end{verbatim} +\begin{verbatim}>>> super(C, c).__init__.im_func is B.__init__.im_func +True\end{verbatim} +\begin{verbatim}>>> super(C, c).__init__() +B.__init__\end{verbatim} + +\texttt{super(cls, subclass)} works for unbound methods: +\begin{verbatim}>>> super(C, C).__init__ +<unbound method C.__init__>\end{verbatim} +\begin{verbatim}>>> super(C, C).__init__.im_func is B.__init__.im_func +True +>>> super(C, C).__init__(c) +B.__init__\end{verbatim} + +\texttt{super(cls, subclass)} is also necessary for classmethods and staticmethods. +Properties and custom descriptorsw works too: +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<super{\_}ex.py>~\\ +~\\ +from~descriptor~import~AttributeDescriptor~\\ +~\\ +class~B(object):~\\ +~~~@staticmethod~\\ +~~~def~sm():~return~"staticmethod"~\\ +~\\ +~~~@classmethod~\\ +~~~def~cm(cls):~return~cls.{\_}{\_}name{\_}{\_}~\\ +~\\ +~~~p~=~property()~\\ +~~~a~=~AttributeDescriptor()~\\ +~\\ +class~C(B):~pass~\\ +~\\ +{\#}</super{\_}ex.py> +}\end{quote} +\begin{verbatim}>>> from super_ex import C\end{verbatim} + +Staticmethod usage: +\begin{verbatim}>>> super(C, C).sm #doctest: +ELLIPSIS +<function sm at 0x...> +>>> super(C, C).sm() +'staticmethod'\end{verbatim} + +Classmethod usage: +\begin{verbatim}>>> super(C, C()).cm +<bound method type.cm of <class 'super_ex.C'>> +>>> super(C, C).cm() # C is automatically passed +'C'\end{verbatim} + +Property usage: +\begin{verbatim}>>> print super(C, C).p #doctest: +ELLIPSIS +<property object at 0x...> +>>> super(C, C).a #doctest: +ELLIPSIS +Getting <descriptor.AttributeDescriptor object at 0x...> from <class 'super_ex.C'>\end{verbatim} + +\texttt{super} does not work with old-style classes, however you can use the +following trick: +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<super{\_}old{\_}new.py>~\\ +class~O:~\\ +~~~~def~{\_}{\_}init{\_}{\_}(self):~\\ +~~~~~~~~print~"O.{\_}{\_}init{\_}{\_}"~\\ +~\\ +class~N(O,~object):~\\ +~~~~def~{\_}{\_}init{\_}{\_}(self):~\\ +~~~~~~~~print~"N.{\_}{\_}init{\_}{\_}"~\\ +~~~~~~~~super(N,~self).{\_}{\_}init{\_}{\_}()~\\ +~\\ +{\#}</super{\_}old{\_}new.py> +}\end{quote} +\begin{verbatim}>>> from super_old_new import N +>>> new = N() +N.__init__ +O.__init__\end{verbatim} + +There are dozens of tricky points concerning \texttt{super}, be warned! + + +%___________________________________________________________________________ + +\hypertarget{subclassing-built-in-types-new-vs-init}{} +\pdfbookmark[2]{Subclassing built-in types; {\_}{\_}new{\_}{\_} vs. {\_}{\_}init{\_}{\_}}{subclassing-built-in-types-new-vs-init} +\subsubsection*{Subclassing built-in types; {\_}{\_}new{\_}{\_} vs. {\_}{\_}init{\_}{\_}} +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<point.py>~\\ +~\\ +class~NotWorkingPoint(tuple):~\\ +~~~~def~{\_}{\_}init{\_}{\_}(self,~x,~y):~\\ +~~~~~~~~super(NotWorkingPoint,~self).{\_}{\_}init{\_}{\_}((x,y))~\\ +~~~~~~~~self.x,~self.y~=~x,~y~\\ +~\\ +{\#}</point.py> +}\end{quote} +\begin{verbatim}>>> from point import NotWorkingPoint +>>> p = NotWorkingPoint(2,3) +Traceback (most recent call last): + ... +TypeError: tuple() takes at most 1 argument (2 given)\end{verbatim} +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<point.py>~\\ +~\\ +class~Point(tuple):~\\ +~~~~def~{\_}{\_}new{\_}{\_}(cls,~x,~y):~\\ +~~~~~~~~return~super(Point,~cls).{\_}{\_}new{\_}{\_}(cls,~(x,y))~\\ +~~~~def~{\_}{\_}init{\_}{\_}(self,~x,~y):~\\ +~~~~~~~~super(Point,~self).{\_}{\_}init{\_}{\_}((x,~y))~\\ +~~~~~~~~self.x,~self.y~=~x,~y~\\ +~\\ +{\#}</point.py> +}\end{quote} + +Notice that{\_}{\_}new{\_}{\_} is a staticmethod, not a classmethod, so one needs +to pass the class explicitely. +\begin{verbatim}>>> from point import Point +>>> p = Point(2,3) +>>> print p, p.x, p.y +(2, 3) 2 3\end{verbatim} + + +%___________________________________________________________________________ + +\hypertarget{be-careful-when-using-new-with-mutable-types}{} +\pdfbookmark[2]{Be careful when using {\_}{\_}new{\_}{\_} with mutable types}{be-careful-when-using-new-with-mutable-types} +\subsubsection*{Be careful when using {\_}{\_}new{\_}{\_} with mutable types} +\begin{verbatim}>>> class ListWithDefault(list): +... def __new__(cls): +... return super(ListWithDefault, cls).__new__(cls, ["hello"]) +... +>>> print ListWithDefault() # beware! NOT ["hello"]! +[]\end{verbatim} + +Reason: lists are re-initialized to empty lists in list.{\_}{\_}init{\_}{\_}! + +Instead +\begin{verbatim}>>> class ListWithDefault(list): +... def __init__(self): +... super(ListWithDefault, self).__init__(["hello"]) +... +>>> print ListWithDefault() # works! +['hello']\end{verbatim} + + +%___________________________________________________________________________ + +\hypertarget{lecture-3-magic-i-e-decorators-and-metaclasses}{} +\pdfbookmark[0]{Lecture 3: Magic (i.e. decorators and metaclasses)}{lecture-3-magic-i-e-decorators-and-metaclasses} +\section*{Lecture 3: Magic (i.e. decorators and metaclasses)} + + +%___________________________________________________________________________ + +\hypertarget{part-i-decorators}{} +\pdfbookmark[1]{Part I: decorators}{part-i-decorators} +\subsection*{Part I: decorators} + +Decorators are just sugar: their functionality was already in the language +\begin{verbatim}>>> def s(): pass +>>> s = staticmethod(s)\end{verbatim} +\begin{verbatim}>>> @staticmethod +... def s(): pass +...\end{verbatim} + +However sugar \emph{does} matter. + + +%___________________________________________________________________________ + +\hypertarget{a-typical-decorator-traced}{} +\pdfbookmark[2]{A typical decorator: traced}{a-typical-decorator-traced} +\subsubsection*{A typical decorator: traced} +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<traced.py>~\\ +~\\ +def~traced(func):~\\ +~~~~def~tracedfunc(*args,~**kw):~\\ +~~~~~~~~print~"calling~{\%}s.{\%}s"~{\%}~(func.{\_}{\_}module{\_}{\_},~func.{\_}{\_}name{\_}{\_})~\\ +~~~~~~~~return~func(*args,~**kw)~\\ +~~~~tracedfunc.{\_}{\_}name{\_}{\_}~=~func.{\_}{\_}name{\_}{\_}~\\ +~~~~return~tracedfunc~\\ +~\\ +@traced~\\ +def~f():~pass~\\ +~\\ +{\#}</traced.py> +}\end{quote} +\begin{verbatim}>>> from traced import f +>>> f() +calling traced.f\end{verbatim} + + +%___________________________________________________________________________ + +\hypertarget{a-decorator-factory-timed}{} +\pdfbookmark[2]{A decorator factory: Timed}{a-decorator-factory-timed} +\subsubsection*{A decorator factory: Timed} +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<timed.py>~\\ +~\\ +import~sys,~time~\\ +~\\ +class~Timed(object):~\\ +~~~~"{}"{}"Decorator~factory:~each~decorator~object~wraps~a~function~and~~\\ +~~~~executes~it~many~times~(default~100~times).~\\ +~~~~The~average~time~spent~in~one~iteration,~expressed~in~milliseconds,~~\\ +~~~~is~stored~in~the~attributes~wrappedfunc.time~and~wrappedfunc.clocktime,~\\ +~~~~and~displayed~into~a~log~file~which~defaults~to~stdout.~\\ +~~~~"{}"{}"~\\ +~~~~def~{\_}{\_}init{\_}{\_}(self,~repeat=100,~logfile=sys.stdout):~\\ +~~~~~~~~self.repeat~=~repeat~\\ +~~~~~~~~self.logfile~=~logfile~\\ +~~~~def~{\_}{\_}call{\_}{\_}(self,~func):~\\ +~~~~~~~~def~wrappedfunc(*args,~**kw):~\\ +~~~~~~~~~~~~fullname~=~"{\%}s.{\%}s~..."~{\%}~(func.{\_}{\_}module{\_}{\_},~func.func{\_}name)~\\ +~~~~~~~~~~~~print~>{}>~self.logfile,~'Executing~{\%}s'~{\%}~fullname.ljust(30),~\\ +~~~~~~~~~~~~time1~=~time.time()~\\ +~~~~~~~~~~~~clocktime1~=~time.clock()~\\ +~~~~~~~~~~~~for~i~in~xrange(self.repeat):~\\ +~~~~~~~~~~~~~~~~res~=~func(*args,**kw)~{\#}~executes~func~self.repeat~times~\\ +~~~~~~~~~~~~time2~=~time.time()~\\ +~~~~~~~~~~~~clocktime2~=~time.clock()~\\ +~~~~~~~~~~~~wrappedfunc.time~=~1000*(time2-time1)/self.repeat~\\ +~~~~~~~~~~~~wrappedfunc.clocktime~=~1000*(clocktime2~-~clocktime1)/self.repeat~\\ +~~~~~~~~~~~~print~>{}>~self.logfile,~{\textbackslash}~\\ +~~~~~~~~~~~~~~~~~~'Real~time:~{\%}s~ms;'~{\%}~self.rounding(wrappedfunc.time),~\\ +~~~~~~~~~~~~print~>{}>~self.logfile,~{\textbackslash}~\\ +~~~~~~~~~~~~~~~~~~'Clock~time:~{\%}s~ms'~{\%}~self.rounding(wrappedfunc.clocktime)~\\ +~~~~~~~~~~~~return~res~\\ +~~~~~~~~wrappedfunc.func{\_}name~=~func.func{\_}name~\\ +~~~~~~~~wrappedfunc.{\_}{\_}module{\_}{\_}~=~func.{\_}{\_}module{\_}{\_}~\\ +~~~~~~~~return~wrappedfunc~\\ +~~~~@staticmethod~\\ +~~~~def~rounding(float{\_}):~\\ +~~~~~~~~"Three~digits~rounding~for~small~numbers,~1~digit~rounding~otherwise."~\\ +~~~~~~~~if~float{\_}~<~10.:~\\ +~~~~~~~~~~~~return~"{\%}5.3f"~{\%}~float{\_}~\\ +~~~~~~~~else:~\\ +~~~~~~~~~~~~return~"{\%}5.1f"~{\%}~float{\_}~\\ +~~~~\\ +{\#}</timed.py> +}\end{quote} +\begin{verbatim}>>> from timed import Timed +>>> from random import sample +>>> example_ls = sample(xrange(1000000), 1000) +>>> @Timed() +... def list_sort(ls): +... ls.sort() +... +>>> list_sort(example_ls) #doctest: +ELLIPSIS +Executing __main__.list_sort ... Real time: 0... ms; Clock time: 0... ms\end{verbatim} + + +%___________________________________________________________________________ + +\hypertarget{a-powerful-decorator-pattern}{} +\pdfbookmark[2]{A powerful decorator pattern}{a-powerful-decorator-pattern} +\subsubsection*{A powerful decorator pattern} +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<traced{\_}function2.py>~\\ +~\\ +from~decorators~import~decorator~\\ +~\\ +def~trace(f,~*args,~**kw):~\\ +~~~~print~"calling~{\%}s~with~args~{\%}s,~{\%}s"~{\%}~(f.func{\_}name,~args,~kw)~~\\ +~~~~return~f(*args,~**kw)~\\ +~\\ +traced{\_}function~=~decorator(trace)~\\ +~\\ +@traced{\_}function~\\ +def~f1(x):~\\ +~~~~pass~\\ +~\\ +@traced{\_}function~\\ +def~f2(x,~y):~\\ +~~~~pass~\\ +~\\ +{\#}</traced{\_}function2.py> +}\end{quote} +\begin{verbatim}>>> from traced_function2 import traced_function, f1, f2 +>>> f1(1) +calling f1 with args (1,), {} +>>> f2(1,2) +calling f2 with args (1, 2), {}\end{verbatim} + +works with pydoc: +\begin{quote}{\ttfamily \raggedright \noindent +{\$}~pydoc2.4~traced{\_}function2.f2~~~\\ +Help~on~function~f1~in~traced{\_}function2:~\\ +~\\ +traced{\_}function2.f1~=~f1(x)~\\ +~\\ +{\$}~pydoc2.4~traced{\_}function2.f2~~~\\ +Help~on~function~f2~in~traced{\_}function2:~\\ +~\\ +traced{\_}function2.f2~=~f2(x,~y) +}\end{quote} + +Here is the source code: +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<decorators.py>~\\ +~\\ +def~getinfo(func):~\\ +~~~~"{}"{}"Return~an~info~dictionary~containing:~\\ +~~~~-~name~(the~name~of~the~function~:~str)~\\ +~~~~-~argnames~(the~names~of~the~arguments~:~list)~\\ +~~~~-~defarg~(the~values~of~the~default~arguments~:~list)~\\ +~~~~-~fullsign~(the~full~signature~:~str)~\\ +~~~~-~shortsign~(the~short~signature~:~str)~\\ +~~~~-~arg0~...~argn~(shortcuts~for~the~names~of~the~arguments)~\\ +~\\ +~~~~>{}>{}>~def~f(self,~x=1,~y=2,~*args,~**kw):~pass~\\ +~\\ +~~~~>{}>{}>~info~=~getinfo(f)~\\ +~\\ +~~~~>{}>{}>~info{[}"name"]~\\ +~~~~'f'~\\ +~~~~>{}>{}>~info{[}"argnames"]~\\ +~~~~{[}'self',~'x',~'y',~'args',~'kw']~\\ +~~~~~\\ +~~~~>{}>{}>~info{[}"defarg"]~\\ +~~~~(1,~2)~\\ +~\\ +~~~~>{}>{}>~info{[}"shortsign"]~\\ +~~~~'self,~x,~y,~*args,~**kw'~\\ +~~~~~\\ +~~~~>{}>{}>~info{[}"fullsign"]~\\ +~~~~'self,~x=defarg{[}0],~y=defarg{[}1],~*args,~**kw'~\\ +~\\ +~~~~>{}>{}>~info{[}"arg0"],~info{[}"arg1"],~info{[}"arg2"],~info{[}"arg3"],~info{[}"arg4"]~\\ +~~~~('self',~'x',~'y',~'args',~'kw')~\\ +~~~~"{}"{}"~\\ +~~~~assert~inspect.ismethod(func)~or~inspect.isfunction(func)~\\ +~~~~regargs,~varargs,~varkwargs,~defaults~=~inspect.getargspec(func)~\\ +~~~~argnames~=~list(regargs)~\\ +~~~~if~varargs:~argnames.append(varargs)~\\ +~~~~if~varkwargs:~argnames.append(varkwargs)~\\ +~~~~counter~=~itertools.count()~\\ +~~~~fullsign~=~inspect.formatargspec(~\\ +~~~~~~~~regargs,~varargs,~varkwargs,~defaults,~\\ +~~~~~~~~formatvalue=lambda~value:~"=defarg{[}{\%}i]"~{\%}~counter.next()){[}1:-1]~\\ +~~~~shortsign~=~inspect.formatargspec(~\\ +~~~~~~~~regargs,~varargs,~varkwargs,~defaults,~\\ +~~~~~~~~formatvalue=lambda~value:~"{}"){[}1:-1]~\\ +~~~~dic~=~dict(("arg{\%}s"~{\%}~n,~name)~for~n,~name~in~enumerate(argnames))~\\ +~~~~dic.update(name=func.{\_}{\_}name{\_}{\_},~argnames=argnames,~shortsign=shortsign,~\\ +~~~~~~~~fullsign~=~fullsign,~defarg~=~func.func{\_}defaults~or~())~\\ +~~~~return~dic~\\ +~\\ +def~{\_}contains{\_}reserved{\_}names(dic):~{\#}~helper~\\ +~~~~return~"{\_}call{\_}"~in~dic~or~"{\_}func{\_}"~in~dic~\\ +~\\ +def~{\_}decorate(func,~caller):~\\ +~~~~"{}"{}"Takes~a~function~and~a~caller~and~returns~the~function~\\ +~~~~decorated~with~that~caller.~The~decorated~function~is~obtained~\\ +~~~~by~evaluating~a~lambda~function~with~the~correct~signature.~\\ +~~~~"{}"{}"~\\ +~~~~infodict~=~getinfo(func)~\\ +~~~~assert~not~{\_}contains{\_}reserved{\_}names(infodict{[}"argnames"]),~{\textbackslash}~\\ +~~~~~~~~~~~"You~cannot~use~{\_}call{\_}~or~{\_}func{\_}~as~argument~names!"~\\ +~~~~execdict=dict({\_}func{\_}=func,~{\_}call{\_}=caller,~defarg=func.func{\_}defaults~or~())~\\ +~~~~if~func.{\_}{\_}name{\_}{\_}~==~"<lambda>":~\\ +~~~~~~~~lambda{\_}src~=~"lambda~{\%}(fullsign)s:~{\_}call{\_}({\_}func{\_},~{\%}(shortsign)s)"~{\textbackslash}~\\ +~~~~~~~~~~~~~~~~~~~~~{\%}~infodict~\\ +~~~~~~~~dec{\_}func~=~eval(lambda{\_}src,~execdict)~\\ +~~~~else:~\\ +~~~~~~~~func{\_}src~=~"{}"{}"def~{\%}(name)s({\%}(fullsign)s):~\\ +~~~~~~~~return~{\_}call{\_}({\_}func{\_},~{\%}(shortsign)s)"{}"{}"~{\%}~infodict~\\ +~~~~~~~~exec~func{\_}src~in~execdict~~\\ +~~~~~~~~dec{\_}func~=~execdict{[}func.{\_}{\_}name{\_}{\_}]~\\ +~~~~{\#}import~sys;~print~>{}>~sys.stderr,~func{\_}src~{\#}~for~debugging~~\\ +~~~~dec{\_}func.{\_}{\_}doc{\_}{\_}~=~func.{\_}{\_}doc{\_}{\_}~\\ +~~~~dec{\_}func.{\_}{\_}dict{\_}{\_}~=~func.{\_}{\_}dict{\_}{\_}~\\ +~~~~return~dec{\_}func~\\ +~\\ +class~decorator(object):~\\ +~~~~"{}"{}"General~purpose~decorator~factory:~takes~a~caller~function~as~\\ +input~and~returns~a~decorator.~A~caller~function~is~any~function~like~this::~\\ +~\\ +~~~~def~caller(func,~*args,~**kw):~\\ +~~~~~~~~{\#}~do~something~\\ +~~~~~~~~return~func(*args,~**kw)~\\ +~~~~~\\ +Here~is~an~example~of~usage:~\\ +~\\ +~~~~>{}>{}>~@decorator~\\ +~~~~...~def~chatty(f,~*args,~**kw):~\\ +~~~~...~~~~~print~"Calling~{\%}r"~{\%}~f.{\_}{\_}name{\_}{\_}~\\ +~~~~...~~~~~return~f(*args,~**kw)~\\ +~~~~~\\ +~~~~>{}>{}>~@chatty~\\ +~~~~...~def~f():~pass~\\ +~~~~...~\\ +~~~~>{}>{}>~f()~\\ +~~~~Calling~'f'~\\ +~~~~"{}"{}"~\\ +~~~~def~{\_}{\_}init{\_}{\_}(self,~caller):~\\ +~~~~~~~~self.caller~=~caller~\\ +~~~~def~{\_}{\_}call{\_}{\_}(self,~func):~\\ +~~~~~~~~return~{\_}decorate(func,~self.caller)~\\ +~\\ +~\\ +{\#}</decorators.py> +}\end{quote} + +The possibilities of this pattern are endless. + + +%___________________________________________________________________________ + +\hypertarget{a-deferred-decorator}{} +\pdfbookmark[2]{A deferred decorator}{a-deferred-decorator} +\subsubsection*{A deferred decorator} + +You want to execute a procedure only after a certain time delay (for instance +for use within an asyncronous Web framework): +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<deferred.py>~\\ +"Deferring~the~execution~of~a~procedure~(function~returning~None)"~\\ +~\\ +import~threading~\\ +from~decorators~import~decorator~\\ +~\\ +def~deferred(nsec):~\\ +~~~~def~call{\_}later(func,~*args,~**kw):~\\ +~~~~~~~~return~threading.Timer(nsec,~func,~args,~kw).start()~\\ +~~~~return~decorator(call{\_}later)~\\ +~\\ +@deferred(2)~\\ +def~hello():~\\ +~~~~print~"hello"~\\ +~\\ +if~{\_}{\_}name{\_}{\_}~==~"{\_}{\_}main{\_}{\_}":~\\ +~~~~hello()~~~~~\\ +~~~~print~"Calling~hello()~..."~\\ +~~~~\\ +~\\ +{\#}</deferred.py>~\\ +~\\ +{\$}~python~deferred.py +}\end{quote} + +Show an example of an experimental decorator based web framework +(doctester{\_}frontend). + + +%___________________________________________________________________________ + +\hypertarget{part-ii-metaclasses}{} +\pdfbookmark[1]{Part II: metaclasses}{part-ii-metaclasses} +\subsection*{Part II: metaclasses} + +Metaclasses are there! Consider this example from a recent post on c.l.py: +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<BaseClass.py>~\\ +~\\ +class~BaseClass(object):~\\ +~~~"Do~something"~\\ +~\\ +{\#}</BaseClass.py> +}\end{quote} +\begin{verbatim}>>> import BaseClass # instead of 'from BaseClass import BaseClass' +>>> class C(BaseClass): pass +... +Traceback (most recent call last): + ... +TypeError: Error when calling the metaclass bases + module.__init__() takes at most 2 arguments (3 given)\end{verbatim} + +The reason for the error is that class \texttt{C(BaseClass): pass} is +actually calling the \texttt{type} metaclass with three arguments: +\begin{quote}{\ttfamily \raggedright \noindent +C~=~type("C",~(BaseClass,),~{\{}{\}}) +}\end{quote} + +\texttt{type.{\_}{\_}new{\_}{\_}} tries to use \texttt{type(BaseClass)} as metaclass, +but since BaseClass here is a module, and \texttt{ModuleType} is not +a metaclass, it cannot work. The error message reflects a conflict with +the signature of ModuleType which requires two parameters and not three. + +So even if you don't use them, you may want to know they exist. + + +%___________________________________________________________________________ + +\hypertarget{rejuvenating-old-style-classes}{} +\pdfbookmark[2]{Rejuvenating old-style classes}{rejuvenating-old-style-classes} +\subsubsection*{Rejuvenating old-style classes} +\begin{verbatim}>>> class Old: pass +>>> print type(Old) +<type 'classobj'>\end{verbatim} +\begin{verbatim}>>> __metaclass__ = type # to rejuvenate class +>>> class NotOld: pass +... +>>> print NotOld.__class__ +<type 'type'>\end{verbatim} + + +%___________________________________________________________________________ + +\hypertarget{a-typical-metaclass-example-metatracer}{} +\pdfbookmark[2]{A typical metaclass example: MetaTracer}{a-typical-metaclass-example-metatracer} +\subsubsection*{A typical metaclass example: MetaTracer} +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<metatracer.py>~\\ +~\\ +import~inspect~\\ +from~decorators~import~decorator~\\ +~\\ +@decorator~\\ +def~traced(meth,~*args,~**kw):~\\ +~~~~cls~=~meth.{\_}{\_}cls{\_}{\_}~\\ +~~~~modname~=~meth.{\_}{\_}module{\_}{\_}~or~cls.{\_}{\_}module{\_}{\_}~\\ +~~~~print~"calling~{\%}s.{\%}s.{\%}s"~{\%}~(modname,~cls.{\_}{\_}name{\_}{\_},~meth.{\_}{\_}name{\_}{\_})~\\ +~~~~return~meth(*args,~**kw)~\\ +~\\ +class~MetaTracer(type):~~~~~~~~~~~~~\\ +~~~~def~{\_}{\_}init{\_}{\_}(cls,~name,~bases,~dic):~\\ +~~~~~~~~super(MetaTracer,~cls).{\_}{\_}init{\_}{\_}(name,~bases,~dic)~\\ +~~~~~~~~for~k,~v~in~dic.iteritems():~\\ +~~~~~~~~~~~~if~inspect.isfunction(v):~\\ +~~~~~~~~~~~~~~~~v.{\_}{\_}cls{\_}{\_}~=~cls~{\#}~so~we~know~in~which~class~v~was~defined~\\ +~~~~~~~~~~~~~~~~setattr(cls,~k,~traced(v))~\\ +~\\ +{\#}</metatracer.py> +}\end{quote} + +Usage: exploring classes in the standard library +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<dictmixin.py>~\\ +~\\ +from~metatracer~import~MetaTracer~\\ +from~UserDict~import~DictMixin~\\ +~\\ +class~TracedDM(DictMixin,~object):~\\ +~~~~{\_}{\_}metaclass{\_}{\_}~=~MetaTracer~\\ +~~~~def~{\_}{\_}getitem{\_}{\_}(self,~item):~\\ +~~~~~~~~return~item~\\ +~~~~def~keys(self):~~\\ +~~~~~~~~return~{[}1,2,3]~\\ +~\\ +{\#}</dictmixin.py> +}\end{quote} +\begin{verbatim}>>> from dictmixin import TracedDM +>>> print TracedDM() +calling dictmixin.TracedDM.keys +calling dictmixin.TracedDM.__getitem__ +calling dictmixin.TracedDM.__getitem__ +calling dictmixin.TracedDM.__getitem__ +{1: 1, 2: 2, 3: 3}\end{verbatim} + + +%___________________________________________________________________________ + +\hypertarget{real-life-example-check-overriding}{} +\pdfbookmark[2]{Real life example: check overriding}{real-life-example-check-overriding} +\subsubsection*{Real life example: check overriding} +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<check{\_}overriding.py>~\\ +~\\ +class~Base(object):~\\ +~~~~a~=~0~\\ +~\\ +class~CheckOverriding(type):~\\ +~~~~"Prints~a~message~if~we~are~overriding~a~name."~\\ +~~~~def~{\_}{\_}new{\_}{\_}(mcl,~name,~bases,~dic):~\\ +~~~~~~~~for~name,~val~in~dic.iteritems():~\\ +~~~~~~~~~~~~if~name.startswith("{\_}{\_}")~and~name.endswith("{\_}{\_}"):~~\\ +~~~~~~~~~~~~~~~~continue~{\#}~ignore~special~names~\\ +~~~~~~~~~~~~a{\_}base{\_}has{\_}name~=~True~in~(hasattr(base,~name)~for~base~in~bases)~\\ +~~~~~~~~~~~~if~a{\_}base{\_}has{\_}name:~\\ +~~~~~~~~~~~~~~~~print~"AlreadyDefinedNameWarning:~"~+~name~\\ +~~~~~~~~return~super(CheckOverriding,~mcl).{\_}{\_}new{\_}{\_}(mcl,~name,~bases,~dic)~\\ +~\\ +class~MyClass(Base):~\\ +~~~~{\_}{\_}metaclass{\_}{\_}~=~CheckOverriding~\\ +~~~~a~=~1~\\ +~\\ +class~ChildClass(MyClass):~\\ +~~~~a~=~2 +}\end{quote} + +{\#}{\textless}/check{\_}overriding.py{\textgreater} +\begin{verbatim}>>> import check_overriding +AlreadyDefinedNameWarning: a +AlreadyDefinedNameWarning: a\end{verbatim} + + +%___________________________________________________________________________ + +\hypertarget{logfile}{} +\pdfbookmark[2]{LogFile}{logfile} +\subsubsection*{LogFile} +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<logfile.py>~\\ +~\\ +import~subprocess~\\ +~\\ +def~memoize(func):~\\ +~~~~memoize{\_}dic~=~{\{}{\}}~\\ +~~~~def~wrapped{\_}func(*args):~\\ +~~~~~~~~if~args~in~memoize{\_}dic:~\\ +~~~~~~~~~~~~return~memoize{\_}dic{[}args]~\\ +~~~~~~~~else:~\\ +~~~~~~~~~~~~result~=~func(*args)~\\ +~~~~~~~~~~~~memoize{\_}dic{[}args]~=~result~\\ +~~~~~~~~~~~~return~result~\\ +~~~~wrapped{\_}func.{\_}{\_}name{\_}{\_}~=~func.{\_}{\_}name{\_}{\_}~\\ +~~~~wrapped{\_}func.{\_}{\_}doc{\_}{\_}~=~func.{\_}{\_}doc{\_}{\_}~\\ +~~~~wrapped{\_}func.{\_}{\_}dict{\_}{\_}~=~func.{\_}{\_}dict{\_}{\_}~\\ +~~~~return~wrapped{\_}func~\\ +~\\ +class~Memoize(type):~{\#}~Singleton~is~a~special~case~of~Memoize~\\ +~~~~@memoize~\\ +~~~~def~{\_}{\_}call{\_}{\_}(cls,~*args):~\\ +~~~~~~~~return~super(Memoize,~cls).{\_}{\_}call{\_}{\_}(*args)~\\ +~\\ +class~LogFile(file):~\\ +~~~~"{}"{}"Open~a~file~for~append."{}"{}"~\\ +~~~~{\_}{\_}metaclass{\_}{\_}~=~Memoize~\\ +~~~~def~{\_}{\_}init{\_}{\_}(self,~name~=~"/tmp/err.log"):~\\ +~~~~~~~~self.viewer{\_}cmd~=~'xterm~-e~less'.split()~\\ +~~~~~~~~super(LogFile,~self).{\_}{\_}init{\_}{\_}(name,~"a")~\\ +~\\ +~~~~def~display(self,~*ls):~\\ +~~~~~~~~"Use~'less'~to~display~the~log~file~in~a~separate~xterm."~\\ +~~~~~~~~print~>{}>~self,~"{\textbackslash}n".join(map(str,~ls));~self.flush()~\\ +~~~~~~~~subprocess.call(self.viewer{\_}cmd~+~{[}self.name])~\\ +~\\ +~~~~def~reset(self):~\\ +~~~~~~~~"Erase~the~log~file."~\\ +~~~~~~~~print~>{}>~file(self.name,~"w")~\\ +~\\ +if~{\_}{\_}name{\_}{\_}~==~"{\_}{\_}main{\_}{\_}":~{\#}~test~\\ +~~~~print~>{}>~LogFile(),~"hello"~\\ +~~~~print~>{}>~LogFile(),~"world"~\\ +~~~~LogFile().display()~\\ +~\\ +{\#}</logfile.py>~\\ +~\\ +{\$}~python~logfile.py +}\end{quote} + + +%___________________________________________________________________________ + +\hypertarget{cooperative-hierarchies}{} +\pdfbookmark[2]{Cooperative hierarchies}{cooperative-hierarchies} +\subsubsection*{Cooperative hierarchies} +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<cooperative{\_}init.py>~\\ +~\\ +"{}"{}"Given~a~hierarchy,~makes~{\_}{\_}init{\_}{\_}~cooperative.~\\ +The~only~change~needed~is~to~add~a~line~\\ +~\\ +~~~{\_}{\_}metaclass{\_}{\_}~=~CooperativeInit~\\ +~\\ +to~the~base~class~of~your~hierarchy."{}"{}"~\\ +~\\ +from~decorators~import~decorator~~\\ +~\\ +class~CooperativeInit(type):~\\ +~~~~def~{\_}{\_}init{\_}{\_}(cls,~name,~bases,~dic):~\\ +~\\ +~~~~~~~~@decorator~\\ +~~~~~~~~def~make{\_}cooperative({\_}{\_}init{\_}{\_},~self,~*args,~**kw):~\\ +~~~~~~~~~~~~super(cls,~self).{\_}{\_}init{\_}{\_}(*args,~**kw)~\\ +~~~~~~~~~~~~{\_}{\_}init{\_}{\_}(self,~*args,~**kw)~\\ +~\\ +~~~~~~~~{\_}{\_}init{\_}{\_}~=~dic.get("{\_}{\_}init{\_}{\_}")~\\ +~~~~~~~~if~{\_}{\_}init{\_}{\_}:~\\ +~~~~~~~~~~~~cls.{\_}{\_}init{\_}{\_}~=~make{\_}cooperative({\_}{\_}init{\_}{\_})~\\ +~\\ +class~Base:~\\ +~~~~{\_}{\_}metaclass{\_}{\_}~=~CooperativeInit~\\ +~~~~def~{\_}{\_}init{\_}{\_}(self):~\\ +~~~~~~~~print~"B.{\_}{\_}init{\_}{\_}"~\\ +~\\ +class~C1(Base):~\\ +~~~~def~{\_}{\_}init{\_}{\_}(self):~\\ +~~~~~~~~print~"C1.{\_}{\_}init{\_}{\_}"~\\ +~\\ +class~C2(Base):~\\ +~~~def~{\_}{\_}init{\_}{\_}(self):~\\ +~~~~~~~print~"C2.{\_}{\_}init{\_}{\_}"~\\ +~\\ +class~D(C1,~C2):~\\ +~~~~def~{\_}{\_}init{\_}{\_}(self):~\\ +~~~~~~~~print~"D.{\_}{\_}init{\_}{\_}"~\\ +~\\ +{\#}</cooperative{\_}init.py> +}\end{quote} +\begin{verbatim}>>> from cooperative_init import D +>>> d = D() +B.__init__ +C2.__init__ +C1.__init__ +D.__init__\end{verbatim} + + +%___________________________________________________________________________ + +\hypertarget{metaclass-enhanced-modules}{} +\pdfbookmark[2]{Metaclass-enhanced modules}{metaclass-enhanced-modules} +\subsubsection*{Metaclass-enhanced modules} +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<import{\_}with{\_}metaclass.py>~\\ +"{}"{}"~\\ +`{}`import{\_}with{\_}metaclass(metaclass,~modulepath)`{}`~generates~\\ +a~new~module~from~and~old~module,~by~enhancing~all~of~its~classes.~\\ +This~is~not~perfect,~but~it~should~give~you~a~start."{}"{}"~\\ +~\\ +import~os,~sys,~inspect,~types~\\ +~\\ +def~import{\_}with{\_}metaclass(metaclass,~modulepath):~\\ +~~~~modname~=~os.path.basename(modulepath){[}:-3]~{\#}~simplistic~\\ +~~~~mod~=~types.ModuleType(modname)~\\ +~~~~locs~=~dict(~\\ +~~~~~~~~{\_}{\_}module{\_}{\_}~=~modname,~\\ +~~~~~~~~{\_}{\_}metaclass{\_}{\_}~=~metaclass,~\\ +~~~~~~~~object~=~metaclass("object",~(),~{\{}{\}}))~\\ +~~~~execfile(modulepath,~locs)~\\ +~~~~for~k,~v~in~locs.iteritems():~\\ +~~~~~~~~if~inspect.isclass(v):~{\#}~otherwise~it~would~be~"{\_}{\_}builtin{\_}{\_}"~\\ +~~~~~~~~~~~~v.{\_}{\_}module{\_}{\_}~=~"{\_}{\_}dynamic{\_}{\_}"~\\ +~~~~~~~~setattr(mod,~k,~v)~\\ +~~~~return~mod +}\end{quote} + +{\#}{\textless}/import{\_}with{\_}metaclass.py{\textgreater} +\begin{verbatim}>>> from import_with_metaclass import import_with_metaclass +>>> from metatracer import MetaTracer +>>> traced_optparse = import_with_metaclass(MetaTracer, +... "/usr/lib/python2.4/optparse.py") +>>> op = traced_optparse.OptionParser() +calling __dynamic__.OptionParser.__init__ +calling __dynamic__.OptionContainer.__init__ +calling __dynamic__.OptionParser._create_option_list +calling __dynamic__.OptionContainer._create_option_mappings +calling __dynamic__.OptionContainer.set_conflict_handler +calling __dynamic__.OptionContainer.set_description +calling __dynamic__.OptionParser.set_usage +calling __dynamic__.IndentedHelpFormatter.__init__ +calling __dynamic__.HelpFormatter.__init__ +calling __dynamic__.HelpFormatter.set_parser +calling __dynamic__.OptionParser._populate_option_list +calling __dynamic__.OptionParser._add_help_option +calling __dynamic__.OptionContainer.add_option +calling __dynamic__.Option.__init__ +calling __dynamic__.Option._check_opt_strings +calling __dynamic__.Option._set_opt_strings +calling __dynamic__.Option._set_attrs +calling __dynamic__.OptionContainer._check_conflict +calling __dynamic__.OptionParser._init_parsing_state\end{verbatim} + +traced{\_}optparse is a dynamically generated module not leaving in the +file system. + + +%___________________________________________________________________________ + +\hypertarget{magic-properties}{} +\pdfbookmark[2]{Magic properties}{magic-properties} +\subsubsection*{Magic properties} +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<magicprop.py>~\\ +~\\ +class~MagicProperties(type):~\\ +~~~~def~{\_}{\_}init{\_}{\_}(cls,~name,~bases,~dic):~\\ +~~~~~~~~prop{\_}names~=~set(name{[}3:]~for~name~in~dic~\\ +~~~~~~~~~~~~~~~~~~~~~~~~~if~name.startswith("get")~\\ +~~~~~~~~~~~~~~~~~~~~~~~~~or~name.startswith("set"))~\\ +~~~~~~~~for~name~in~prop{\_}names:~\\ +~~~~~~~~~~~~getter~=~getattr(cls,~"get"~+~name,~None)~\\ +~~~~~~~~~~~~setter~=~getattr(cls,~"set"~+~name,~None)~\\ +~~~~~~~~~~~~setattr(cls,~name,~property(getter,~setter))~\\ +~\\ +class~Base(object):~\\ +~~~~{\_}{\_}metaclass{\_}{\_}~=~MagicProperties~\\ +~~~~def~getx(self):~\\ +~~~~~~~~return~self.{\_}x~\\ +~~~~def~setx(self,~value):~\\ +~~~~~~~~self.{\_}x~=~value~\\ +~\\ +class~Child(Base):~\\ +~~~~def~getx(self):~\\ +~~~~~~~~print~"getting~x"~\\ +~~~~~~~~return~super(Child,~self).getx()~~\\ +~~~~def~setx(self,~value):~\\ +~~~~~~~~print~"setting~x"~\\ +~~~~~~~~super(Child,~self).setx(value)~~\\ +~\\ +{\#}</magicprop.py> +}\end{quote} +\begin{verbatim}>>> from magicprop import Child +>>> c = Child() +>>> c.x = 1 +setting x +>>> print c.x +getting x +1\end{verbatim} + + +%___________________________________________________________________________ + +\hypertarget{hack-evil-properties}{} +\pdfbookmark[2]{Hack: evil properties}{hack-evil-properties} +\subsubsection*{Hack: evil properties} +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<evilprop.py>~\\ +~\\ +def~convert2property(name,~bases,~d):~\\ +~~~~return~property(d.get('get'),~d.get('set'),~\\ +~~~~~~~~~~~~~~~~~~~~d.get('del'),d.get('{\_}{\_}doc{\_}{\_}'))~\\ +~\\ +class~C(object):~\\ +~~~~class~x:~\\ +~~~~~~~~"{}"{}"An~evil~test~property"{}"{}"~\\ +~~~~~~~~{\_}{\_}metaclass{\_}{\_}~=~convert2property~\\ +~~~~~~~~def~get(self):~\\ +~~~~~~~~~~~~print~'Getting~{\%}s'~{\%}~self.{\_}x~\\ +~~~~~~~~~~~~return~self.{\_}x~\\ +~~~~~~~~def~set(self,~value):~\\ +~~~~~~~~~~~~self.{\_}x~=~value~\\ +~~~~~~~~~~~~print~'Setting~to',~value~\\ +~\\ +{\#}</evilprop.py> +}\end{quote} +\begin{verbatim}>>> from evilprop import C +>>> c = C() +>>> c.x = 5 +Setting to 5 +>>> c.x +Getting 5 +5 +>>> print C.x.__doc__ +An evil test property\end{verbatim} + + +%___________________________________________________________________________ + +\hypertarget{why-i-suggest-not-to-use-metaclasses-in-production-code}{} +\pdfbookmark[2]{Why I suggest not to use metaclasses in production code}{why-i-suggest-not-to-use-metaclasses-in-production-code} +\subsubsection*{Why I suggest \emph{not} to use metaclasses in production code} +\begin{quote} +\begin{itemize} +\item {} +there are very few good use case for metaclasses in production code +(i.e. 99{\%} of time you don't need them) + +\item {} +they put a cognitive burden on the developer; + +\item {} +a design without metaclasses is less magic and likely more robust; + +\item {} +a design with metaclasses makes it difficult to use other metaclasses +for debugging. + +\end{itemize} +\end{quote} + +As far as I know, string.Template is the only metaclass-enhanced class +in the standard library; the metaclass is used to give the possibility to +change the defaults: +\begin{quote}{\ttfamily \raggedright \noindent +delimiter~=~'{\$}'~\\ +idpattern~=~r'{[}{\_}a-z]{[}{\_}a-z0-9]*' +}\end{quote} + +in subclasses of Template. +\begin{verbatim}>>> from string import Template +>>> from metatracer import MetaTracer +>>> class TracedTemplate(Template): +... __metaclass__ = MetaTracer +... +Traceback (most recent call last): + ... +TypeError: Error when calling the metaclass bases + metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases\end{verbatim} + +Solution: use a consistent metaclass +\begin{verbatim}>>> class GoodMeta(MetaTracer, type(Template)): pass +... +>>> class TracedTemplate(Template): +... __metaclass__ = GoodMeta\end{verbatim} + + +%___________________________________________________________________________ + +\hypertarget{is-there-an-automatic-way-of-solving-the-conflict}{} +\pdfbookmark[2]{Is there an automatic way of solving the conflict?}{is-there-an-automatic-way-of-solving-the-conflict} +\subsubsection*{Is there an automatic way of solving the conflict?} + +Yes, but you really need to be a metaclass wizard. + +\href{http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/204197}{http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/204197} +\begin{verbatim}>>> from noconflict import classmaker +>>> class TracedTemplate(Template): +... __metaclass__ = classmaker((MetaTracer,)) +>>> print type(TracedTemplate) +<class 'noconflict._MetaTracer_TemplateMetaclass'>\end{verbatim} +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<noconflict.py>~\\ +~\\ +import~inspect,~types,~{\_}{\_}builtin{\_}{\_}~\\ +from~skip{\_}redundant~import~skip{\_}redundant~\\ +~\\ +memoized{\_}metaclasses{\_}map~=~{\{}{\}}~\\ +~\\ +{\#}~utility~function~\\ +def~remove{\_}redundant(metaclasses):~\\ +~~~skipset~=~set({[}types.ClassType])~\\ +~~~for~meta~in~metaclasses:~{\#}~determines~the~metaclasses~to~be~skipped~\\ +~~~~~~~skipset.update(inspect.getmro(meta){[}1:])~\\ +~~~return~tuple(skip{\_}redundant(metaclasses,~skipset))~\\ +~\\ +{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}~\\ +{\#}{\#}~now~the~core~of~the~module:~two~mutually~recursive~functions~{\#}{\#}~\\ +{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}~\\ +~\\ +def~get{\_}noconflict{\_}metaclass(bases,~left{\_}metas,~right{\_}metas):~\\ +~~~~"{}"{}"Not~intended~to~be~used~outside~of~this~module,~unless~you~know~\\ +~~~~what~you~are~doing."{}"{}"~\\ +~~~~{\#}~make~tuple~of~needed~metaclasses~in~specified~priority~order~\\ +~~~~metas~=~left{\_}metas~+~tuple(map(type,~bases))~+~right{\_}metas~\\ +~~~~needed{\_}metas~=~remove{\_}redundant(metas)~\\ +~\\ +~~~~{\#}~return~existing~confict-solving~meta,~if~any~\\ +~~~~if~needed{\_}metas~in~memoized{\_}metaclasses{\_}map:~\\ +~~~~~~return~memoized{\_}metaclasses{\_}map{[}needed{\_}metas]~\\ +~~~~{\#}~nope:~compute,~memoize~and~return~needed~conflict-solving~meta~\\ +~~~~elif~not~needed{\_}metas:~~~~~~~~~{\#}~wee,~a~trivial~case,~happy~us~\\ +~~~~~~~~meta~=~type~\\ +~~~~elif~len(needed{\_}metas)~==~1:~{\#}~another~trivial~case~\\ +~~~~~~~meta~=~needed{\_}metas{[}0]~\\ +~~~~{\#}~check~for~recursion,~can~happen~i.e.~for~Zope~ExtensionClasses~\\ +~~~~elif~needed{\_}metas~==~bases:~~\\ +~~~~~~~~raise~TypeError("Incompatible~root~metatypes",~needed{\_}metas)~\\ +~~~~else:~{\#}~gotta~work~...~\\ +~~~~~~~~metaname~=~'{\_}'~+~'{}'.join({[}m.{\_}{\_}name{\_}{\_}~for~m~in~needed{\_}metas])~\\ +~~~~~~~~meta~=~classmaker()(metaname,~needed{\_}metas,~{\{}{\}})~\\ +~~~~memoized{\_}metaclasses{\_}map{[}needed{\_}metas]~=~meta~\\ +~~~~return~meta~\\ +~\\ +def~classmaker(left{\_}metas=(),~right{\_}metas=()):~\\ +~~~def~make{\_}class(name,~bases,~adict):~\\ +~~~~~~~metaclass~=~get{\_}noconflict{\_}metaclass(bases,~left{\_}metas,~right{\_}metas)~\\ +~~~~~~~return~metaclass(name,~bases,~adict)~\\ +~~~return~make{\_}class~\\ +~\\ +{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}~\\ +{\#}{\#}~and~now~a~conflict-safe~replacement~for~'type'~~~~~~~~~~~~~~{\#}{\#}~~\\ +{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}{\#}~\\ +~\\ +{\_}{\_}type{\_}{\_}={\_}{\_}builtin{\_}{\_}.type~{\#}~the~aboriginal~'type'~\\ +{\#}~left~available~in~case~you~decide~to~rebind~{\_}{\_}builtin{\_}{\_}.type~\\ +~\\ +class~safetype({\_}{\_}type{\_}{\_}):~\\ +~~~~{\#}~this~is~REALLY~DEEP~MAGIC~\\ +~~~~"{}"{}"Overrides~the~`{}`{\_}{\_}new{\_}{\_}`{}`~method~of~the~`{}`type`{}`~metaclass,~making~the~\\ +~~~~generation~of~classes~conflict-proof."{}"{}"~\\ +~~~~def~{\_}{\_}new{\_}{\_}(mcl,~*args):~\\ +~~~~~~~~nargs~=~len(args)~\\ +~~~~~~~~if~nargs~==~1:~{\#}~works~as~{\_}{\_}builtin{\_}{\_}.type~\\ +~~~~~~~~~~~~return~{\_}{\_}type{\_}{\_}(args{[}0])~~\\ +~~~~~~~~elif~nargs~==~3:~{\#}~creates~the~class~using~the~appropriate~metaclass~\\ +~~~~~~~~~~~~n,~b,~d~=~args~{\#}~name,~bases~and~dictionary~\\ +~~~~~~~~~~~~meta~=~get{\_}noconflict{\_}metaclass(b,~(mcl,),~())~~\\ +~~~~~~~~~~~~if~meta~is~mcl:~{\#}~meta~is~trivial,~dispatch~to~the~default~{\_}{\_}new{\_}{\_}~\\ +~~~~~~~~~~~~~~~~return~super(safetype,~mcl).{\_}{\_}new{\_}{\_}(mcl,~n,~b,~d)~\\ +~~~~~~~~~~~~else:~{\#}~non-trivial~metaclass,~dispatch~to~the~right~{\_}{\_}new{\_}{\_}~\\ +~~~~~~~~~~~~~~~~{\#}~(it~will~take~a~second~round)~{\#}~print~mcl,~meta~\\ +~~~~~~~~~~~~~~~~return~super(mcl,~meta).{\_}{\_}new{\_}{\_}(meta,~n,~b,~d)~\\ +~~~~~~~~else:~\\ +~~~~~~~~~~~~raise~TypeError('{\%}s()~takes~1~or~3~arguments'~{\%}~mcl.{\_}{\_}name{\_}{\_})~\\ +~\\ +{\#}</noconflict.py> +}\end{quote} + +\end{document} + diff --git a/pypers/oxford/callsupermethod.py b/pypers/oxford/callsupermethod.py new file mode 100755 index 0000000..5565ad5 --- /dev/null +++ b/pypers/oxford/callsupermethod.py @@ -0,0 +1,123 @@ +class B(object): + def __new__(cls, *args, **kw): + print "B.__new__" + return super(B, cls).__new__(cls, *args, **kw) + def __init__(self, *args, **kw): + print "B.__init__" + super(B, self).__init__(*args, **kw) + +######################################################################## + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +from magicsuper import object + +class B(object): + def __new__(cls, *args, **kw): + print "B.__new__" + return callsupermethod(cls, *args, **kw) + def __init__(self, *args, **kw): + print "B.__init__" + callsupermethod(*args, **kw) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @staticmethod + def sm(): + print "B.sm" + @classmethod + def cm(cls): + print cls.__name__ + + +class C(B): + def __new__(cls, *args, **kw): + print args, kw + return callsupermethod(cls, *args, **kw) + @staticmethod + def sm(): + callsupermethod() + @classmethod + def cm(cls): + callsupermethod() + + +c = C(1, x=2) +c.sm() +c.cm() diff --git a/pypers/oxford/check_overriding.py b/pypers/oxford/check_overriding.py new file mode 100755 index 0000000..5157757 --- /dev/null +++ b/pypers/oxford/check_overriding.py @@ -0,0 +1,24 @@ +# check_overriding.py
+
+class Base(object):
+ a = 0
+
+class CheckOverriding(type):
+ "Prints a message if we are overriding a name."
+ def __new__(mcl, name, bases, dic):
+ for name, val in dic.iteritems():
+ if name.startswith("__") and name.endswith("__"):
+ continue # ignore special names
+ a_base_has_name = True in (hasattr(base, name) for base in bases)
+ if a_base_has_name:
+ print "AlreadyDefinedNameWarning: " + name
+ return super(CheckOverriding, mcl).__new__(mcl, name, bases, dic)
+
+class MyClass(Base):
+ __metaclass__ = CheckOverriding
+ a = 1
+
+class ChildClass(MyClass):
+ a = 2
+
+
diff --git a/pypers/oxford/chop.py b/pypers/oxford/chop.py new file mode 100755 index 0000000..3325d52 --- /dev/null +++ b/pypers/oxford/chop.py @@ -0,0 +1,14 @@ +# chop.py
+
+# see also http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/303279
+
+import itertools
+
+def chop(iterable, batchsize):
+ it = iter(iterable)
+ while True:
+ batch = list(itertools.islice(it, batchsize))
+ if batch: yield batch
+ else: break
+
+
diff --git a/pypers/oxford/cooperative_init.py b/pypers/oxford/cooperative_init.py new file mode 100755 index 0000000..0118115 --- /dev/null +++ b/pypers/oxford/cooperative_init.py @@ -0,0 +1,41 @@ +# cooperative_init.py
+
+"""Given a hierarchy, makes __init__ cooperative.
+The only change needed is to add a line
+
+ __metaclass__ = CooperativeInit
+
+to the base class of your hierarchy."""
+
+from decorators import decorator
+
+class CooperativeInit(type):
+ def __init__(cls, name, bases, dic):
+
+ @decorator
+ def make_cooperative(__init__, self, *args, **kw):
+ super(cls, self).__init__(*args, **kw)
+ __init__(self, *args, **kw)
+
+ __init__ = dic.get("__init__")
+ if __init__:
+ cls.__init__ = make_cooperative(__init__)
+
+class Base:
+ __metaclass__ = CooperativeInit
+ def __init__(self):
+ print "B.__init__"
+
+class C1(Base):
+ def __init__(self):
+ print "C1.__init__"
+
+class C2(Base):
+ def __init__(self):
+ print "C2.__init__"
+
+class D(C1, C2):
+ def __init__(self):
+ print "D.__init__"
+
+
diff --git a/pypers/oxford/cript_user.py b/pypers/oxford/cript_user.py new file mode 100755 index 0000000..f17a903 --- /dev/null +++ b/pypers/oxford/cript_user.py @@ -0,0 +1,17 @@ +# cript_user.py + +from crypt import crypt + +def cryptedAttribute(seed): + def get(self): + return getattr(self, "_pw", None) + def set(self, value): + self._pw = crypt(value, seed) + return property(get, set) + +class User(object): + pw = cryptedAttribute("a") + def __init__(self, un, pw): + self.un, self.pw = un, pw + + diff --git a/pypers/oxford/crypt_user.py b/pypers/oxford/crypt_user.py new file mode 100755 index 0000000..ce23b6a --- /dev/null +++ b/pypers/oxford/crypt_user.py @@ -0,0 +1,20 @@ +# crypt_user.py
+
+class User(object):
+ def __init__(self, username, password):
+ self.username, self.password = username, password
+
+
+
+from crypt import crypt
+
+def cryptedAttribute(seed="x"):
+ def get(self):
+ return getattr(self, "_pw", None)
+ def set(self, value):
+ self._pw = crypt(value, seed)
+ return property(get, set)
+
+User.password = cryptedAttribute()
+
+
diff --git a/pypers/oxford/custom_iterable.py b/pypers/oxford/custom_iterable.py new file mode 100755 index 0000000..d4c9aa0 --- /dev/null +++ b/pypers/oxford/custom_iterable.py @@ -0,0 +1,9 @@ +# custom_iterable.py +class MyIter(object): + def __iter__(self): + yield 1 + yield 2 + yield 3 + +it = MyIter(); # print it, iter(it) + diff --git a/pypers/oxford/data.txt b/pypers/oxford/data.txt new file mode 100755 index 0000000..d0e1ec9 --- /dev/null +++ b/pypers/oxford/data.txt @@ -0,0 +1,3 @@ +value1 +value2 +END diff --git a/pypers/oxford/dec.py b/pypers/oxford/dec.py new file mode 100755 index 0000000..1220550 --- /dev/null +++ b/pypers/oxford/dec.py @@ -0,0 +1,25 @@ +class with_attrs(object): + def __init__(self, **kw): + self.kw = kw + def __call__(self, func): + print "the decorator was called with %s" % self.kw + def wrapped_func(): + print "%s was called" % func.__name__ + return func() + wrapped_func.__dict__.update(self.kw) + return wrapped_func + +@with_attrs(author="M.S.", date="2005-04-19") +def long_named_function(): + print "do something" + return "ok" + +#long_named_function = decorator(long_named_function) + + +print long_named_function.author +print long_named_function.date + +print "-" * 77 + +print long_named_function() diff --git a/pypers/oxford/decorate.py b/pypers/oxford/decorate.py new file mode 100755 index 0000000..a5663a3 --- /dev/null +++ b/pypers/oxford/decorate.py @@ -0,0 +1,3 @@ +# decorate.py + + diff --git a/pypers/oxford/decorators.py b/pypers/oxford/decorators.py new file mode 100755 index 0000000..d393658 --- /dev/null +++ b/pypers/oxford/decorators.py @@ -0,0 +1,104 @@ +# decorators.py
+
+import inspect, itertools
+
+def getinfo(func):
+ """Return an info dictionary containing:
+ - name (the name of the function : str)
+ - argnames (the names of the arguments : list)
+ - defarg (the values of the default arguments : list)
+ - fullsign (the full signature : str)
+ - shortsign (the short signature : str)
+ - arg0 ... argn (shortcuts for the names of the arguments)
+
+ >> def f(self, x=1, y=2, *args, **kw): pass
+
+ >> info = getinfo(f)
+
+ >> info["name"]
+ 'f'
+ >> info["argnames"]
+ ['self', 'x', 'y', 'args', 'kw']
+
+ >> info["defarg"]
+ (1, 2)
+
+ >> info["shortsign"]
+ 'self, x, y, *args, **kw'
+
+ >> info["fullsign"]
+ 'self, x=defarg[0], y=defarg[1], *args, **kw'
+
+ >> info["arg0"], info["arg1"], info["arg2"], info["arg3"], info["arg4"]
+ ('self', 'x', 'y', 'args', 'kw')
+ """
+ assert inspect.ismethod(func) or inspect.isfunction(func)
+ regargs, varargs, varkwargs, defaults = inspect.getargspec(func)
+ argnames = list(regargs)
+ if varargs: argnames.append(varargs)
+ if varkwargs: argnames.append(varkwargs)
+ counter = itertools.count()
+ fullsign = inspect.formatargspec(
+ regargs, varargs, varkwargs, defaults,
+ formatvalue=lambda value: "=defarg[%i]" % counter.next())[1:-1]
+ shortsign = inspect.formatargspec(
+ regargs, varargs, varkwargs, defaults,
+ formatvalue=lambda value: "")[1:-1]
+ dic = dict(("arg%s" % n, name) for n, name in enumerate(argnames))
+ dic.update(name=func.__name__, argnames=argnames, shortsign=shortsign,
+ fullsign = fullsign, defarg = func.func_defaults or ())
+ return dic
+
+def _contains_reserved_names(dic): # helper
+ return "_call_" in dic or "_func_" in dic
+
+def _decorate(func, caller):
+ """Takes a function and a caller and returns the function
+ decorated with that caller. The decorated function is obtained
+ by evaluating a lambda function with the correct signature.
+ """
+ infodict = getinfo(func)
+ assert not _contains_reserved_names(infodict["argnames"]), \
+ "You cannot use _call_ or _func_ as argument names!"
+ execdict=dict(_func_=func, _call_=caller, defarg=func.func_defaults or ())
+ if func.__name__ == "<lambda>":
+ lambda_src = "lambda %(fullsign)s: _call_(_func_, %(shortsign)s)" \
+ % infodict
+ dec_func = eval(lambda_src, execdict)
+ else:
+ func_src = """def %(name)s(%(fullsign)s):
+ return _call_(_func_, %(shortsign)s)""" % infodict
+ exec func_src in execdict
+ dec_func = execdict[func.__name__]
+ dec_func.__doc__ = func.__doc__
+ dec_func.__dict__ = func.__dict__
+ return dec_func
+
+class decorator(object):
+ """General purpose decorator factory: takes a caller function as
+input and returns a decorator. A caller function is any function like this::
+
+ def caller(func, *args, **kw):
+ # do something
+ return func(*args, **kw)
+
+Here is an example of usage:
+
+ >> @decorator
+ .. def chatty(f, *args, **kw):
+ .. print "Calling %r" % f.__name__
+ .. return f(*args, **kw)
+
+ >> @chatty
+ .. def f(): pass
+ ..
+ >> f()
+ Calling 'f'
+ """
+ def __init__(self, caller):
+ self.caller = caller
+ def __call__(self, func):
+ return _decorate(func, self.caller)
+
+
+
diff --git a/pypers/oxford/defaultdict.py b/pypers/oxford/defaultdict.py new file mode 100755 index 0000000..ce14508 --- /dev/null +++ b/pypers/oxford/defaultdict.py @@ -0,0 +1,21 @@ + +def defaultdict(default, dictclass=dict): + class defdict(dictclass): + def __getitem__(self, key): + try: + return super(defdict, self).__getitem__(key) + except KeyError: + return self.setdefault(key, default) + return defdict + +d = defaultdict(0)() +d["x"] += 1 +d["x"] += 1 +d["y"] += 1 +print d + +d = defaultdict(list())() +d["x"].append(1) +d["x"].append(2) +d["y"].append(1) +print d diff --git a/pypers/oxford/defaultdict2.py b/pypers/oxford/defaultdict2.py new file mode 100755 index 0000000..cf77b4c --- /dev/null +++ b/pypers/oxford/defaultdict2.py @@ -0,0 +1,20 @@ +def defaultdict(defaultfactory, dictclass=dict): + class defdict(dictclass): + def __getitem__(self, key): + try: + return super(defdict, self).__getitem__(key) + except KeyError: + return self.setdefault(key, defaultfactory()) + return defdict + +d = defaultdict(int)() +d["x"] += 1 +d["x"] += 1 +d["y"] += 1 +print d + +d = defaultdict(list)() +d["x"].append(1) +d["x"].append(2) +d["y"].append(1) +print d diff --git a/pypers/oxford/deferred.py b/pypers/oxford/deferred.py new file mode 100755 index 0000000..653421e --- /dev/null +++ b/pypers/oxford/deferred.py @@ -0,0 +1,21 @@ +# deferred.py
+"Deferring the execution of a procedure (function returning None)"
+
+import threading
+from decorators import decorator
+
+def deferred(nsec):
+ def call_later(func, *args, **kw):
+ return threading.Timer(nsec, func, args, kw).start()
+ return decorator(call_later)
+
+@deferred(2)
+def hello():
+ print "hello"
+
+if __name__ == "__main__":
+ hello()
+ print "Calling hello() ..."
+
+
+
diff --git a/pypers/oxford/descriptor.py b/pypers/oxford/descriptor.py new file mode 100755 index 0000000..4b7c6a9 --- /dev/null +++ b/pypers/oxford/descriptor.py @@ -0,0 +1,43 @@ +# descriptor.py
+
+
+class AttributeDescriptor(object):
+ def __get__(self, obj, cls=None):
+ if obj is None and cls is None:
+ raise TypeError("__get__(None, None) is invalid")
+ elif obj is None:
+ return self.get_from_class(cls)
+ else:
+ return self.get_from_obj(obj)
+ def get_from_class(self, cls):
+ print "Getting %s from %s" % (self, cls)
+ def get_from_obj(self, obj):
+ print "Getting %s from %s" % (self, obj)
+
+
+class Staticmethod(AttributeDescriptor):
+ def __init__(self, func):
+ self.func = func
+ def get_from_class(self, cls):
+ return self.func
+ get_from_obj = get_from_class
+
+
+class Classmethod(AttributeDescriptor):
+ def __init__(self, func):
+ self.func = func
+ def get_from_class(self, cls):
+ return self.func.__get__(cls, type(cls))
+ def get_from_obj(self, obj):
+ return self.get_from_class(obj.__class__)
+
+class C(object):
+ s = Staticmethod(lambda : 1)
+ c = Classmethod(lambda cls : cls.__name__)
+
+c = C()
+
+assert C.s() == c.s() == 1
+assert C.c() == c.c() == "C"
+
+
diff --git a/pypers/oxford/dictmixin.py b/pypers/oxford/dictmixin.py new file mode 100755 index 0000000..d91a9f2 --- /dev/null +++ b/pypers/oxford/dictmixin.py @@ -0,0 +1,13 @@ +# dictmixin.py
+
+from metatracer import MetaTracer
+from UserDict import DictMixin
+
+class TracedDM(DictMixin, object):
+ __metaclass__ = MetaTracer
+ def __getitem__(self, item):
+ return item
+ def keys(self):
+ return [1,2,3]
+
+
diff --git a/pypers/oxford/dictwrapper.py b/pypers/oxford/dictwrapper.py new file mode 100755 index 0000000..7ee8daa --- /dev/null +++ b/pypers/oxford/dictwrapper.py @@ -0,0 +1,7 @@ +# dictwrapper.py
+
+class DictWrapper(object):
+ def __init__(self, **kw):
+ self.__dict__.update(kw)
+
+
diff --git a/pypers/oxford/doctest_talk/Makefile b/pypers/oxford/doctest_talk/Makefile new file mode 100755 index 0000000..77a6cc0 --- /dev/null +++ b/pypers/oxford/doctest_talk/Makefile @@ -0,0 +1,5 @@ +talk: talk.txt + python2.4 maketalk.py talk.txt +test: P01.html + tidy P01.html > /dev/null 2> x.txt; less x.txt + diff --git a/pypers/oxford/doctest_talk/P01.html b/pypers/oxford/doctest_talk/P01.html new file mode 100755 index 0000000..a3a9e4d --- /dev/null +++ b/pypers/oxford/doctest_talk/P01.html @@ -0,0 +1,110 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> + <html> + <head> + <meta name="generator" content="Generated by Python"> + <title>P01</title> + +<STYLE TYPE="text/css"> + body { font-size: 160%; } +</STYLE> + + </head> +<body bgcolor="lightblue"> + + +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><small> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P02.html'>Next</a></td> <td bgcolor="lightblue"><a href='P25.html'>Prev</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>Last</a></td> <td bgcolor="lightblue"><a href='P01.html'>First</a></td> +</tr> +<tr> + <td bgcolor="lightblue">Current</td> <td bgcolor="lightblue">P01</td> +</tr> +<tr> + <td bgcolor="lightblue"></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P01.html'>P01</a></td> <td bgcolor="lightblue"><a href='P02.html'>P02</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P03.html'>P03</a></td> <td bgcolor="lightblue"><a href='P04.html'>P04</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P05.html'>P05</a></td> <td bgcolor="lightblue"><a href='P06.html'>P06</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P07.html'>P07</a></td> <td bgcolor="lightblue"><a href='P08.html'>P08</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P09.html'>P09</a></td> <td bgcolor="lightblue"><a href='P10.html'>P10</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P11.html'>P11</a></td> <td bgcolor="lightblue"><a href='P12.html'>P12</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P13.html'>P13</a></td> <td bgcolor="lightblue"><a href='P14.html'>P14</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P15.html'>P15</a></td> <td bgcolor="lightblue"><a href='P16.html'>P16</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P17.html'>P17</a></td> <td bgcolor="lightblue"><a href='P18.html'>P18</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P19.html'>P19</a></td> <td bgcolor="lightblue"><a href='P20.html'>P20</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P21.html'>P21</a></td> <td bgcolor="lightblue"><a href='P22.html'>P22</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P23.html'>P23</a></td> <td bgcolor="lightblue"><a href='P24.html'>P24</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>P25</a></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><img src = "accu2005.png" alt = "logo"></td> +</tr> +</table> +</td> +</tr> +</table> +</small></td> <td bgcolor="lightblue"><h1>Automatic testing in Python: wonderful doctest!</h1><br/> + +<center> + + ACCU Conference 2005 <br/> <br/> + + 22 Apr 2005 <br/> <br/> + + Michele Simionato <br/> <br/> + + michele.simionato@gmail.com <br/> <br/> + +</center></td> +</tr> +</table> + + </body> + +</html> diff --git a/pypers/oxford/doctest_talk/P02.html b/pypers/oxford/doctest_talk/P02.html new file mode 100755 index 0000000..b187e50 --- /dev/null +++ b/pypers/oxford/doctest_talk/P02.html @@ -0,0 +1,106 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> + <html> + <head> + <meta name="generator" content="Generated by Python"> + <title>P02</title> + +<STYLE TYPE="text/css"> + body { font-size: 160%; } +</STYLE> + + </head> +<body bgcolor="lightblue"> + + +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><small> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P03.html'>Next</a></td> <td bgcolor="lightblue"><a href='P01.html'>Prev</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>Last</a></td> <td bgcolor="lightblue"><a href='P01.html'>First</a></td> +</tr> +<tr> + <td bgcolor="lightblue">Current</td> <td bgcolor="lightblue">P02</td> +</tr> +<tr> + <td bgcolor="lightblue"></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P01.html'>P01</a></td> <td bgcolor="lightblue"><a href='P02.html'>P02</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P03.html'>P03</a></td> <td bgcolor="lightblue"><a href='P04.html'>P04</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P05.html'>P05</a></td> <td bgcolor="lightblue"><a href='P06.html'>P06</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P07.html'>P07</a></td> <td bgcolor="lightblue"><a href='P08.html'>P08</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P09.html'>P09</a></td> <td bgcolor="lightblue"><a href='P10.html'>P10</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P11.html'>P11</a></td> <td bgcolor="lightblue"><a href='P12.html'>P12</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P13.html'>P13</a></td> <td bgcolor="lightblue"><a href='P14.html'>P14</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P15.html'>P15</a></td> <td bgcolor="lightblue"><a href='P16.html'>P16</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P17.html'>P17</a></td> <td bgcolor="lightblue"><a href='P18.html'>P18</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P19.html'>P19</a></td> <td bgcolor="lightblue"><a href='P20.html'>P20</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P21.html'>P21</a></td> <td bgcolor="lightblue"><a href='P22.html'>P22</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P23.html'>P23</a></td> <td bgcolor="lightblue"><a href='P24.html'>P24</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>P25</a></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><img src = "accu2005.png" alt = "logo"></td> +</tr> +</table> +</td> +</tr> +</table> +</small></td> <td bgcolor="lightblue"><h1>Summary</h1><br/> + +<ul> + <li> What is automatic testing? </li> + <li> Why automatic testing is better? </li> + <li> Which kind of automatic testing? </li> + <li> How does it work, in practice? </li> + <li> What's the message?</li> +<ul></td> +</tr> +</table> + + </body> + +</html> diff --git a/pypers/oxford/doctest_talk/P03.html b/pypers/oxford/doctest_talk/P03.html new file mode 100755 index 0000000..dbfc5f8 --- /dev/null +++ b/pypers/oxford/doctest_talk/P03.html @@ -0,0 +1,102 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> + <html> + <head> + <meta name="generator" content="Generated by Python"> + <title>P03</title> + +<STYLE TYPE="text/css"> + body { font-size: 160%; } +</STYLE> + + </head> +<body bgcolor="lightblue"> + + +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><small> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P04.html'>Next</a></td> <td bgcolor="lightblue"><a href='P02.html'>Prev</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>Last</a></td> <td bgcolor="lightblue"><a href='P01.html'>First</a></td> +</tr> +<tr> + <td bgcolor="lightblue">Current</td> <td bgcolor="lightblue">P03</td> +</tr> +<tr> + <td bgcolor="lightblue"></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P01.html'>P01</a></td> <td bgcolor="lightblue"><a href='P02.html'>P02</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P03.html'>P03</a></td> <td bgcolor="lightblue"><a href='P04.html'>P04</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P05.html'>P05</a></td> <td bgcolor="lightblue"><a href='P06.html'>P06</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P07.html'>P07</a></td> <td bgcolor="lightblue"><a href='P08.html'>P08</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P09.html'>P09</a></td> <td bgcolor="lightblue"><a href='P10.html'>P10</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P11.html'>P11</a></td> <td bgcolor="lightblue"><a href='P12.html'>P12</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P13.html'>P13</a></td> <td bgcolor="lightblue"><a href='P14.html'>P14</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P15.html'>P15</a></td> <td bgcolor="lightblue"><a href='P16.html'>P16</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P17.html'>P17</a></td> <td bgcolor="lightblue"><a href='P18.html'>P18</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P19.html'>P19</a></td> <td bgcolor="lightblue"><a href='P20.html'>P20</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P21.html'>P21</a></td> <td bgcolor="lightblue"><a href='P22.html'>P22</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P23.html'>P23</a></td> <td bgcolor="lightblue"><a href='P24.html'>P24</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>P25</a></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><img src = "accu2005.png" alt = "logo"></td> +</tr> +</table> +</td> +</tr> +</table> +</small></td> <td bgcolor="lightblue"><h1>What is automatic testing</h1><br/> + +Any methodology that allows you to test +your application mechanically, repeatedly +and in a <em>controlled reproducible</em> way.</td> +</tr> +</table> + + </body> + +</html> diff --git a/pypers/oxford/doctest_talk/P04.html b/pypers/oxford/doctest_talk/P04.html new file mode 100755 index 0000000..b05ff1a --- /dev/null +++ b/pypers/oxford/doctest_talk/P04.html @@ -0,0 +1,108 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> + <html> + <head> + <meta name="generator" content="Generated by Python"> + <title>P04</title> + +<STYLE TYPE="text/css"> + body { font-size: 160%; } +</STYLE> + + </head> +<body bgcolor="lightblue"> + + +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><small> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P05.html'>Next</a></td> <td bgcolor="lightblue"><a href='P03.html'>Prev</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>Last</a></td> <td bgcolor="lightblue"><a href='P01.html'>First</a></td> +</tr> +<tr> + <td bgcolor="lightblue">Current</td> <td bgcolor="lightblue">P04</td> +</tr> +<tr> + <td bgcolor="lightblue"></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P01.html'>P01</a></td> <td bgcolor="lightblue"><a href='P02.html'>P02</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P03.html'>P03</a></td> <td bgcolor="lightblue"><a href='P04.html'>P04</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P05.html'>P05</a></td> <td bgcolor="lightblue"><a href='P06.html'>P06</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P07.html'>P07</a></td> <td bgcolor="lightblue"><a href='P08.html'>P08</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P09.html'>P09</a></td> <td bgcolor="lightblue"><a href='P10.html'>P10</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P11.html'>P11</a></td> <td bgcolor="lightblue"><a href='P12.html'>P12</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P13.html'>P13</a></td> <td bgcolor="lightblue"><a href='P14.html'>P14</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P15.html'>P15</a></td> <td bgcolor="lightblue"><a href='P16.html'>P16</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P17.html'>P17</a></td> <td bgcolor="lightblue"><a href='P18.html'>P18</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P19.html'>P19</a></td> <td bgcolor="lightblue"><a href='P20.html'>P20</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P21.html'>P21</a></td> <td bgcolor="lightblue"><a href='P22.html'>P22</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P23.html'>P23</a></td> <td bgcolor="lightblue"><a href='P24.html'>P24</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>P25</a></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><img src = "accu2005.png" alt = "logo"></td> +</tr> +</table> +</td> +</tr> +</table> +</small></td> <td bgcolor="lightblue"><h1>Automatic testing is better (1)</h1><br/> + +When doing manual testing typically you spend + +<center><h2> + + 1 hour of coding + 10 hours of testing/debugging + +</center></h2> + +on the other hand ...</td> +</tr> +</table> + + </body> + +</html> diff --git a/pypers/oxford/doctest_talk/P05.html b/pypers/oxford/doctest_talk/P05.html new file mode 100755 index 0000000..62b628a --- /dev/null +++ b/pypers/oxford/doctest_talk/P05.html @@ -0,0 +1,107 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> + <html> + <head> + <meta name="generator" content="Generated by Python"> + <title>P05</title> + +<STYLE TYPE="text/css"> + body { font-size: 160%; } +</STYLE> + + </head> +<body bgcolor="lightblue"> + + +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><small> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P06.html'>Next</a></td> <td bgcolor="lightblue"><a href='P04.html'>Prev</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>Last</a></td> <td bgcolor="lightblue"><a href='P01.html'>First</a></td> +</tr> +<tr> + <td bgcolor="lightblue">Current</td> <td bgcolor="lightblue">P05</td> +</tr> +<tr> + <td bgcolor="lightblue"></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P01.html'>P01</a></td> <td bgcolor="lightblue"><a href='P02.html'>P02</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P03.html'>P03</a></td> <td bgcolor="lightblue"><a href='P04.html'>P04</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P05.html'>P05</a></td> <td bgcolor="lightblue"><a href='P06.html'>P06</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P07.html'>P07</a></td> <td bgcolor="lightblue"><a href='P08.html'>P08</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P09.html'>P09</a></td> <td bgcolor="lightblue"><a href='P10.html'>P10</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P11.html'>P11</a></td> <td bgcolor="lightblue"><a href='P12.html'>P12</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P13.html'>P13</a></td> <td bgcolor="lightblue"><a href='P14.html'>P14</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P15.html'>P15</a></td> <td bgcolor="lightblue"><a href='P16.html'>P16</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P17.html'>P17</a></td> <td bgcolor="lightblue"><a href='P18.html'>P18</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P19.html'>P19</a></td> <td bgcolor="lightblue"><a href='P20.html'>P20</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P21.html'>P21</a></td> <td bgcolor="lightblue"><a href='P22.html'>P22</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P23.html'>P23</a></td> <td bgcolor="lightblue"><a href='P24.html'>P24</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>P25</a></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><img src = "accu2005.png" alt = "logo"></td> +</tr> +</table> +</td> +</tr> +</table> +</small></td> <td bgcolor="lightblue"><h1>Automatic testing is better (2)</h1><br/> + +... when doing automatic testing typically you spend + +<br/> <br/> +<center><h2> + + 1 hour of coding + 10 hours of testing/debugging ! + +</center></h2></td> +</tr> +</table> + + </body> + +</html> diff --git a/pypers/oxford/doctest_talk/P06.html b/pypers/oxford/doctest_talk/P06.html new file mode 100755 index 0000000..f4a18cb --- /dev/null +++ b/pypers/oxford/doctest_talk/P06.html @@ -0,0 +1,108 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> + <html> + <head> + <meta name="generator" content="Generated by Python"> + <title>P06</title> + +<STYLE TYPE="text/css"> + body { font-size: 160%; } +</STYLE> + + </head> +<body bgcolor="lightblue"> + + +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><small> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P07.html'>Next</a></td> <td bgcolor="lightblue"><a href='P05.html'>Prev</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>Last</a></td> <td bgcolor="lightblue"><a href='P01.html'>First</a></td> +</tr> +<tr> + <td bgcolor="lightblue">Current</td> <td bgcolor="lightblue">P06</td> +</tr> +<tr> + <td bgcolor="lightblue"></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P01.html'>P01</a></td> <td bgcolor="lightblue"><a href='P02.html'>P02</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P03.html'>P03</a></td> <td bgcolor="lightblue"><a href='P04.html'>P04</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P05.html'>P05</a></td> <td bgcolor="lightblue"><a href='P06.html'>P06</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P07.html'>P07</a></td> <td bgcolor="lightblue"><a href='P08.html'>P08</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P09.html'>P09</a></td> <td bgcolor="lightblue"><a href='P10.html'>P10</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P11.html'>P11</a></td> <td bgcolor="lightblue"><a href='P12.html'>P12</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P13.html'>P13</a></td> <td bgcolor="lightblue"><a href='P14.html'>P14</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P15.html'>P15</a></td> <td bgcolor="lightblue"><a href='P16.html'>P16</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P17.html'>P17</a></td> <td bgcolor="lightblue"><a href='P18.html'>P18</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P19.html'>P19</a></td> <td bgcolor="lightblue"><a href='P20.html'>P20</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P21.html'>P21</a></td> <td bgcolor="lightblue"><a href='P22.html'>P22</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P23.html'>P23</a></td> <td bgcolor="lightblue"><a href='P24.html'>P24</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>P25</a></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><img src = "accu2005.png" alt = "logo"></td> +</tr> +</table> +</td> +</tr> +</table> +</small></td> <td bgcolor="lightblue"><h1>However ...</h1><br/> + +Think about six month later! + <br/><br/> +<center><em> + + there is a difference</em> + + <h2><u>Refactoring!</u><h2> + +</center></td> +</tr> +</table> + + </body> + +</html> diff --git a/pypers/oxford/doctest_talk/P07.html b/pypers/oxford/doctest_talk/P07.html new file mode 100755 index 0000000..b6c31ae --- /dev/null +++ b/pypers/oxford/doctest_talk/P07.html @@ -0,0 +1,107 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> + <html> + <head> + <meta name="generator" content="Generated by Python"> + <title>P07</title> + +<STYLE TYPE="text/css"> + body { font-size: 160%; } +</STYLE> + + </head> +<body bgcolor="lightblue"> + + +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><small> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P08.html'>Next</a></td> <td bgcolor="lightblue"><a href='P06.html'>Prev</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>Last</a></td> <td bgcolor="lightblue"><a href='P01.html'>First</a></td> +</tr> +<tr> + <td bgcolor="lightblue">Current</td> <td bgcolor="lightblue">P07</td> +</tr> +<tr> + <td bgcolor="lightblue"></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P01.html'>P01</a></td> <td bgcolor="lightblue"><a href='P02.html'>P02</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P03.html'>P03</a></td> <td bgcolor="lightblue"><a href='P04.html'>P04</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P05.html'>P05</a></td> <td bgcolor="lightblue"><a href='P06.html'>P06</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P07.html'>P07</a></td> <td bgcolor="lightblue"><a href='P08.html'>P08</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P09.html'>P09</a></td> <td bgcolor="lightblue"><a href='P10.html'>P10</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P11.html'>P11</a></td> <td bgcolor="lightblue"><a href='P12.html'>P12</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P13.html'>P13</a></td> <td bgcolor="lightblue"><a href='P14.html'>P14</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P15.html'>P15</a></td> <td bgcolor="lightblue"><a href='P16.html'>P16</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P17.html'>P17</a></td> <td bgcolor="lightblue"><a href='P18.html'>P18</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P19.html'>P19</a></td> <td bgcolor="lightblue"><a href='P20.html'>P20</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P21.html'>P21</a></td> <td bgcolor="lightblue"><a href='P22.html'>P22</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P23.html'>P23</a></td> <td bgcolor="lightblue"><a href='P24.html'>P24</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>P25</a></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><img src = "accu2005.png" alt = "logo"></td> +</tr> +</table> +</td> +</tr> +</table> +</small></td> <td bgcolor="lightblue"><h1>Automatic testing in Python</h1><br/> + +There are two standard testing frameworks in Python: + +<ol> + <li> unittest </li> + <li> doctest </li> +</ol> + +Which one should I use?</td> +</tr> +</table> + + </body> + +</html> diff --git a/pypers/oxford/doctest_talk/P08.html b/pypers/oxford/doctest_talk/P08.html new file mode 100755 index 0000000..6c6c43e --- /dev/null +++ b/pypers/oxford/doctest_talk/P08.html @@ -0,0 +1,102 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> + <html> + <head> + <meta name="generator" content="Generated by Python"> + <title>P08</title> + +<STYLE TYPE="text/css"> + body { font-size: 160%; } +</STYLE> + + </head> +<body bgcolor="lightblue"> + + +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><small> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P09.html'>Next</a></td> <td bgcolor="lightblue"><a href='P07.html'>Prev</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>Last</a></td> <td bgcolor="lightblue"><a href='P01.html'>First</a></td> +</tr> +<tr> + <td bgcolor="lightblue">Current</td> <td bgcolor="lightblue">P08</td> +</tr> +<tr> + <td bgcolor="lightblue"></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P01.html'>P01</a></td> <td bgcolor="lightblue"><a href='P02.html'>P02</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P03.html'>P03</a></td> <td bgcolor="lightblue"><a href='P04.html'>P04</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P05.html'>P05</a></td> <td bgcolor="lightblue"><a href='P06.html'>P06</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P07.html'>P07</a></td> <td bgcolor="lightblue"><a href='P08.html'>P08</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P09.html'>P09</a></td> <td bgcolor="lightblue"><a href='P10.html'>P10</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P11.html'>P11</a></td> <td bgcolor="lightblue"><a href='P12.html'>P12</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P13.html'>P13</a></td> <td bgcolor="lightblue"><a href='P14.html'>P14</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P15.html'>P15</a></td> <td bgcolor="lightblue"><a href='P16.html'>P16</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P17.html'>P17</a></td> <td bgcolor="lightblue"><a href='P18.html'>P18</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P19.html'>P19</a></td> <td bgcolor="lightblue"><a href='P20.html'>P20</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P21.html'>P21</a></td> <td bgcolor="lightblue"><a href='P22.html'>P22</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P23.html'>P23</a></td> <td bgcolor="lightblue"><a href='P24.html'>P24</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>P25</a></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><img src = "accu2005.png" alt = "logo"></td> +</tr> +</table> +</td> +</tr> +</table> +</small></td> <td bgcolor="lightblue"><h1>Well,</h1><br/> + +since my talk has <em>doctest</em> in the title ... + + ;-)</td> +</tr> +</table> + + </body> + +</html> diff --git a/pypers/oxford/doctest_talk/P09.html b/pypers/oxford/doctest_talk/P09.html new file mode 100755 index 0000000..0e92531 --- /dev/null +++ b/pypers/oxford/doctest_talk/P09.html @@ -0,0 +1,114 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> + <html> + <head> + <meta name="generator" content="Generated by Python"> + <title>P09</title> + +<STYLE TYPE="text/css"> + body { font-size: 160%; } +</STYLE> + + </head> +<body bgcolor="lightblue"> + + +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><small> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P10.html'>Next</a></td> <td bgcolor="lightblue"><a href='P08.html'>Prev</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>Last</a></td> <td bgcolor="lightblue"><a href='P01.html'>First</a></td> +</tr> +<tr> + <td bgcolor="lightblue">Current</td> <td bgcolor="lightblue">P09</td> +</tr> +<tr> + <td bgcolor="lightblue"></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P01.html'>P01</a></td> <td bgcolor="lightblue"><a href='P02.html'>P02</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P03.html'>P03</a></td> <td bgcolor="lightblue"><a href='P04.html'>P04</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P05.html'>P05</a></td> <td bgcolor="lightblue"><a href='P06.html'>P06</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P07.html'>P07</a></td> <td bgcolor="lightblue"><a href='P08.html'>P08</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P09.html'>P09</a></td> <td bgcolor="lightblue"><a href='P10.html'>P10</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P11.html'>P11</a></td> <td bgcolor="lightblue"><a href='P12.html'>P12</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P13.html'>P13</a></td> <td bgcolor="lightblue"><a href='P14.html'>P14</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P15.html'>P15</a></td> <td bgcolor="lightblue"><a href='P16.html'>P16</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P17.html'>P17</a></td> <td bgcolor="lightblue"><a href='P18.html'>P18</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P19.html'>P19</a></td> <td bgcolor="lightblue"><a href='P20.html'>P20</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P21.html'>P21</a></td> <td bgcolor="lightblue"><a href='P22.html'>P22</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P23.html'>P23</a></td> <td bgcolor="lightblue"><a href='P24.html'>P24</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>P25</a></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><img src = "accu2005.png" alt = "logo"></td> +</tr> +</table> +</td> +</tr> +</table> +</small></td> <td bgcolor="lightblue"><h1>More seriously ...</h1><br/> + +Use different testing frameworks; each one has advantages +and disadvantages; use combinations of them; invent your +own testing procedure. + +I use combinations of + +<ul> + <li> unittest </li> + <li> doctest </li> + <li> custom tests </li> + <li> Makefile driven tests </li> + <li> et al. </li> +</ul> + +doctest emphasis is on <em>documentation</em></td> +</tr> +</table> + + </body> + +</html> diff --git a/pypers/oxford/doctest_talk/P10.html b/pypers/oxford/doctest_talk/P10.html new file mode 100755 index 0000000..19c6e36 --- /dev/null +++ b/pypers/oxford/doctest_talk/P10.html @@ -0,0 +1,101 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> + <html> + <head> + <meta name="generator" content="Generated by Python"> + <title>P10</title> + +<STYLE TYPE="text/css"> + body { font-size: 160%; } +</STYLE> + + </head> +<body bgcolor="lightblue"> + + +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><small> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P11.html'>Next</a></td> <td bgcolor="lightblue"><a href='P09.html'>Prev</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>Last</a></td> <td bgcolor="lightblue"><a href='P01.html'>First</a></td> +</tr> +<tr> + <td bgcolor="lightblue">Current</td> <td bgcolor="lightblue">P10</td> +</tr> +<tr> + <td bgcolor="lightblue"></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P01.html'>P01</a></td> <td bgcolor="lightblue"><a href='P02.html'>P02</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P03.html'>P03</a></td> <td bgcolor="lightblue"><a href='P04.html'>P04</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P05.html'>P05</a></td> <td bgcolor="lightblue"><a href='P06.html'>P06</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P07.html'>P07</a></td> <td bgcolor="lightblue"><a href='P08.html'>P08</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P09.html'>P09</a></td> <td bgcolor="lightblue"><a href='P10.html'>P10</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P11.html'>P11</a></td> <td bgcolor="lightblue"><a href='P12.html'>P12</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P13.html'>P13</a></td> <td bgcolor="lightblue"><a href='P14.html'>P14</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P15.html'>P15</a></td> <td bgcolor="lightblue"><a href='P16.html'>P16</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P17.html'>P17</a></td> <td bgcolor="lightblue"><a href='P18.html'>P18</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P19.html'>P19</a></td> <td bgcolor="lightblue"><a href='P20.html'>P20</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P21.html'>P21</a></td> <td bgcolor="lightblue"><a href='P22.html'>P22</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P23.html'>P23</a></td> <td bgcolor="lightblue"><a href='P24.html'>P24</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>P25</a></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><img src = "accu2005.png" alt = "logo"></td> +</tr> +</table> +</td> +</tr> +</table> +</small></td> <td bgcolor="lightblue"><h1>What is doctest?</h1><br/> + +In its simplest form (which I do not use that much) doctest allows +you to include tests in the docstrings of your application.</td> +</tr> +</table> + + </body> + +</html> diff --git a/pypers/oxford/doctest_talk/P11.html b/pypers/oxford/doctest_talk/P11.html new file mode 100755 index 0000000..e838716 --- /dev/null +++ b/pypers/oxford/doctest_talk/P11.html @@ -0,0 +1,114 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> + <html> + <head> + <meta name="generator" content="Generated by Python"> + <title>P11</title> + +<STYLE TYPE="text/css"> + body { font-size: 160%; } +</STYLE> + + </head> +<body bgcolor="lightblue"> + + +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><small> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P12.html'>Next</a></td> <td bgcolor="lightblue"><a href='P10.html'>Prev</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>Last</a></td> <td bgcolor="lightblue"><a href='P01.html'>First</a></td> +</tr> +<tr> + <td bgcolor="lightblue">Current</td> <td bgcolor="lightblue">P11</td> +</tr> +<tr> + <td bgcolor="lightblue"></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P01.html'>P01</a></td> <td bgcolor="lightblue"><a href='P02.html'>P02</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P03.html'>P03</a></td> <td bgcolor="lightblue"><a href='P04.html'>P04</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P05.html'>P05</a></td> <td bgcolor="lightblue"><a href='P06.html'>P06</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P07.html'>P07</a></td> <td bgcolor="lightblue"><a href='P08.html'>P08</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P09.html'>P09</a></td> <td bgcolor="lightblue"><a href='P10.html'>P10</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P11.html'>P11</a></td> <td bgcolor="lightblue"><a href='P12.html'>P12</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P13.html'>P13</a></td> <td bgcolor="lightblue"><a href='P14.html'>P14</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P15.html'>P15</a></td> <td bgcolor="lightblue"><a href='P16.html'>P16</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P17.html'>P17</a></td> <td bgcolor="lightblue"><a href='P18.html'>P18</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P19.html'>P19</a></td> <td bgcolor="lightblue"><a href='P20.html'>P20</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P21.html'>P21</a></td> <td bgcolor="lightblue"><a href='P22.html'>P22</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P23.html'>P23</a></td> <td bgcolor="lightblue"><a href='P24.html'>P24</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>P25</a></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><img src = "accu2005.png" alt = "logo"></td> +</tr> +</table> +</td> +</tr> +</table> +</small></td> <td bgcolor="lightblue"><h1>Example</h1><br/> +<pre># split.py +import re +SEP = re.compile(r"\s*[,;]\s*") + +def split(text): + """Split a string taking as separators "," ";". + Example: + >>> from split import split + >>> split("hello, world!; welcome to PyUK!") + ['hello', 'world!', 'welcome to PyUK!'] + """ + return SEP.split(text) + +if __name__ == "__main__": + import doctest; doctest.testmod() +</pre></td> +</tr> +</table> + + </body> + +</html> diff --git a/pypers/oxford/doctest_talk/P12.html b/pypers/oxford/doctest_talk/P12.html new file mode 100755 index 0000000..230eb27 --- /dev/null +++ b/pypers/oxford/doctest_talk/P12.html @@ -0,0 +1,119 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> + <html> + <head> + <meta name="generator" content="Generated by Python"> + <title>P12</title> + +<STYLE TYPE="text/css"> + body { font-size: 160%; } +</STYLE> + + </head> +<body bgcolor="lightblue"> + + +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><small> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P13.html'>Next</a></td> <td bgcolor="lightblue"><a href='P11.html'>Prev</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>Last</a></td> <td bgcolor="lightblue"><a href='P01.html'>First</a></td> +</tr> +<tr> + <td bgcolor="lightblue">Current</td> <td bgcolor="lightblue">P12</td> +</tr> +<tr> + <td bgcolor="lightblue"></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P01.html'>P01</a></td> <td bgcolor="lightblue"><a href='P02.html'>P02</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P03.html'>P03</a></td> <td bgcolor="lightblue"><a href='P04.html'>P04</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P05.html'>P05</a></td> <td bgcolor="lightblue"><a href='P06.html'>P06</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P07.html'>P07</a></td> <td bgcolor="lightblue"><a href='P08.html'>P08</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P09.html'>P09</a></td> <td bgcolor="lightblue"><a href='P10.html'>P10</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P11.html'>P11</a></td> <td bgcolor="lightblue"><a href='P12.html'>P12</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P13.html'>P13</a></td> <td bgcolor="lightblue"><a href='P14.html'>P14</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P15.html'>P15</a></td> <td bgcolor="lightblue"><a href='P16.html'>P16</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P17.html'>P17</a></td> <td bgcolor="lightblue"><a href='P18.html'>P18</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P19.html'>P19</a></td> <td bgcolor="lightblue"><a href='P20.html'>P20</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P21.html'>P21</a></td> <td bgcolor="lightblue"><a href='P22.html'>P22</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P23.html'>P23</a></td> <td bgcolor="lightblue"><a href='P24.html'>P24</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>P25</a></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><img src = "accu2005.png" alt = "logo"></td> +</tr> +</table> +</td> +</tr> +</table> +</small></td> <td bgcolor="lightblue"><h1>Running doctest in verbose mode</h1><br/> + +<pre> +$ python split.py -v +Running __main__.__doc__ +0 of 0 examples failed in __main__.__doc__ +Running __main__.split.__doc__ +Trying: from split import split +Expecting: nothing +ok +Trying: split("hello, world!; welcome to Oxford!") +Expecting: ['hello', 'world!', 'welcome to Oxford!'] +ok +0 of 2 examples failed in __main__.split.__doc__ +1 items had no tests: + __main__ +1 items passed all tests: + 2 tests in __main__.split +2 tests in 2 items. +2 passed and 0 failed. +Test passed. +</pre></td> +</tr> +</table> + + </body> + +</html> diff --git a/pypers/oxford/doctest_talk/P13.html b/pypers/oxford/doctest_talk/P13.html new file mode 100755 index 0000000..b2fe415 --- /dev/null +++ b/pypers/oxford/doctest_talk/P13.html @@ -0,0 +1,111 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> + <html> + <head> + <meta name="generator" content="Generated by Python"> + <title>P13</title> + +<STYLE TYPE="text/css"> + body { font-size: 160%; } +</STYLE> + + </head> +<body bgcolor="lightblue"> + + +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><small> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P14.html'>Next</a></td> <td bgcolor="lightblue"><a href='P12.html'>Prev</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>Last</a></td> <td bgcolor="lightblue"><a href='P01.html'>First</a></td> +</tr> +<tr> + <td bgcolor="lightblue">Current</td> <td bgcolor="lightblue">P13</td> +</tr> +<tr> + <td bgcolor="lightblue"></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P01.html'>P01</a></td> <td bgcolor="lightblue"><a href='P02.html'>P02</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P03.html'>P03</a></td> <td bgcolor="lightblue"><a href='P04.html'>P04</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P05.html'>P05</a></td> <td bgcolor="lightblue"><a href='P06.html'>P06</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P07.html'>P07</a></td> <td bgcolor="lightblue"><a href='P08.html'>P08</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P09.html'>P09</a></td> <td bgcolor="lightblue"><a href='P10.html'>P10</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P11.html'>P11</a></td> <td bgcolor="lightblue"><a href='P12.html'>P12</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P13.html'>P13</a></td> <td bgcolor="lightblue"><a href='P14.html'>P14</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P15.html'>P15</a></td> <td bgcolor="lightblue"><a href='P16.html'>P16</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P17.html'>P17</a></td> <td bgcolor="lightblue"><a href='P18.html'>P18</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P19.html'>P19</a></td> <td bgcolor="lightblue"><a href='P20.html'>P20</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P21.html'>P21</a></td> <td bgcolor="lightblue"><a href='P22.html'>P22</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P23.html'>P23</a></td> <td bgcolor="lightblue"><a href='P24.html'>P24</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>P25</a></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><img src = "accu2005.png" alt = "logo"></td> +</tr> +</table> +</td> +</tr> +</table> +</small></td> <td bgcolor="lightblue"><h1>Why I do not use the docstring approach</h1><br/> + +<ul> +<li> It makes you end up with very large docstrings</li> + +<li> It abuses the original purpose of docstrings</li> + +<li> It conflates two different aspects (code and tests on the code)</li> + +<li> It is much easier to write the documentation in a separate + text file </li> + +<li> Testing should be done by an external tool anyway </li> +</ul></td> +</tr> +</table> + + </body> + +</html> diff --git a/pypers/oxford/doctest_talk/P14.html b/pypers/oxford/doctest_talk/P14.html new file mode 100755 index 0000000..5c16abf --- /dev/null +++ b/pypers/oxford/doctest_talk/P14.html @@ -0,0 +1,118 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> + <html> + <head> + <meta name="generator" content="Generated by Python"> + <title>P14</title> + +<STYLE TYPE="text/css"> + body { font-size: 160%; } +</STYLE> + + </head> +<body bgcolor="lightblue"> + + +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><small> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P15.html'>Next</a></td> <td bgcolor="lightblue"><a href='P13.html'>Prev</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>Last</a></td> <td bgcolor="lightblue"><a href='P01.html'>First</a></td> +</tr> +<tr> + <td bgcolor="lightblue">Current</td> <td bgcolor="lightblue">P14</td> +</tr> +<tr> + <td bgcolor="lightblue"></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P01.html'>P01</a></td> <td bgcolor="lightblue"><a href='P02.html'>P02</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P03.html'>P03</a></td> <td bgcolor="lightblue"><a href='P04.html'>P04</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P05.html'>P05</a></td> <td bgcolor="lightblue"><a href='P06.html'>P06</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P07.html'>P07</a></td> <td bgcolor="lightblue"><a href='P08.html'>P08</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P09.html'>P09</a></td> <td bgcolor="lightblue"><a href='P10.html'>P10</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P11.html'>P11</a></td> <td bgcolor="lightblue"><a href='P12.html'>P12</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P13.html'>P13</a></td> <td bgcolor="lightblue"><a href='P14.html'>P14</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P15.html'>P15</a></td> <td bgcolor="lightblue"><a href='P16.html'>P16</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P17.html'>P17</a></td> <td bgcolor="lightblue"><a href='P18.html'>P18</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P19.html'>P19</a></td> <td bgcolor="lightblue"><a href='P20.html'>P20</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P21.html'>P21</a></td> <td bgcolor="lightblue"><a href='P22.html'>P22</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P23.html'>P23</a></td> <td bgcolor="lightblue"><a href='P24.html'>P24</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>P25</a></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><img src = "accu2005.png" alt = "logo"></td> +</tr> +</table> +</td> +</tr> +</table> +</small></td> <td bgcolor="lightblue"><h1>How I use doctest</h1><br/> + +I hacked inside doctest and wrote a custom utility +to extract doctests from documentation files since + +<ul> + <li>I like keeping the documentation on a separate rst file</li> + + <li>there is no sync problem since you run the tests all the time</li> + + <li>it is useful for writing articles ...</li> + + <li> ... but also documentation for internal usage in the company</li> +</ul> + +http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/410052 + +<pre> +$ python -m doctester < split.txt +doctest: run 4 tests, failed 0 +</pre></td> +</tr> +</table> + + </body> + +</html> diff --git a/pypers/oxford/doctest_talk/P15.html b/pypers/oxford/doctest_talk/P15.html new file mode 100755 index 0000000..a6843e3 --- /dev/null +++ b/pypers/oxford/doctest_talk/P15.html @@ -0,0 +1,115 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> + <html> + <head> + <meta name="generator" content="Generated by Python"> + <title>P15</title> + +<STYLE TYPE="text/css"> + body { font-size: 160%; } +</STYLE> + + </head> +<body bgcolor="lightblue"> + + +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><small> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P16.html'>Next</a></td> <td bgcolor="lightblue"><a href='P14.html'>Prev</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>Last</a></td> <td bgcolor="lightblue"><a href='P01.html'>First</a></td> +</tr> +<tr> + <td bgcolor="lightblue">Current</td> <td bgcolor="lightblue">P15</td> +</tr> +<tr> + <td bgcolor="lightblue"></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P01.html'>P01</a></td> <td bgcolor="lightblue"><a href='P02.html'>P02</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P03.html'>P03</a></td> <td bgcolor="lightblue"><a href='P04.html'>P04</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P05.html'>P05</a></td> <td bgcolor="lightblue"><a href='P06.html'>P06</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P07.html'>P07</a></td> <td bgcolor="lightblue"><a href='P08.html'>P08</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P09.html'>P09</a></td> <td bgcolor="lightblue"><a href='P10.html'>P10</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P11.html'>P11</a></td> <td bgcolor="lightblue"><a href='P12.html'>P12</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P13.html'>P13</a></td> <td bgcolor="lightblue"><a href='P14.html'>P14</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P15.html'>P15</a></td> <td bgcolor="lightblue"><a href='P16.html'>P16</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P17.html'>P17</a></td> <td bgcolor="lightblue"><a href='P18.html'>P18</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P19.html'>P19</a></td> <td bgcolor="lightblue"><a href='P20.html'>P20</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P21.html'>P21</a></td> <td bgcolor="lightblue"><a href='P22.html'>P22</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P23.html'>P23</a></td> <td bgcolor="lightblue"><a href='P24.html'>P24</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>P25</a></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><img src = "accu2005.png" alt = "logo"></td> +</tr> +</table> +</td> +</tr> +</table> +</small></td> <td bgcolor="lightblue"><h1>Testing the doctester frontend</h1><br/> + +<pre> +>>> from ms.webtester import start_server, stop_server +>>> from ms.http_utils import urlopen +>>> baseurl = "http://localhost:7080/" +>>> home = "/home/micheles/md/python/quixote/" + +>>> start_server(home + "doctester_frontend.py") +>>> import time; time.sleep(2) # wait a bit + +Making a POST: + +>>> res = urlopen(baseurl, dict(txt=">>> 1 + 1\n2")).read() +>>> assert "tests" in res +>>> stop_server() + +</pre></td> +</tr> +</table> + + </body> + +</html> diff --git a/pypers/oxford/doctest_talk/P16.html b/pypers/oxford/doctest_talk/P16.html new file mode 100755 index 0000000..d5b4541 --- /dev/null +++ b/pypers/oxford/doctest_talk/P16.html @@ -0,0 +1,118 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> + <html> + <head> + <meta name="generator" content="Generated by Python"> + <title>P16</title> + +<STYLE TYPE="text/css"> + body { font-size: 160%; } +</STYLE> + + </head> +<body bgcolor="lightblue"> + + +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><small> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P17.html'>Next</a></td> <td bgcolor="lightblue"><a href='P15.html'>Prev</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>Last</a></td> <td bgcolor="lightblue"><a href='P01.html'>First</a></td> +</tr> +<tr> + <td bgcolor="lightblue">Current</td> <td bgcolor="lightblue">P16</td> +</tr> +<tr> + <td bgcolor="lightblue"></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P01.html'>P01</a></td> <td bgcolor="lightblue"><a href='P02.html'>P02</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P03.html'>P03</a></td> <td bgcolor="lightblue"><a href='P04.html'>P04</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P05.html'>P05</a></td> <td bgcolor="lightblue"><a href='P06.html'>P06</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P07.html'>P07</a></td> <td bgcolor="lightblue"><a href='P08.html'>P08</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P09.html'>P09</a></td> <td bgcolor="lightblue"><a href='P10.html'>P10</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P11.html'>P11</a></td> <td bgcolor="lightblue"><a href='P12.html'>P12</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P13.html'>P13</a></td> <td bgcolor="lightblue"><a href='P14.html'>P14</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P15.html'>P15</a></td> <td bgcolor="lightblue"><a href='P16.html'>P16</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P17.html'>P17</a></td> <td bgcolor="lightblue"><a href='P18.html'>P18</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P19.html'>P19</a></td> <td bgcolor="lightblue"><a href='P20.html'>P20</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P21.html'>P21</a></td> <td bgcolor="lightblue"><a href='P22.html'>P22</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P23.html'>P23</a></td> <td bgcolor="lightblue"><a href='P24.html'>P24</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>P25</a></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><img src = "accu2005.png" alt = "logo"></td> +</tr> +</table> +</td> +</tr> +</table> +</small></td> <td bgcolor="lightblue"><h1>Managing exceptions</h1><br/> + +It is possible to test that your program raises the exception you +expect: + +<pre> + +$ echo "# split cannot work on a list +>>> from split import split +>>> split([]) +Traceback (most recent call last): + ... +TypeError: expected string or buffer +" > x.txt + +$ doct x.txt +x.txt: 2 tests passed in 0.01 seconds + +</pre> + +(notice however that relying on exception messages may be risky)</td> +</tr> +</table> + + </body> + +</html> diff --git a/pypers/oxford/doctest_talk/P17.html b/pypers/oxford/doctest_talk/P17.html new file mode 100755 index 0000000..da93de5 --- /dev/null +++ b/pypers/oxford/doctest_talk/P17.html @@ -0,0 +1,116 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> + <html> + <head> + <meta name="generator" content="Generated by Python"> + <title>P17</title> + +<STYLE TYPE="text/css"> + body { font-size: 160%; } +</STYLE> + + </head> +<body bgcolor="lightblue"> + + +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><small> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P18.html'>Next</a></td> <td bgcolor="lightblue"><a href='P16.html'>Prev</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>Last</a></td> <td bgcolor="lightblue"><a href='P01.html'>First</a></td> +</tr> +<tr> + <td bgcolor="lightblue">Current</td> <td bgcolor="lightblue">P17</td> +</tr> +<tr> + <td bgcolor="lightblue"></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P01.html'>P01</a></td> <td bgcolor="lightblue"><a href='P02.html'>P02</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P03.html'>P03</a></td> <td bgcolor="lightblue"><a href='P04.html'>P04</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P05.html'>P05</a></td> <td bgcolor="lightblue"><a href='P06.html'>P06</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P07.html'>P07</a></td> <td bgcolor="lightblue"><a href='P08.html'>P08</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P09.html'>P09</a></td> <td bgcolor="lightblue"><a href='P10.html'>P10</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P11.html'>P11</a></td> <td bgcolor="lightblue"><a href='P12.html'>P12</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P13.html'>P13</a></td> <td bgcolor="lightblue"><a href='P14.html'>P14</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P15.html'>P15</a></td> <td bgcolor="lightblue"><a href='P16.html'>P16</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P17.html'>P17</a></td> <td bgcolor="lightblue"><a href='P18.html'>P18</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P19.html'>P19</a></td> <td bgcolor="lightblue"><a href='P20.html'>P20</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P21.html'>P21</a></td> <td bgcolor="lightblue"><a href='P22.html'>P22</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P23.html'>P23</a></td> <td bgcolor="lightblue"><a href='P24.html'>P24</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>P25</a></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><img src = "accu2005.png" alt = "logo"></td> +</tr> +</table> +</td> +</tr> +</table> +</small></td> <td bgcolor="lightblue"><h1>When tests fail</h1><br/> + +<pre> + +$ cat split-failure.txt +An example of failed text: + +>>> from split import split +>>> split("hello, world") +['hello', ' world'] + +$ doct split-failure.txt +***************************************************************** +Failure in example: split("hello, world") +from line #5 of split-failure.txt +Expected: ['hello', ' world'] +Got: ['hello', 'world'] + +</pre></td> +</tr> +</table> + + </body> + +</html> diff --git a/pypers/oxford/doctest_talk/P18.html b/pypers/oxford/doctest_talk/P18.html new file mode 100755 index 0000000..0acc512 --- /dev/null +++ b/pypers/oxford/doctest_talk/P18.html @@ -0,0 +1,110 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> + <html> + <head> + <meta name="generator" content="Generated by Python"> + <title>P18</title> + +<STYLE TYPE="text/css"> + body { font-size: 160%; } +</STYLE> + + </head> +<body bgcolor="lightblue"> + + +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><small> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P19.html'>Next</a></td> <td bgcolor="lightblue"><a href='P17.html'>Prev</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>Last</a></td> <td bgcolor="lightblue"><a href='P01.html'>First</a></td> +</tr> +<tr> + <td bgcolor="lightblue">Current</td> <td bgcolor="lightblue">P18</td> +</tr> +<tr> + <td bgcolor="lightblue"></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P01.html'>P01</a></td> <td bgcolor="lightblue"><a href='P02.html'>P02</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P03.html'>P03</a></td> <td bgcolor="lightblue"><a href='P04.html'>P04</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P05.html'>P05</a></td> <td bgcolor="lightblue"><a href='P06.html'>P06</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P07.html'>P07</a></td> <td bgcolor="lightblue"><a href='P08.html'>P08</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P09.html'>P09</a></td> <td bgcolor="lightblue"><a href='P10.html'>P10</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P11.html'>P11</a></td> <td bgcolor="lightblue"><a href='P12.html'>P12</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P13.html'>P13</a></td> <td bgcolor="lightblue"><a href='P14.html'>P14</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P15.html'>P15</a></td> <td bgcolor="lightblue"><a href='P16.html'>P16</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P17.html'>P17</a></td> <td bgcolor="lightblue"><a href='P18.html'>P18</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P19.html'>P19</a></td> <td bgcolor="lightblue"><a href='P20.html'>P20</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P21.html'>P21</a></td> <td bgcolor="lightblue"><a href='P22.html'>P22</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P23.html'>P23</a></td> <td bgcolor="lightblue"><a href='P24.html'>P24</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>P25</a></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><img src = "accu2005.png" alt = "logo"></td> +</tr> +</table> +</td> +</tr> +</table> +</small></td> <td bgcolor="lightblue"><h1>Converting doctests to unittests</h1><br/> + +<pre> + import unittest + import doctest + import my_module_with_doctests + + suite = doctest.DocTestSuite(my_module_with_doctests) + runner = unittest.TextTestRunner() + runner.run(suite) +</pre> + +<h2>For Python 2.3+<h2></td> +</tr> +</table> + + </body> + +</html> diff --git a/pypers/oxford/doctest_talk/P19.html b/pypers/oxford/doctest_talk/P19.html new file mode 100755 index 0000000..744a43b --- /dev/null +++ b/pypers/oxford/doctest_talk/P19.html @@ -0,0 +1,113 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> + <html> + <head> + <meta name="generator" content="Generated by Python"> + <title>P19</title> + +<STYLE TYPE="text/css"> + body { font-size: 160%; } +</STYLE> + + </head> +<body bgcolor="lightblue"> + + +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><small> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P20.html'>Next</a></td> <td bgcolor="lightblue"><a href='P18.html'>Prev</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>Last</a></td> <td bgcolor="lightblue"><a href='P01.html'>First</a></td> +</tr> +<tr> + <td bgcolor="lightblue">Current</td> <td bgcolor="lightblue">P19</td> +</tr> +<tr> + <td bgcolor="lightblue"></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P01.html'>P01</a></td> <td bgcolor="lightblue"><a href='P02.html'>P02</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P03.html'>P03</a></td> <td bgcolor="lightblue"><a href='P04.html'>P04</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P05.html'>P05</a></td> <td bgcolor="lightblue"><a href='P06.html'>P06</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P07.html'>P07</a></td> <td bgcolor="lightblue"><a href='P08.html'>P08</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P09.html'>P09</a></td> <td bgcolor="lightblue"><a href='P10.html'>P10</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P11.html'>P11</a></td> <td bgcolor="lightblue"><a href='P12.html'>P12</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P13.html'>P13</a></td> <td bgcolor="lightblue"><a href='P14.html'>P14</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P15.html'>P15</a></td> <td bgcolor="lightblue"><a href='P16.html'>P16</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P17.html'>P17</a></td> <td bgcolor="lightblue"><a href='P18.html'>P18</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P19.html'>P19</a></td> <td bgcolor="lightblue"><a href='P20.html'>P20</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P21.html'>P21</a></td> <td bgcolor="lightblue"><a href='P22.html'>P22</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P23.html'>P23</a></td> <td bgcolor="lightblue"><a href='P24.html'>P24</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>P25</a></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><img src = "accu2005.png" alt = "logo"></td> +</tr> +</table> +</td> +</tr> +</table> +</small></td> <td bgcolor="lightblue"><h1>doctest is becoming even better</h1><br/> + +With Python 2.4 you can run doctests on external text files: + +<pre> + import doctest, unittest + doctest.testfile(my_documentation_file, package=mypackage) +</pre> + +you can also convert these doctests into unittests: + +<pre> + import doctest, unittest + suite = doctest.DocFileSuite(my_documentation_file, package=mypackage) + unittest.TextTestRunner().run(suite) +</pre></td> +</tr> +</table> + + </body> + +</html> diff --git a/pypers/oxford/doctest_talk/P20.html b/pypers/oxford/doctest_talk/P20.html new file mode 100755 index 0000000..8aea60d --- /dev/null +++ b/pypers/oxford/doctest_talk/P20.html @@ -0,0 +1,108 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> + <html> + <head> + <meta name="generator" content="Generated by Python"> + <title>P20</title> + +<STYLE TYPE="text/css"> + body { font-size: 160%; } +</STYLE> + + </head> +<body bgcolor="lightblue"> + + +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><small> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P21.html'>Next</a></td> <td bgcolor="lightblue"><a href='P19.html'>Prev</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>Last</a></td> <td bgcolor="lightblue"><a href='P01.html'>First</a></td> +</tr> +<tr> + <td bgcolor="lightblue">Current</td> <td bgcolor="lightblue">P20</td> +</tr> +<tr> + <td bgcolor="lightblue"></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P01.html'>P01</a></td> <td bgcolor="lightblue"><a href='P02.html'>P02</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P03.html'>P03</a></td> <td bgcolor="lightblue"><a href='P04.html'>P04</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P05.html'>P05</a></td> <td bgcolor="lightblue"><a href='P06.html'>P06</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P07.html'>P07</a></td> <td bgcolor="lightblue"><a href='P08.html'>P08</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P09.html'>P09</a></td> <td bgcolor="lightblue"><a href='P10.html'>P10</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P11.html'>P11</a></td> <td bgcolor="lightblue"><a href='P12.html'>P12</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P13.html'>P13</a></td> <td bgcolor="lightblue"><a href='P14.html'>P14</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P15.html'>P15</a></td> <td bgcolor="lightblue"><a href='P16.html'>P16</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P17.html'>P17</a></td> <td bgcolor="lightblue"><a href='P18.html'>P18</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P19.html'>P19</a></td> <td bgcolor="lightblue"><a href='P20.html'>P20</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P21.html'>P21</a></td> <td bgcolor="lightblue"><a href='P22.html'>P22</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P23.html'>P23</a></td> <td bgcolor="lightblue"><a href='P24.html'>P24</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>P25</a></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><img src = "accu2005.png" alt = "logo"></td> +</tr> +</table> +</td> +</tr> +</table> +</small></td> <td bgcolor="lightblue"><h1>Python 2.4 recognizes blank lines</h1><br/> + +Blank lines can be marked with <BLANKLINE> : +<pre> +>>> print 'foo\n\nbar\n' +foo +<BLANKLINE> +bar +<BLANKLINE> + +</pre></td> +</tr> +</table> + + </body> + +</html> diff --git a/pypers/oxford/doctest_talk/P21.html b/pypers/oxford/doctest_talk/P21.html new file mode 100755 index 0000000..34a3aa5 --- /dev/null +++ b/pypers/oxford/doctest_talk/P21.html @@ -0,0 +1,117 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> + <html> + <head> + <meta name="generator" content="Generated by Python"> + <title>P21</title> + +<STYLE TYPE="text/css"> + body { font-size: 160%; } +</STYLE> + + </head> +<body bgcolor="lightblue"> + + +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><small> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P22.html'>Next</a></td> <td bgcolor="lightblue"><a href='P20.html'>Prev</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>Last</a></td> <td bgcolor="lightblue"><a href='P01.html'>First</a></td> +</tr> +<tr> + <td bgcolor="lightblue">Current</td> <td bgcolor="lightblue">P21</td> +</tr> +<tr> + <td bgcolor="lightblue"></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P01.html'>P01</a></td> <td bgcolor="lightblue"><a href='P02.html'>P02</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P03.html'>P03</a></td> <td bgcolor="lightblue"><a href='P04.html'>P04</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P05.html'>P05</a></td> <td bgcolor="lightblue"><a href='P06.html'>P06</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P07.html'>P07</a></td> <td bgcolor="lightblue"><a href='P08.html'>P08</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P09.html'>P09</a></td> <td bgcolor="lightblue"><a href='P10.html'>P10</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P11.html'>P11</a></td> <td bgcolor="lightblue"><a href='P12.html'>P12</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P13.html'>P13</a></td> <td bgcolor="lightblue"><a href='P14.html'>P14</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P15.html'>P15</a></td> <td bgcolor="lightblue"><a href='P16.html'>P16</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P17.html'>P17</a></td> <td bgcolor="lightblue"><a href='P18.html'>P18</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P19.html'>P19</a></td> <td bgcolor="lightblue"><a href='P20.html'>P20</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P21.html'>P21</a></td> <td bgcolor="lightblue"><a href='P22.html'>P22</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P23.html'>P23</a></td> <td bgcolor="lightblue"><a href='P24.html'>P24</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>P25</a></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><img src = "accu2005.png" alt = "logo"></td> +</tr> +</table> +</td> +</tr> +</table> +</small></td> <td bgcolor="lightblue"><h1>Python 2.4 recognizes flags!</h1><br/> + +<ul> +<li> If the ellipsis flag is used, then '...' can be used to + elide substrings in the desired output: <pre> +>>> print range(1000) #doctest: +ELLIPSIS +[0, 1, 2, ..., 999] + +</pre></li> + +<li> + If the whitespace normalization flag is used, then + differences in whitespace are ignored.<pre> +>>> print range(20) #doctest: +NORMALIZE_WHITESPACE +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, +12, 13, 14, 15, 16, 17, 18, 19] + +</pre></li> + +</ul></td> +</tr> +</table> + + </body> + +</html> diff --git a/pypers/oxford/doctest_talk/P22.html b/pypers/oxford/doctest_talk/P22.html new file mode 100755 index 0000000..929bbb5 --- /dev/null +++ b/pypers/oxford/doctest_talk/P22.html @@ -0,0 +1,108 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> + <html> + <head> + <meta name="generator" content="Generated by Python"> + <title>P22</title> + +<STYLE TYPE="text/css"> + body { font-size: 160%; } +</STYLE> + + </head> +<body bgcolor="lightblue"> + + +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><small> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P23.html'>Next</a></td> <td bgcolor="lightblue"><a href='P21.html'>Prev</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>Last</a></td> <td bgcolor="lightblue"><a href='P01.html'>First</a></td> +</tr> +<tr> + <td bgcolor="lightblue">Current</td> <td bgcolor="lightblue">P22</td> +</tr> +<tr> + <td bgcolor="lightblue"></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P01.html'>P01</a></td> <td bgcolor="lightblue"><a href='P02.html'>P02</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P03.html'>P03</a></td> <td bgcolor="lightblue"><a href='P04.html'>P04</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P05.html'>P05</a></td> <td bgcolor="lightblue"><a href='P06.html'>P06</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P07.html'>P07</a></td> <td bgcolor="lightblue"><a href='P08.html'>P08</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P09.html'>P09</a></td> <td bgcolor="lightblue"><a href='P10.html'>P10</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P11.html'>P11</a></td> <td bgcolor="lightblue"><a href='P12.html'>P12</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P13.html'>P13</a></td> <td bgcolor="lightblue"><a href='P14.html'>P14</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P15.html'>P15</a></td> <td bgcolor="lightblue"><a href='P16.html'>P16</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P17.html'>P17</a></td> <td bgcolor="lightblue"><a href='P18.html'>P18</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P19.html'>P19</a></td> <td bgcolor="lightblue"><a href='P20.html'>P20</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P21.html'>P21</a></td> <td bgcolor="lightblue"><a href='P22.html'>P22</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P23.html'>P23</a></td> <td bgcolor="lightblue"><a href='P24.html'>P24</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>P25</a></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><img src = "accu2005.png" alt = "logo"></td> +</tr> +</table> +</td> +</tr> +</table> +</small></td> <td bgcolor="lightblue"><h1>Zope experience</h1><br/> + +Literal quote from the PyCON doctest talk: + +<ul> +<li> ~ 5600 tests (~3500 in Zope 3, ~1300 in ZODB, ~800 in Zope 2)</li> +<li> we wrote lots of tests before we knew what we were doing</li> +<li> debugging failed tests is really hard when intent is unclear</li> +<li> often refactor or reimplement tests to make them clearer</li> +<li> most new tests are doctest based</li> +</ul></td> +</tr> +</table> + + </body> + +</html> diff --git a/pypers/oxford/doctest_talk/P23.html b/pypers/oxford/doctest_talk/P23.html new file mode 100755 index 0000000..4ab3427 --- /dev/null +++ b/pypers/oxford/doctest_talk/P23.html @@ -0,0 +1,115 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> + <html> + <head> + <meta name="generator" content="Generated by Python"> + <title>P23</title> + +<STYLE TYPE="text/css"> + body { font-size: 160%; } +</STYLE> + + </head> +<body bgcolor="lightblue"> + + +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><small> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P24.html'>Next</a></td> <td bgcolor="lightblue"><a href='P22.html'>Prev</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>Last</a></td> <td bgcolor="lightblue"><a href='P01.html'>First</a></td> +</tr> +<tr> + <td bgcolor="lightblue">Current</td> <td bgcolor="lightblue">P23</td> +</tr> +<tr> + <td bgcolor="lightblue"></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P01.html'>P01</a></td> <td bgcolor="lightblue"><a href='P02.html'>P02</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P03.html'>P03</a></td> <td bgcolor="lightblue"><a href='P04.html'>P04</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P05.html'>P05</a></td> <td bgcolor="lightblue"><a href='P06.html'>P06</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P07.html'>P07</a></td> <td bgcolor="lightblue"><a href='P08.html'>P08</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P09.html'>P09</a></td> <td bgcolor="lightblue"><a href='P10.html'>P10</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P11.html'>P11</a></td> <td bgcolor="lightblue"><a href='P12.html'>P12</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P13.html'>P13</a></td> <td bgcolor="lightblue"><a href='P14.html'>P14</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P15.html'>P15</a></td> <td bgcolor="lightblue"><a href='P16.html'>P16</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P17.html'>P17</a></td> <td bgcolor="lightblue"><a href='P18.html'>P18</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P19.html'>P19</a></td> <td bgcolor="lightblue"><a href='P20.html'>P20</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P21.html'>P21</a></td> <td bgcolor="lightblue"><a href='P22.html'>P22</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P23.html'>P23</a></td> <td bgcolor="lightblue"><a href='P24.html'>P24</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>P25</a></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><img src = "accu2005.png" alt = "logo"></td> +</tr> +</table> +</td> +</tr> +</table> +</small></td> <td bgcolor="lightblue"><h1>Conclusion (1): good reasons to use doctest</h1><br/> + +<quote> +"Test coverage is important, but test readability is much more important" +</quote> + +<em>-- Tim Peters and Jim Fulton</em> <br/> <br/> + +doctest is good since: + +<ol> + <li> it is easy to understand, to explain and to use </li> + + <li> it makes you improve the quality of your documentation </li> + + <li> it can be converted to unittest anyway </li> + +</ol></td> +</tr> +</table> + + </body> + +</html> diff --git a/pypers/oxford/doctest_talk/P24.html b/pypers/oxford/doctest_talk/P24.html new file mode 100755 index 0000000..276299f --- /dev/null +++ b/pypers/oxford/doctest_talk/P24.html @@ -0,0 +1,112 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> + <html> + <head> + <meta name="generator" content="Generated by Python"> + <title>P24</title> + +<STYLE TYPE="text/css"> + body { font-size: 160%; } +</STYLE> + + </head> +<body bgcolor="lightblue"> + + +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><small> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>Next</a></td> <td bgcolor="lightblue"><a href='P23.html'>Prev</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>Last</a></td> <td bgcolor="lightblue"><a href='P01.html'>First</a></td> +</tr> +<tr> + <td bgcolor="lightblue">Current</td> <td bgcolor="lightblue">P24</td> +</tr> +<tr> + <td bgcolor="lightblue"></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P01.html'>P01</a></td> <td bgcolor="lightblue"><a href='P02.html'>P02</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P03.html'>P03</a></td> <td bgcolor="lightblue"><a href='P04.html'>P04</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P05.html'>P05</a></td> <td bgcolor="lightblue"><a href='P06.html'>P06</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P07.html'>P07</a></td> <td bgcolor="lightblue"><a href='P08.html'>P08</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P09.html'>P09</a></td> <td bgcolor="lightblue"><a href='P10.html'>P10</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P11.html'>P11</a></td> <td bgcolor="lightblue"><a href='P12.html'>P12</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P13.html'>P13</a></td> <td bgcolor="lightblue"><a href='P14.html'>P14</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P15.html'>P15</a></td> <td bgcolor="lightblue"><a href='P16.html'>P16</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P17.html'>P17</a></td> <td bgcolor="lightblue"><a href='P18.html'>P18</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P19.html'>P19</a></td> <td bgcolor="lightblue"><a href='P20.html'>P20</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P21.html'>P21</a></td> <td bgcolor="lightblue"><a href='P22.html'>P22</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P23.html'>P23</a></td> <td bgcolor="lightblue"><a href='P24.html'>P24</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>P25</a></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><img src = "accu2005.png" alt = "logo"></td> +</tr> +</table> +</td> +</tr> +</table> +</small></td> <td bgcolor="lightblue"><h1>Conclusion (2): the message of this talk</h1><br/> + +Automatic testing is good for tons of practical reasons, but also +because: + +<ol> + +<li>It teaches you <em>discipline</em> </li> + +<li>It makes you + <em>think differently</em> </li> + +<li>It is more <em>fun!</em> </li> + +</ol></td> +</tr> +</table> + + </body> + +</html> diff --git a/pypers/oxford/doctest_talk/P25.html b/pypers/oxford/doctest_talk/P25.html new file mode 100755 index 0000000..daae306 --- /dev/null +++ b/pypers/oxford/doctest_talk/P25.html @@ -0,0 +1,110 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> + <html> + <head> + <meta name="generator" content="Generated by Python"> + <title>P25</title> + +<STYLE TYPE="text/css"> + body { font-size: 160%; } +</STYLE> + + </head> +<body bgcolor="lightblue"> + + +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><small> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P01.html'>Next</a></td> <td bgcolor="lightblue"><a href='P24.html'>Prev</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>Last</a></td> <td bgcolor="lightblue"><a href='P01.html'>First</a></td> +</tr> +<tr> + <td bgcolor="lightblue">Current</td> <td bgcolor="lightblue">P25</td> +</tr> +<tr> + <td bgcolor="lightblue"></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P01.html'>P01</a></td> <td bgcolor="lightblue"><a href='P02.html'>P02</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P03.html'>P03</a></td> <td bgcolor="lightblue"><a href='P04.html'>P04</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P05.html'>P05</a></td> <td bgcolor="lightblue"><a href='P06.html'>P06</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P07.html'>P07</a></td> <td bgcolor="lightblue"><a href='P08.html'>P08</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P09.html'>P09</a></td> <td bgcolor="lightblue"><a href='P10.html'>P10</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P11.html'>P11</a></td> <td bgcolor="lightblue"><a href='P12.html'>P12</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P13.html'>P13</a></td> <td bgcolor="lightblue"><a href='P14.html'>P14</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P15.html'>P15</a></td> <td bgcolor="lightblue"><a href='P16.html'>P16</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P17.html'>P17</a></td> <td bgcolor="lightblue"><a href='P18.html'>P18</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P19.html'>P19</a></td> <td bgcolor="lightblue"><a href='P20.html'>P20</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P21.html'>P21</a></td> <td bgcolor="lightblue"><a href='P22.html'>P22</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P23.html'>P23</a></td> <td bgcolor="lightblue"><a href='P24.html'>P24</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>P25</a></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><img src = "accu2005.png" alt = "logo"></td> +</tr> +</table> +</td> +</tr> +</table> +</small></td> <td bgcolor="lightblue"><h1>References</h1><br/> + +<ul> + +<li>The standard library documentation +http://docs.python.org/lib/module-doctest.html </li> + +<li> The doctest talk by Tim Peters and Jim Fulton +http://www.python.org/pycon/dc2004/papers/4/</li> + +<li> doctest.py <em>(use the source, Luke!)</em></li> +</ul> +</td> +</tr> +</table> + + </body> + +</html> diff --git a/pypers/oxford/doctest_talk/P26.html b/pypers/oxford/doctest_talk/P26.html new file mode 100755 index 0000000..cb376d4 --- /dev/null +++ b/pypers/oxford/doctest_talk/P26.html @@ -0,0 +1,113 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> + <html> + <head> + <meta name="generator" content="Generated by Python"> + <title>P26</title> + +<STYLE TYPE="text/css"> + body { font-size: 160%; } +</STYLE> + + </head> +<body bgcolor="lightblue"> + + +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><small> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><img src = "accu2005.png" alt = "logo"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P01.html'>Next</a></td> <td bgcolor="lightblue"><a href='P25.html'>Prev</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P26.html'>Last</a></td> <td bgcolor="lightblue"><a href='P01.html'>First</a></td> +</tr> +<tr> + <td bgcolor="lightblue">Current</td> <td bgcolor="lightblue">P26</td> +</tr> +<tr> + <td bgcolor="lightblue"></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +<tr> + <td bgcolor="lightblue"> +<table border=0 summary='a table'> +<tr> + <td bgcolor="lightblue"><a href='P01.html'>P01</a></td> <td bgcolor="lightblue"><a href='P02.html'>P02</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P03.html'>P03</a></td> <td bgcolor="lightblue"><a href='P04.html'>P04</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P05.html'>P05</a></td> <td bgcolor="lightblue"><a href='P06.html'>P06</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P07.html'>P07</a></td> <td bgcolor="lightblue"><a href='P08.html'>P08</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P09.html'>P09</a></td> <td bgcolor="lightblue"><a href='P10.html'>P10</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P11.html'>P11</a></td> <td bgcolor="lightblue"><a href='P12.html'>P12</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P13.html'>P13</a></td> <td bgcolor="lightblue"><a href='P14.html'>P14</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P15.html'>P15</a></td> <td bgcolor="lightblue"><a href='P16.html'>P16</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P17.html'>P17</a></td> <td bgcolor="lightblue"><a href='P18.html'>P18</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P19.html'>P19</a></td> <td bgcolor="lightblue"><a href='P20.html'>P20</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P21.html'>P21</a></td> <td bgcolor="lightblue"><a href='P22.html'>P22</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P23.html'>P23</a></td> <td bgcolor="lightblue"><a href='P24.html'>P24</a></td> +</tr> +<tr> + <td bgcolor="lightblue"><a href='P25.html'>P25</a></td> <td bgcolor="lightblue"><a href='P26.html'>P26</a></td> +</tr> +<tr> + <td bgcolor="lightblue"></td> <td bgcolor="lightblue"></td> +</tr> +</table> +</td> +</tr> +</table> +</small></td> <td bgcolor="lightblue"><h1>References</h1><br/> + +<ul> + +<li>The standard library documentation +http://docs.python.org/lib/module-doctest.html </li> + +<li> The doctest talk by Tim Peters and Jim Fulton +http://www.python.org/pycon/dc2004/papers/4/</li> + +<li> doctest.py <em>(use the source, Luke!)</em></li> +</ul> +</td> +</tr> +</table> + + </body> + +</html> diff --git a/pypers/oxford/doctest_talk/abstract.txt b/pypers/oxford/doctest_talk/abstract.txt new file mode 100755 index 0000000..e671aae --- /dev/null +++ b/pypers/oxford/doctest_talk/abstract.txt @@ -0,0 +1,11 @@ +Automatic testing proved extremely effective in +helping code design, improving code reliability, +and enabling code refactoring. +Therefore, most modern languages provide a +standard testing framework. +Python is no exception and provide a powerful +Smalltalk/Java-inspired unittest framework. +However, Python also provides a *second*, less known +but arguably *better*, testing framework called doctest.. +In this talk I will show to the audience the wonders of +doctest, explaining why it is so cool and deserving attention. diff --git a/pypers/oxford/doctest_talk/doct_pkg.py b/pypers/oxford/doctest_talk/doct_pkg.py new file mode 100755 index 0000000..331fcca --- /dev/null +++ b/pypers/oxford/doctest_talk/doct_pkg.py @@ -0,0 +1,22 @@ +import test_pkg, doctest, os + +import sys +from ms.file_utils import ifiles + +pkg = __import__("test_pkg") + +pkg_name = pkg.__name__ +csd = os.path.dirname(test_pkg.__path__[0]) # current search directory +os.chdir(csd) + +print "Testing package", pkg_name + +total_fail, total_ok = 0, 0 +for f in ifiles(pkg_name, lambda f: f.endswith(".py"), abs_path=True): + f = f[:-3].replace("/", ".") + fail, ok = doctest.testmod(__import__(f, globals(), locals(), [f])) + total_fail += fail + total_ok += ok +print "Failed %s, passed %s" % (total_fail, total_ok) +# doctest.testmod(test_pkg) # only tests __init__ +# doctest.run_docstring_examples(test_pkg, globals()) # idem diff --git a/pypers/oxford/doctest_talk/doctester_frontend.py b/pypers/oxford/doctest_talk/doctester_frontend.py new file mode 100755 index 0000000..0a1acc7 --- /dev/null +++ b/pypers/oxford/doctest_talk/doctester_frontend.py @@ -0,0 +1,44 @@ +from ms.quixote_utils_exp import Website, htmlpage, FormPage +from doctester import runtests +from StringIO import StringIO +import sys + +class DoctestPage(FormPage): + form_config = """\ + [text] + name: txt + title: Doctester input + + [checkbox] + name: verbose + title: Verbose + + [submit] + name: test + value: test!""" + + @htmlpage() + def exitpage(self): + if self.form["verbose"]: + yield "<pre>%s</pre>" % self.out.getvalue() + else: + yield "%(tests)s tests, %(fail)s failed" % vars(self) + + @htmlpage() + def errorpage(self): + yield self.form.get_widget('txt').error + yield "<pre>%s</pre>" % self.out.getvalue() + + def checker(self): + sys.stdout_orig = sys.stdout + sys.stdout = self.out = StringIO() + txt, verbose = self.form["txt"] or "", self.form["verbose"] + self.fail, self.tests = runtests(txt, verbose=verbose) + sys.stdout = sys.stdout_orig + if self.fail: + self.form.set_error("txt", "Doctester error") + +publisher = Website(_q_index=DoctestPage("doctester")).publisher() + +if __name__ == "__main__": + publisher.run_show(port=7080) diff --git a/pypers/oxford/doctest_talk/doctester_frontend.txt b/pypers/oxford/doctest_talk/doctester_frontend.txt new file mode 100755 index 0000000..72233d4 --- /dev/null +++ b/pypers/oxford/doctest_talk/doctester_frontend.txt @@ -0,0 +1,23 @@ +A simple example of how to doctest a Web application +-------------------------------------------------------- + +A few imports and settings: + +>>> from ms.webtester import start_server, stop_server +>>> from ms.http_utils import urlopen +>>> baseurl = "http://localhost:7080/" +>>> server = "/home/micheles/md/python/quixote/doctester_frontend.py" + +Starting the server: + +>>> start_server(server) +>>> import time; time.sleep(2) # wait a bit + +Making a POST: + +>>> res = urlopen(baseurl, dict(txt=">>> 1 + 1\n2")).read() +>>> assert "tests" in res + +We are done: + +>>> stop_server() diff --git a/pypers/oxford/doctest_talk/ex24.py b/pypers/oxford/doctest_talk/ex24.py new file mode 100755 index 0000000..ce45cf8 --- /dev/null +++ b/pypers/oxford/doctest_talk/ex24.py @@ -0,0 +1,17 @@ +""" +>>> print "Hello, World!" +Hello, World! + +>>> print "Hello World! 2" #doctest: +ELLIPSIS +Hello ... 2 +>>> print range(1000) #doctest: +ELLIPSIS +[0, 1, 2, ..., 999] + +>>> print "ciao come va nina?" #doctest: +ELLIPSIS +ciao ... nina? + +""" + +if __name__ == "__main__": + import doctest, __main__ + doctest.testmod(__main__) diff --git a/pypers/oxford/doctest_talk/ex_inner.py b/pypers/oxford/doctest_talk/ex_inner.py new file mode 100755 index 0000000..b59dc3d --- /dev/null +++ b/pypers/oxford/doctest_talk/ex_inner.py @@ -0,0 +1,16 @@ +# split.py +import re + +def outer(): + def split(text, sep = re.compile(r"\s*[,;]\s*")): + """Split a string taking as separators "," ";". + Example: + >>> from split import split + >>> split("hello, world!; welcome to the Italian Code Jam!") + ['hello', 'world!', 'welcome to the Italian Code Jam!'] + """ + return sep.split(text) + +if __name__ == "__main__": + import __main__, doctest + doctest.testmod(__main__) diff --git a/pypers/oxford/doctest_talk/index.html b/pypers/oxford/doctest_talk/index.html new file mode 100755 index 0000000..870e229 --- /dev/null +++ b/pypers/oxford/doctest_talk/index.html @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> +<meta name="generator" content="Docutils 0.3.4: http://docutils.sourceforge.net/" /> +<title>Partecs Training: Internal Documentation</title> +<link rel="stylesheet" href="default.css" type="text/css" /> +</head> +<body> +<h1 class="title">Partecs Training: Internal Documentation</h1> +<div class="document" id="partecs-training-internal-documentation"> +<p><a class="reference" href="http://wiki.partecs.com/Developers/PartecsTraining/P01.html">Michele's slides for the Italian Code Jam conference</a></p> +</div> +</body> +</html> diff --git a/pypers/oxford/doctest_talk/index.txt b/pypers/oxford/doctest_talk/index.txt new file mode 100755 index 0000000..8988e3e --- /dev/null +++ b/pypers/oxford/doctest_talk/index.txt @@ -0,0 +1,6 @@ +Partecs Training: Internal Documentation +========================================= + +`Michele's slides for the Italian Code Jam conference`__ + +__ http://wiki.partecs.com/Developers/PartecsTraining/P01.html diff --git a/pypers/oxford/doctest_talk/maketalk.py b/pypers/oxford/doctest_talk/maketalk.py new file mode 100755 index 0000000..abd3394 --- /dev/null +++ b/pypers/oxford/doctest_talk/maketalk.py @@ -0,0 +1,98 @@ +#!/usr/bin/python +import exc_debugger, webbrowser +from ms.html_utils import makelink, TableList, tidy +import os, sys, re +INCLUDE = re.compile(r"\n.. include::\s+([\w\./]+)") + +BGCOLOR = "lightblue" +CSS = """ +<STYLE TYPE="text/css"> + body { font-size: 160%; } +</STYLE> +""" + +class HTMLDocument(list): + def __init__(self, ls = []): + super(HTMLDocument, self).__init__(ls) + self.reorder() + def reorder(self): + """Generates circular 'prev' and 'next' tabs.""" + self.npages = len(self) + self.pageindex = [] + for i, page in enumerate(self): + page.prev = self[i-1].namext + if i == self.npages-1: i = -1 + page.next = self[i+1].namext + page.first = self[0].namext + page.last = self[-1].namext + page.document = self + self.pageindex.append(page) + +class HTMLPage(object): + def __init__(self, id_txt): + self.BGCOLOR = BGCOLOR + id, txt = id_txt + if isinstance(id, int): + self.name = "P%02d" % (id + 1) + else: + self.name = id + self.namext = self.name + ".html" + lines = txt.splitlines() + if lines: # empty page + self.title = lines[0] + self.txt = "\n".join(lines[2:]) + else: + self.title = "" + self.txt = "" + self.txt = "<h1>%s</h1><br/>\n" % self.title + \ + INCLUDE.sub(lambda m: "<pre>%s</pre>" % + file(m.group(1)).read(), self.txt) + self.head = """ + <head> + <meta name="generator" content="Generated by Python"> + <title>%s</title> + %s + </head>""" % (self.name, CSS) + def html(self): + self.body = """\n<body bgcolor="%(BGCOLOR)s">\n + %(txt)s + </body> + """ % vars(self) + return """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> + <html>%s\n</html>""" % (self.head + self.body) + def __str__(self): + box = TableList.make( + makelink(self.next, "Next"), + makelink(self.prev, "Prev"), + makelink(self.last, "Last"), + makelink(self.first, "First"), + "Current", + self.name, + border = 0, + color = BGCOLOR) + logo = TableList.col( + '<img src = "accu2005.png" alt = "logo">', + border = 0, + color = BGCOLOR) + links = [makelink(page.namext, page.name) + for page in self.document.pageindex] + opts = dict(border = 0, color = BGCOLOR, ncols=2) + index = TableList.make(*links, **opts) + self.txt = str( + TableList.row("<small>%s</small>" % TableList.col(box, index, logo, + border = 0, color = BGCOLOR), + self.txt, border = 0, color = BGCOLOR)) + return self.html() + #return tidy(self.html(), "-i")[0] + +def main(fname): + BLANK = re.compile(r"\n\n\n\s*") + chunks = BLANK.split(file(fname).read()) + pages = HTMLDocument(map(HTMLPage, enumerate(chunks))) + for page in pages: + print >> file(page.namext, "w"), page + #os.system("xterm -e elinks P01.html&") + webbrowser.open("file:%s/P01.html" % os.getcwd()) + +if __name__ == "__main__": + main(sys.argv[1]) diff --git a/pypers/oxford/doctest_talk/more.txt b/pypers/oxford/doctest_talk/more.txt new file mode 100755 index 0000000..0e0b7fc --- /dev/null +++ b/pypers/oxford/doctest_talk/more.txt @@ -0,0 +1,33 @@ + +Doctest and exceptions +-------------------------------------- + +<pre> + +>>> from partecs_voting import vote_validator +>>> from partecs_voting.util import TheVoteIsClosedError +>>> validate = vote_validator( +... choices = ["A", "B", "C"], +... OpeningDate = "Sun Aug 29 06:00:00 2004", +... ClosingDate = "Sun Aug 30 22:00:00 2004") +>>> try: # assuming you run this after Aug 29 2004 +... validate("A") +... except TheVoteIsClosedError: +... print "The vote is closed!" +The vote is closed! + +</pre> + +Real example +---------------------------------------------------------- + +<pre> + +>>> from partecs_voting import make_ballot +>>> ballot=make_ballot(["A", "B"], [["A"], ["A"], ["B"]], "BordaBallot") +>>> print ballot.getresults() +ALL RESULTS: +A 2 +B 1 + +</pre> diff --git a/pypers/oxford/doctest_talk/refresh.txt b/pypers/oxford/doctest_talk/refresh.txt new file mode 100755 index 0000000..e69de29 --- /dev/null +++ b/pypers/oxford/doctest_talk/refresh.txt diff --git a/pypers/oxford/doctest_talk/split-failure.txt b/pypers/oxford/doctest_talk/split-failure.txt new file mode 100755 index 0000000..0bce3e2 --- /dev/null +++ b/pypers/oxford/doctest_talk/split-failure.txt @@ -0,0 +1,5 @@ +An example of failed text: + +>>> from split import split +>>> split("hello, world") +['hello', ' world'] diff --git a/pypers/oxford/doctest_talk/split-failure_txt.py b/pypers/oxford/doctest_talk/split-failure_txt.py new file mode 100755 index 0000000..6819fb5 --- /dev/null +++ b/pypers/oxford/doctest_talk/split-failure_txt.py @@ -0,0 +1,8 @@ +""" +An example of failed text: + +>>> from split import split +>>> split("hello, world") +['hello', ' world'] + +""" diff --git a/pypers/oxford/doctest_talk/split.html b/pypers/oxford/doctest_talk/split.html new file mode 100755 index 0000000..3cc867a --- /dev/null +++ b/pypers/oxford/doctest_talk/split.html @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> +<meta name="generator" content="Docutils 0.3.4: http://docutils.sourceforge.net/" /> +<title>Documentation for the 'split' module</title> +<link rel="stylesheet" href="default.css" type="text/css" /> +</head> +<body> +<h1 class="title">Documentation for the 'split' module</h1> +<div class="document" id="documentation-for-the-split-module"> +<p>The module contains a 'split' function, which +splits a string taking as separators "," and ";". +This is an example of usage:</p> +<pre class="doctest-block"> +>>> from split import split +>>> split("hello, world!; welcome to the Italian Code Jam!") +['hello', 'world!', 'welcome to the Italian Code Jam!'] +</pre> +<p>Notice that 'split' eats whitespaces:</p> +<pre class="doctest-block"> +>>> split("hello , world") +['hello', 'world'] +</pre> +<pre class="doctest-block"> +>>> split("hello , ; world") +['hello', '', 'world'] +</pre> +</div> +</body> +</html> diff --git a/pypers/oxford/doctest_talk/split.py b/pypers/oxford/doctest_talk/split.py new file mode 100755 index 0000000..fda986c --- /dev/null +++ b/pypers/oxford/doctest_talk/split.py @@ -0,0 +1,15 @@ +# split.py +import re +SEP = re.compile(r"\s*[,;]\s*") + +def split(text): + """Split a string taking as separators "," ";". + Example: + >>> from split import split + >>> split("hello, world!; welcome to PyUK!") + ['hello', 'world!', 'welcome to PyUK!'] + """ + return SEP.split(text) + +if __name__ == "__main__": + import doctest; doctest.testmod() diff --git a/pypers/oxford/doctest_talk/split.txt b/pypers/oxford/doctest_talk/split.txt new file mode 100755 index 0000000..8e3e8fe --- /dev/null +++ b/pypers/oxford/doctest_talk/split.txt @@ -0,0 +1,18 @@ +Documentation for the 'split' module +===================================== + +The module contains a 'split' function, which +splits a string taking as separators "," and ";". +This is an example of usage: + +>>> from split import split +>>> split("hello, world!; welcome to the Italian Code Jam!") +['hello', 'world!', 'welcome to the Italian Code Jam!'] + +Notice that 'split' eats whitespaces: + +>>> split("hello , world") +['hello', 'world'] + +>>> split("hello , ; world") +['hello', '', 'world'] diff --git a/pypers/oxford/doctest_talk/talk.txt b/pypers/oxford/doctest_talk/talk.txt new file mode 100755 index 0000000..c3b2991 --- /dev/null +++ b/pypers/oxford/doctest_talk/talk.txt @@ -0,0 +1,403 @@ +Automatic testing in Python: wonderful doctest! +=============================================== + +<center> + + ACCU Conference 2005 <br/> <br/> + + 22 Apr 2005 <br/> <br/> + + Michele Simionato <br/> <br/> + + michele.simionato@gmail.com <br/> <br/> + +</center> + + +Summary +------------ + +<ul> + <li> What is automatic testing? </li> + <li> Why automatic testing is better? </li> + <li> Which kind of automatic testing? </li> + <li> How does it work, in practice? </li> + <li> What's the message?</li> +<ul> + + +What is automatic testing +------------------------- + +Any methodology that allows you to test +your application mechanically, repeatedly +and in a <em>controlled reproducible</em> way. + + +Automatic testing is better (1) +----------------------------------- + +When doing manual testing typically you spend + +<center><h2> + + 1 hour of coding + 10 hours of testing/debugging + +</center></h2> + +on the other hand ... + + +Automatic testing is better (2) +----------------------------------- + +... when doing automatic testing typically you spend + +<br/> <br/> +<center><h2> + + 1 hour of coding + 10 hours of testing/debugging ! + +</center></h2> + + +However ... +--------------------------------------- + +Think about six month later! + <br/><br/> +<center><em> + + there is a difference</em> + + <h2><u>Refactoring!</u><h2> + +</center> + + +Automatic testing in Python +------------------------------- + +There are two standard testing frameworks in Python: + +<ol> + <li> unittest </li> + <li> doctest </li> +</ol> + +Which one should I use? + + +Well, +------------------------- + +since my talk has <em>doctest</em> in the title ... + + ;-) + + +More seriously ... +-------------------------- + +Use different testing frameworks; each one has advantages +and disadvantages; use combinations of them; invent your +own testing procedure. + +I use combinations of + +<ul> + <li> unittest </li> + <li> doctest </li> + <li> custom tests </li> + <li> Makefile driven tests </li> + <li> et al. </li> +</ul> + +doctest emphasis is on <em>documentation</em> + + +What is doctest? +-------------------------------- + +In its simplest form (which I do not use that much) doctest allows +you to include tests in the docstrings of your application. + + +Example +------- + +.. include:: split.py + + +Running doctest in verbose mode +-------------------------------------------------------------------- + +<pre> +$ python split.py -v +Running __main__.__doc__ +0 of 0 examples failed in __main__.__doc__ +Running __main__.split.__doc__ +Trying: from split import split +Expecting: nothing +ok +Trying: split("hello, world!; welcome to Oxford!") +Expecting: ['hello', 'world!', 'welcome to Oxford!'] +ok +0 of 2 examples failed in __main__.split.__doc__ +1 items had no tests: + __main__ +1 items passed all tests: + 2 tests in __main__.split +2 tests in 2 items. +2 passed and 0 failed. +Test passed. +</pre> + + +Why I do not use the docstring approach +------------------------------------------------------- + +<ul> +<li> It makes you end up with very large docstrings</li> + +<li> It abuses the original purpose of docstrings</li> + +<li> It conflates two different aspects (code and tests on the code)</li> + +<li> It is much easier to write the documentation in a separate + text file </li> + +<li> Testing should be done by an external tool anyway </li> +</ul> + + +How I use doctest +--------------------- + +I hacked inside doctest and wrote a custom utility +to extract doctests from documentation files since + +<ul> + <li>I like keeping the documentation on a separate rst file</li> + + <li>there is no sync problem since you run the tests all the time</li> + + <li>it is useful for writing articles ...</li> + + <li> ... but also documentation for internal usage in the company</li> +</ul> + +http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/410052 + +<pre> +$ python -m doctester < split.txt +doctest: run 4 tests, failed 0 +</pre> + + +Testing the doctester frontend +-------------------------------------------------- + +<pre> +>>> from ms.webtester import start_server, stop_server +>>> from ms.http_utils import urlopen +>>> baseurl = "http://localhost:7080/" +>>> home = "/home/micheles/md/python/quixote/" + +>>> start_server(home + "doctester_frontend.py") +>>> import time; time.sleep(2) # wait a bit + +Making a POST: + +>>> res = urlopen(baseurl, dict(txt=">>> 1 + 1\n2")).read() +>>> assert "tests" in res +>>> stop_server() + +</pre> + + +Managing exceptions +-------------------------------------------------------------- + +It is possible to test that your program raises the exception you +expect: + +<pre> + +$ echo "# split cannot work on a list +>>> from split import split +>>> split([]) +Traceback (most recent call last): + ... +TypeError: expected string or buffer +" > x.txt + +$ doct x.txt +x.txt: 2 tests passed in 0.01 seconds + +</pre> + +(notice however that relying on exception messages may be risky) + + +When tests fail +----------------------------------------------------------------- + +<pre> + +$ cat split-failure.txt +An example of failed text: + +>>> from split import split +>>> split("hello, world") +['hello', ' world'] + +$ doct split-failure.txt +***************************************************************** +Failure in example: split("hello, world") +from line #5 of split-failure.txt +Expected: ['hello', ' world'] +Got: ['hello', 'world'] + +</pre> + + +Converting doctests to unittests +------------------------------------------------------------------ + +<pre> + import unittest + import doctest + import my_module_with_doctests + + suite = doctest.DocTestSuite(my_module_with_doctests) + runner = unittest.TextTestRunner() + runner.run(suite) +</pre> + +<h2>For Python 2.3+<h2> + + +doctest is becoming even better +---------------------------------------------------- + +With Python 2.4 you can run doctests on external text files: + +<pre> + import doctest, unittest + doctest.testfile(my_documentation_file, package=mypackage) +</pre> + +you can also convert these doctests into unittests: + +<pre> + import doctest, unittest + suite = doctest.DocFileSuite(my_documentation_file, package=mypackage) + unittest.TextTestRunner().run(suite) +</pre> + + +Python 2.4 recognizes blank lines +-------------------------------------------------------------- + +Blank lines can be marked with <BLANKLINE> : +<pre> +>>> print 'foo\n\nbar\n' +foo +<BLANKLINE> +bar +<BLANKLINE> + +</pre> + + +Python 2.4 recognizes flags! +-------------------------------------------------------------- + +<ul> +<li> If the ellipsis flag is used, then '...' can be used to + elide substrings in the desired output: <pre> +>>> print range(1000) #doctest: +ELLIPSIS +[0, 1, 2, ..., 999] + +</pre></li> + +<li> + If the whitespace normalization flag is used, then + differences in whitespace are ignored.<pre> +>>> print range(20) #doctest: +NORMALIZE_WHITESPACE +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, +12, 13, 14, 15, 16, 17, 18, 19] + +</pre></li> + +</ul> + + +Zope experience +---------------------------------------------------------------- + +Literal quote from the PyCON doctest talk: + +<ul> +<li> ~ 5600 tests (~3500 in Zope 3, ~1300 in ZODB, ~800 in Zope 2)</li> +<li> we wrote lots of tests before we knew what we were doing</li> +<li> debugging failed tests is really hard when intent is unclear</li> +<li> often refactor or reimplement tests to make them clearer</li> +<li> most new tests are doctest based</li> +</ul> + + +Conclusion (1): good reasons to use doctest +----------------------------------------------------------- + +<quote> +"Test coverage is important, but test readability is much more important" +</quote> + +<em>-- Tim Peters and Jim Fulton</em> <br/> <br/> + +doctest is good since: + +<ol> + <li> it is easy to understand, to explain and to use </li> + + <li> it makes you improve the quality of your documentation </li> + + <li> it can be converted to unittest anyway </li> + +</ol> + + +Conclusion (2): the message of this talk +----------------------------------------------------------- + +Automatic testing is good for tons of practical reasons, but also +because: + +<ol> + +<li>It teaches you <em>discipline</em> </li> + +<li>It makes you + <em>think differently</em> </li> + +<li>It is more <em>fun!</em> </li> + +</ol> + + +References +----------------------------------------------------------- + +<ul> + +<li>The standard library documentation +http://docs.python.org/lib/module-doctest.html </li> + +<li> The doctest talk by Tim Peters and Jim Fulton +http://www.python.org/pycon/dc2004/papers/4/</li> + +<li> doctest.py <em>(use the source, Luke!)</em></li> +</ul> + diff --git a/pypers/oxford/doctest_talk/test_pkg/__init__.py b/pypers/oxford/doctest_talk/test_pkg/__init__.py new file mode 100755 index 0000000..3963e63 --- /dev/null +++ b/pypers/oxford/doctest_talk/test_pkg/__init__.py @@ -0,0 +1,6 @@ +"""Trying to doctest a package. + +>>> 1 + 1 == 2 +True + +""" diff --git a/pypers/oxford/doctest_talk/test_pkg/a.py b/pypers/oxford/doctest_talk/test_pkg/a.py new file mode 100755 index 0000000..ef2abf0 --- /dev/null +++ b/pypers/oxford/doctest_talk/test_pkg/a.py @@ -0,0 +1,7 @@ +""" +Module a + +>>> 1 + 1 == 2 +True + +""" diff --git a/pypers/oxford/doctest_talk/test_pkg/b.py b/pypers/oxford/doctest_talk/test_pkg/b.py new file mode 100755 index 0000000..f17cd98 --- /dev/null +++ b/pypers/oxford/doctest_talk/test_pkg/b.py @@ -0,0 +1,7 @@ +""" +Module b + +>>> 1 + 1 == 2 +True + +""" diff --git a/pypers/oxford/doctest_talk/testfile_ex.py b/pypers/oxford/doctest_talk/testfile_ex.py new file mode 100755 index 0000000..3d85fcc --- /dev/null +++ b/pypers/oxford/doctest_talk/testfile_ex.py @@ -0,0 +1,11 @@ +import doctest, unittest +suite = doctest.DocFileSuite("example.txt", package='ms.test') +unittest.TextTestRunner().run(suite) + +# alternatively + +#import types +#f = file("/home/micheles/md/scripts/ms/test/example.txt") +#mod = types.ModuleType("example", f.read()) +#suite = doctest.DocTestSuite(mod) +#unittest.TextTestRunner().run(suite) diff --git a/pypers/oxford/doctest_talk/the_story.txt b/pypers/oxford/doctest_talk/the_story.txt new file mode 100755 index 0000000..ccdbdbd --- /dev/null +++ b/pypers/oxford/doctest_talk/the_story.txt @@ -0,0 +1,23 @@ +- a physicist perspective +- the story of this talk +- making the slides +- what I wanted to realize +- shallow knowledge +- browser requirements +- a word about security +- the preparation of my lectures +- emacs +- importance of documentation +- the right mindset +- the doctester at work +- real life experience with outsourcing +- if you want to start a new company ... +- /home/micheles/code/branches/1.3/lib/python/partecs_voting/tests +- doctest vs. unittest +- show the slides till page 6 +- a word about converting to testing methodologies +- doctesting web applications +- doctest 2 unittest +- zope experience +- cleverness vs. wisdom +- three things to remember diff --git a/pypers/oxford/doctest_talk/x.html b/pypers/oxford/doctest_talk/x.html new file mode 100755 index 0000000..57bfa5f --- /dev/null +++ b/pypers/oxford/doctest_talk/x.html @@ -0,0 +1,203 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> + +<html> +<head> + <meta name="generator" content= + "HTML Tidy for Linux/x86 (vers 1st March 2004), see www.w3.org"> + <meta name="generator" content="Generated by Python"> + + <title>P01</title> + <style type="text/css"> + body { font-size: 160%; } + </style> +</head> + +<body bgcolor="lightblue"> + <table border="0"> + <tr> + <td bgcolor="lightblue"> + <table border="0"> + <tr> + <td bgcolor="lightblue"> + <table border="0"> + <tr> + <td bgcolor="lightblue"><img src= + "cjlogo.jpg"></td> + </tr> + </table> + </td> + </tr> + + <tr> + <td bgcolor="lightblue"> + <table border="0"> + <tr> + <td bgcolor="lightblue"><a href= + "P02.html">Next</a></td> + + <td bgcolor="lightblue"><a href= + "P26.html">Prev</a></td> + </tr> + + <tr> + <td bgcolor="lightblue"><a href= + "P01.html">First</a></td> + + <td bgcolor="lightblue"><a href= + "P26.html">Last</a></td> + </tr> + + <tr> + <td bgcolor="lightblue"><a href= + "P01.html">P01</a></td> + + <td bgcolor="lightblue"></td> + </tr> + + <tr> + <td bgcolor="lightblue"></td> + + <td bgcolor="lightblue"></td> + </tr> + </table> + </td> + </tr> + + <tr> + <td bgcolor="lightblue"> + <table border="0"> + <tr> + <td bgcolor="lightblue"><a href= + "P01.html">P01</a></td> + + <td bgcolor="lightblue"><a href= + "P02.html">P02</a></td> + </tr> + + <tr> + <td bgcolor="lightblue"><a href= + "P03.html">P03</a></td> + + <td bgcolor="lightblue"><a href= + "P04.html">P04</a></td> + </tr> + + <tr> + <td bgcolor="lightblue"><a href= + "P05.html">P05</a></td> + + <td bgcolor="lightblue"><a href= + "P06.html">P06</a></td> + </tr> + + <tr> + <td bgcolor="lightblue"><a href= + "P07.html">P07</a></td> + + <td bgcolor="lightblue"><a href= + "P08.html">P08</a></td> + </tr> + + <tr> + <td bgcolor="lightblue"><a href= + "P09.html">P09</a></td> + + <td bgcolor="lightblue"><a href= + "P10.html">P10</a></td> + </tr> + + <tr> + <td bgcolor="lightblue"><a href= + "P11.html">P11</a></td> + + <td bgcolor="lightblue"><a href= + "P12.html">P12</a></td> + </tr> + + <tr> + <td bgcolor="lightblue"><a href= + "P13.html">P13</a></td> + + <td bgcolor="lightblue"><a href= + "P14.html">P14</a></td> + </tr> + + <tr> + <td bgcolor="lightblue"><a href= + "P15.html">P15</a></td> + + <td bgcolor="lightblue"><a href= + "P16.html">P16</a></td> + </tr> + + <tr> + <td bgcolor="lightblue"><a href= + "P17.html">P17</a></td> + + <td bgcolor="lightblue"><a href= + "P18.html">P18</a></td> + </tr> + + <tr> + <td bgcolor="lightblue"><a href= + "P19.html">P19</a></td> + + <td bgcolor="lightblue"><a href= + "P20.html">P20</a></td> + </tr> + + <tr> + <td bgcolor="lightblue"><a href= + "P21.html">P21</a></td> + + <td bgcolor="lightblue"><a href= + "P22.html">P22</a></td> + </tr> + + <tr> + <td bgcolor="lightblue"><a href= + "P23.html">P23</a></td> + + <td bgcolor="lightblue"><a href= + "P24.html">P24</a></td> + </tr> + + <tr> + <td bgcolor="lightblue"><a href= + "P25.html">P25</a></td> + + <td bgcolor="lightblue"><a href= + "P26.html">P26</a></td> + </tr> + + <tr> + <td bgcolor="lightblue"></td> + + <td bgcolor="lightblue"></td> + </tr> + </table> + </td> + </tr> + </table> + </td> + + <td bgcolor="lightblue"> + <h1>Automatic testing in Python: wonderful + doctest!</h1><br> + + <center> + Italian Code Jam<br> + <br> + 09 Oct 2004<br> + <br> + Michele Simionato<br> + <br> + m.simionato@partecs.com<br> + <br> + Partecs s.r.l. + </center><br> + </td> + </tr> + </table> +</body> +</html> diff --git a/pypers/oxford/doctest_talk/x.py b/pypers/oxford/doctest_talk/x.py new file mode 100755 index 0000000..6c263e8 --- /dev/null +++ b/pypers/oxford/doctest_talk/x.py @@ -0,0 +1,19 @@ +r""" +>>> print "foo\n\nbar\n" +foo +<BLANKLINE> +bar +<BLANKLINE> + +>>> print range(1000) #doctest: +ELLIPSIS +[0, 1, 2, ..., 999] + +>>> print range(20) #doctest: +NORMALIZE_WHITESPACE +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, +12, 13, 14, 15, 16, 17, 18, 19] + +""" + +if __name__ == "__main__": + import doctest, __main__ + doctest.testmod(__main__) diff --git a/pypers/oxford/doctest_talk/x.txt b/pypers/oxford/doctest_talk/x.txt new file mode 100755 index 0000000..2bf5990 --- /dev/null +++ b/pypers/oxford/doctest_talk/x.txt @@ -0,0 +1,55 @@ +line 17 column 27 - Warning: missing </small> before <table> +line 95 column 1 - Warning: discarding unexpected </small> +line 12 column 1 - Warning: <body> attribute "bgcolor" has invalid value "lightblue" +line 17 column 3 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 20 column 3 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 23 column 3 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 29 column 3 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 32 column 3 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 32 column 60 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 35 column 3 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 35 column 60 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 38 column 3 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 38 column 40 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 41 column 3 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 41 column 33 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 47 column 3 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 50 column 3 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 50 column 59 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 53 column 3 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 53 column 59 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 56 column 3 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 56 column 59 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 59 column 3 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 59 column 59 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 62 column 3 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 62 column 59 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 65 column 3 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 65 column 59 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 68 column 3 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 68 column 59 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 71 column 3 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 71 column 59 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 74 column 3 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 74 column 59 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 77 column 3 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 77 column 59 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 80 column 3 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 80 column 59 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 83 column 3 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 83 column 59 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 86 column 3 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 86 column 59 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 89 column 3 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 89 column 33 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 95 column 15 - Warning: <td> attribute "bgcolor" has invalid value "lightblue" +line 17 column 27 - Warning: trimming empty <small> +Info: Doctype given is "-//W3C//DTD HTML 4.01//EN" +Info: Document content looks like HTML 4.01 Transitional +46 warnings, 0 errors were found! + + +To learn more about HTML Tidy see http://tidy.sourceforge.net +Please send bug reports to html-tidy@w3.org +HTML and CSS specifications are available from http://www.w3.org/ +Lobby your company to join W3C, see http://www.w3.org/Consortium diff --git a/pypers/oxford/doctester.py b/pypers/oxford/doctester.py new file mode 100755 index 0000000..ec4f964 --- /dev/null +++ b/pypers/oxford/doctester.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +# Author: michele.simionato@gmail.com +"""\ +Filter passing stdin through doctest. Example of usage: +$ doctester.py -v < file.txt +""" +import sys, doctest, textwrap, re, types +#import warnings;warnings.filterwarnings('ignore', category=DeprecationWarning) + +# regular expressions to identify code blocks of the form +#<scriptname.py> ... </scriptname.py> +DOTNAME = r'\b[a-zA-Z_][\w\.]*', # identifier with or without dots +SCRIPT = re.compile(r'(?s)#<(%s)>(.*?)#</\1>' % DOTNAME) + +# a simple utility to extract the scripts contained in the original text +def scripts(txt): + for MO in SCRIPT.finditer(txt): + yield MO.group(1), textwrap.dedent(MO.group(2)) + +# save the scripts in the current directory +def savescripts(txt): + scriptdict = {} + for scriptname, script in scripts(txt): # read scripts + if scriptname not in scriptdict: + scriptdict[scriptname] = script + else: + scriptdict[scriptname] += script + for scriptname in scriptdict: # save scripts + code = '# ' + scriptname + scriptdict[scriptname] + print >> file(scriptname, 'w'), code + +# based on a clever trick: it converts the original text into the docstring of +# the main module; works both for Python 2.3 and 2.4; +# main is needed to keep global variables in it (for instance to keep +# threads working) +def runtests(txt, verbose=False): + savescripts(txt) + try: + main = __import__("_main_") + except ImportError: + main = types.ModuleType("__main__") + main.__doc__ = txt + failed, tot = doctest.testmod(main, verbose=verbose) + doctest.master = None # cleanup the DocTestRunner + # needed to avoid a warning in case of multiple calls of runtests + if not verbose: + print >> sys.stderr, "doctest: run %s tests, failed %s" % (tot, failed) + # remove scripts + return failed, tot + +if __name__ == '__main__': + try: set # need sets for option parsing + except NameError: import sets; set = sets.Set # for Python 2.3 + valid_options = set("-v -h".split()) + options = set(sys.argv[1:]) + assert options < valid_options, "Unrecognized option" + if "-h" in options: # print usage message and exit + sys.exit(__doc__) + runtests(sys.stdin.read(), "-v" in options) diff --git a/pypers/oxford/dynamic_pages.py b/pypers/oxford/dynamic_pages.py new file mode 100755 index 0000000..0e6369f --- /dev/null +++ b/pypers/oxford/dynamic_pages.py @@ -0,0 +1,9 @@ +class WebApplication(object): + def __getattr__(self, name): + return "Page %s" % name + + +app = WebApplication() + +print app.page1 +print app.page2 diff --git a/pypers/oxford/evilprop.py b/pypers/oxford/evilprop.py new file mode 100755 index 0000000..655df20 --- /dev/null +++ b/pypers/oxford/evilprop.py @@ -0,0 +1,18 @@ +# evilprop.py
+
+def convert2property(name, bases, d):
+ return property(d.get('get'), d.get('set'),
+ d.get('del'),d.get('__doc__'))
+
+class C(object):
+ class x:
+ """An evil test property"""
+ __metaclass__ = convert2property
+ def get(self):
+ print 'Getting %s' % self._x
+ return self._x
+ def set(self, value):
+ self._x = value
+ print 'Setting to', value
+
+
diff --git a/pypers/oxford/ex.py b/pypers/oxford/ex.py new file mode 100755 index 0000000..3868a1e --- /dev/null +++ b/pypers/oxford/ex.py @@ -0,0 +1,26 @@ +class Base(object): + def __init__(self): + print "B.__init__" + +class MyClass(Base): + "I do not cooperate with others" + def __init__(self): + print "MyClass.__init__" + Base.__init__(self) #instead of super(MyClass, self).__init__() + + +class Mixin(Base): + "I am cooperative with others" + def __init__(self): + print "Mixin.__init__" + super(Mixin, self).__init__() + + +class HerClass(MyClass, Mixin): + "I am cooperative too" + def __init__(self): + print "HerClass.__init__" + super(HerClass, self).__init__() + + +h = HerClass() diff --git a/pypers/oxford/flatten.py b/pypers/oxford/flatten.py new file mode 100755 index 0000000..0695967 --- /dev/null +++ b/pypers/oxford/flatten.py @@ -0,0 +1,27 @@ + +## def flatten(container): +## for obj in container: +## if hasattr(obj, "__iter__"): +## for el in flatten(obj): +## yield el +## else: +## yield obj + +def walk(container, level=0): + for obj in container: + if not hasattr(obj, "__iter__"): + yield obj, level + else: + for subobj, lvl in walk(obj, level + 1): + yield subobj, lvl + +def pprint(container): + for obj, level in walk(container): + print " "*level, obj + +def flatten(container): + return (obj for obj, level in walk(container)) + +nested_ls = [1,[2,[3,[[[4,5],6]]]],7] +pprint(nested_ls) +pprint(flatten(nested_ls)) diff --git a/pypers/oxford/for_loop.py b/pypers/oxford/for_loop.py new file mode 100755 index 0000000..fc062d1 --- /dev/null +++ b/pypers/oxford/for_loop.py @@ -0,0 +1,15 @@ +result = [] +for i in range(10): + result.append(lambda : i) + + +# could be converted in + +result = [] +def for_block(i): + result.append(lambda : i) +for i in range(10): for_block(i) + +#func = list(lambda : i for i in range(10)) +#print func[0]() +print result[1]() diff --git a/pypers/oxford/frontpage.txt b/pypers/oxford/frontpage.txt new file mode 100755 index 0000000..160418d --- /dev/null +++ b/pypers/oxford/frontpage.txt @@ -0,0 +1,10 @@ +Lectures on Advanced Python Programming +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. image:: accu2005.png + +:Author: Michele Simionato +:Given: 19 April 2005 +:Revised: 7 September 2005 + +.. contents:: diff --git a/pypers/oxford/gen_with_attr.py b/pypers/oxford/gen_with_attr.py new file mode 100755 index 0000000..ee1afd5 --- /dev/null +++ b/pypers/oxford/gen_with_attr.py @@ -0,0 +1,21 @@ +class MyIter(object): + def __iter__(self): + yield 1 + yield 2 + yield 3 + +it = MyIter() +for i in it: print i + +i0 = MyIter() +i1 = iter(i0) +i2 = iter(i1) + +print i0 is i1 +print i1 is i2 + +it = MyIter() +print it, iter(it) +print iter(it), iter(iter(it)) + + diff --git a/pypers/oxford/getlevel.py b/pypers/oxford/getlevel.py new file mode 100755 index 0000000..e86d50c --- /dev/null +++ b/pypers/oxford/getlevel.py @@ -0,0 +1,9 @@ +def getlevel(obj, lvl, i): + if lvl == 0: + return obj[i] + else: + return getlevel(obj[i], lvl-1) + +ls = [[0, [1, [2], 3], 4, 5]] + +print getlevel(ls, 0, 1) diff --git a/pypers/oxford/htmltable.py b/pypers/oxford/htmltable.py new file mode 100755 index 0000000..ed46745 --- /dev/null +++ b/pypers/oxford/htmltable.py @@ -0,0 +1,19 @@ +# htmltable.py
+
+def HTMLTablegen(table):
+ yield "<table>"
+ for row in table:
+ yield "<tr>"
+ for col in row:
+ yield "<td>%s</td>" % col
+ yield "</tr>"
+ yield "</table>"
+
+def test():
+ return "\n".join(HTMLTablegen([["Row", "City"],
+ [1,'London'], [2, 'Oxford']]))
+
+if __name__ == "__main__": # example
+ print test()
+
+
diff --git a/pypers/oxford/import_with_meta.py b/pypers/oxford/import_with_meta.py new file mode 100755 index 0000000..92ef395 --- /dev/null +++ b/pypers/oxford/import_with_meta.py @@ -0,0 +1,18 @@ +# import_with_metaclass.py + +import inspect, types + +def import_with_metaclass(metaclass, modname): + "Module importer substituting custom metaclass" + dct = {'__module__' : modname} + mod = __import__(modname) + for key, val in mod.__dict__.items(): + if inspect.isclass(val): + if isinstance(val, types.ClassType): + bases = (val, object) # convert old-style to new-style + else: + bases = (val,) + setattr(mod, key, metaclass(key, bases, dct)) + return mod + + diff --git a/pypers/oxford/import_with_metaclass.py b/pypers/oxford/import_with_metaclass.py new file mode 100755 index 0000000..bc7175c --- /dev/null +++ b/pypers/oxford/import_with_metaclass.py @@ -0,0 +1,23 @@ +# import_with_metaclass.py
+"""
+``import_with_metaclass(metaclass, modulepath)`` generates
+a new module from and old module, by enhancing all of its classes.
+This is not perfect, but it should give you a start."""
+
+import os, sys, inspect, types
+
+def import_with_metaclass(metaclass, modulepath):
+ modname = os.path.basename(modulepath)[:-3] # simplistic
+ mod = types.ModuleType(modname)
+ locs = dict(
+ __module__ = modname,
+ __metaclass__ = metaclass,
+ object = metaclass("object", (), {}))
+ execfile(modulepath, locs)
+ for k, v in locs.iteritems():
+ if inspect.isclass(v): # otherwise it would be "__builtin__"
+ v.__module__ = "__dynamic__"
+ setattr(mod, k, v)
+ return mod
+
+
diff --git a/pypers/oxford/index.txt b/pypers/oxford/index.txt new file mode 100755 index 0000000..66c7cd2 --- /dev/null +++ b/pypers/oxford/index.txt @@ -0,0 +1,83 @@ +ACCU Conference 2005 - Advanced Python Course Program +========================================================= + +This course is intended for intermediate Python programmers who want to +raise their knowledge of modern Python to an advanced/expert level. + +The course will begin by discussing in detail how Python +works behing the scenes (what really happens when you +write a for loop? what really happens when you access an attribute? +what really happens when you define a class?) and explaining a few +tricky/subtle points. Then, I will move to examples of real life use +cases where the advanced techniques explained here have been useful to me, +as well to cases where I have decided *not* to use those techniques. +To keep the attention of the public, as well as for exemplification +purposes, some time will be spent discussing a few cool hacks and recipes +from the second edition of the Python Cookbook. + +The six hour course is split in three sessions on the same day. +The session titles are: + + 1. Loops (i.e. iterators & generators) + 2. Objects (i.e. delegation & inheritance) + 3. Magic (i.e. decorators & metaclasses) + +Lecture 1 is probably the most useful to the average Python programmer; +lecture 2 is of interest to programmers who wants to unveil the secrets +of the new-style object model; lecture 3 delves into the dark side of Python. + +Lecture 1: Loops (i.e. iterators & generators) +----------------------------------------------------------- + +- the seemingly trivial 'for' loop; +- iterables and iterators; +- ex: read_data +- generator-comprehension; +- generators; +- real life ex: generating HTML, walk +- parsers; +- the itertools module; +- ex: chop, skip_redundant +- sorting iterables; +- iteration caveats; +- final ex: adder + +Lecture 2: Objects (i.e. delegation & inheritance) +------------------------------------------------------------------ + ++ delegation + + - descriptors; + - relationship between functions and methods; + - staticmethods & classmethods; + - ex: MultilingualAttribute + - properties; + - ex: crypted passwords; + - customized attribute access; + - ex: kwdict, DictWrapper, XMLTag + - __getattr__ subtilities; + ++ inheritance + + - subclassing builtins; __new__ vs. __init__; + - private & protected variables; + - multiple inheritance; + - usage of super and a few caveats; + - ex. DictMixin, ChainMap, interp; + - operator overriding; + +Lecture 3: Magic (i.e. decorators & metaclasses) +----------------------------------------------------------- + +- decorators: +- ex: tracing, memoize, timed, HTMLgen; +- metaclasses: why you should know they exist; +- a typical usage: tracing +- real life ex: logfile, check overriding, private/public +- additional real life ex: cmd, plug-in; +- playing with the language: restricting access, super sugar +- a few evil hacks (properties, PEP312) +- caveats: meta-attributes vs. class attributes; +- ex: __name__ (and super) +- why you should not use metaclasses in production code; +- metaclass conflicts; diff --git a/pypers/oxford/infix.py b/pypers/oxford/infix.py new file mode 100755 index 0000000..bebdaa0 --- /dev/null +++ b/pypers/oxford/infix.py @@ -0,0 +1,53 @@ +# infix.py + +class Infix: + def __init__(self, function): + self.function = function + def __ror__(self, other): + return Infix(lambda x, self=self, other=other: self.function(other, x)) + def __or__(self, other): + return self.function(other) + def __rlshift__(self, other): + return Infix(lambda x, self=self, other=other: self.function(other, x)) + def __rshift__(self, other): + return self.function(other) + def __call__(self, value1, value2): + return self.function(value1, value2) + +# Examples + +# simple multiplication +x=Infix(lambda x,y: x*y) +print 2 |x| 4 +# => 8 + +# class checking +isa=Infix(lambda x,y: x.__class__==y.__class__) +print [1,2,3] |isa| [] +print [1,2,3] <<isa>> [] +# => True + +# inclusion checking +is_in=Infix(lambda x,y: y.has_key(x)) +print 1 |is_in| {1:'one'} +print 1 <<is_in>> {1:'one'} +# => True + +# an infix div operator +import operator +div=Infix(operator.div) +print 10 |div| (4 |div| 2) +# => 5 + +# functional programming (not working in jython, use the "curry" recipe! ) +def curry(f,x): + def curried_function(*args, **kw): + return f(*((x,)+args),**kw) + return curried_function +curry=Infix(curry) + +add5= operator.add |curry| 5 +print add5(6) +# => 11 + + diff --git a/pypers/oxford/interp.py b/pypers/oxford/interp.py new file mode 100755 index 0000000..cdf7c3e --- /dev/null +++ b/pypers/oxford/interp.py @@ -0,0 +1,45 @@ +# interp.py + +import UserDict + +class Chainmap(UserDict.DictMixin): + """Combine multiple mappings for sequential lookup. Raymond Hettinger, + http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/305268 """ + + def __init__(self, *maps): + self._maps = maps + + def __getitem__(self, key): + for mapping in self._maps: + try: + return mapping[key] + except KeyError: + pass + raise KeyError(key) + + + +import sys +from string import Template + +def interp(text, repldic=None, safe_substitute=True): + caller = sys._getframe(1) + if repldic: + mapping = Chainmap(repldic, caller.f_locals, caller.f_globals) + else: + mapping = Chainmap(caller.f_locals, caller.f_globals) + t = Template(text) + if safe_substitute: + return t.safe_substitute(mapping) + else: + return t.substitute(mapping) + +## Example: + +language="Python" + +def printmsg(): + opinion = "favorite" + print interp("My $opinion language is $language.") + + diff --git a/pypers/oxford/kwdict.py b/pypers/oxford/kwdict.py new file mode 100755 index 0000000..33e2265 --- /dev/null +++ b/pypers/oxford/kwdict.py @@ -0,0 +1,10 @@ +# kwdict.py
+
+class kwdict(dict): # or UserDict, to make it to work with Zope
+ """A typing shortcut used in place of a keyword dictionary."""
+ def __getattr__(self, name):
+ return self[name]
+ def __setattr__(self, name, value):
+ self[name] = value
+
+
diff --git a/pypers/oxford/latebinding.py b/pypers/oxford/latebinding.py new file mode 100755 index 0000000..b4668ff --- /dev/null +++ b/pypers/oxford/latebinding.py @@ -0,0 +1,11 @@ +def toplevel(): + a = 1 + def f(): + print a + a = 2 + f() + +toplevel() + +func = list(lambda : i for i in range(10)) +print func[0]() diff --git a/pypers/oxford/latebinding.scm b/pypers/oxford/latebinding.scm new file mode 100755 index 0000000..27858ec --- /dev/null +++ b/pypers/oxford/latebinding.scm @@ -0,0 +1,8 @@ +(define (toplevel) + (define a 1) + (define (f) + (display a)) + (set! a 2) + (f)) + +(toplevel) diff --git a/pypers/oxford/lazy.txt b/pypers/oxford/lazy.txt new file mode 100755 index 0000000..7db1f7c --- /dev/null +++ b/pypers/oxford/lazy.txt @@ -0,0 +1,14 @@ +>>> import itertools +>>> def anyTrue(predicate, iterable): +... return True in itertools.imap(predicate, iterable) + +>>> def is3(i): +... print "i=%s" % i +... return i == 3 + +>>> anyTrue(is3, range(10)) +i=0 +i=1 +i=2 +i=3 +True diff --git a/pypers/oxford/logfile.py b/pypers/oxford/logfile.py new file mode 100755 index 0000000..4545b50 --- /dev/null +++ b/pypers/oxford/logfile.py @@ -0,0 +1,45 @@ +# logfile.py
+
+import subprocess
+
+def memoize(func):
+ memoize_dic = {}
+ def wrapped_func(*args):
+ if args in memoize_dic:
+ return memoize_dic[args]
+ else:
+ result = func(*args)
+ memoize_dic[args] = result
+ return result
+ wrapped_func.__name__ = func.__name__
+ wrapped_func.__doc__ = func.__doc__
+ wrapped_func.__dict__ = func.__dict__
+ return wrapped_func
+
+class Memoize(type): # Singleton is a special case of Memoize
+ @memoize
+ def __call__(cls, *args):
+ return super(Memoize, cls).__call__(*args)
+
+class LogFile(file):
+ """Open a file for append."""
+ __metaclass__ = Memoize
+ def __init__(self, name = "/tmp/err.log"):
+ self.viewer_cmd = 'xterm -e less'.split()
+ super(LogFile, self).__init__(name, "a")
+
+ def display(self, *ls):
+ "Use 'less' to display the log file in a separate xterm."
+ print >> self, "\n".join(map(str, ls)); self.flush()
+ subprocess.call(self.viewer_cmd + [self.name])
+
+ def reset(self):
+ "Erase the log file."
+ print >> file(self.name, "w")
+
+if __name__ == "__main__": # test
+ print >> LogFile(), "hello"
+ print >> LogFile(), "world"
+ LogFile().display()
+
+
diff --git a/pypers/oxford/loops.html b/pypers/oxford/loops.html new file mode 100755 index 0000000..7c55eac --- /dev/null +++ b/pypers/oxford/loops.html @@ -0,0 +1,753 @@ +<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<meta name="generator" content="Docutils 0.3.10: http://docutils.sourceforge.net/" />
+<title>Lecture 1: Loops (i.e. iterators & generators)</title>
+<style type="text/css">
+
+/*
+:Author: David Goodger
+:Contact: goodger@users.sourceforge.net
+:Date: $Date: 2005-06-06 15:09:07 +0200 (Mon, 06 Jun 2005) $
+:Version: $Revision: 3442 $
+:Copyright: This stylesheet has been placed in the public domain.
+
+Default cascading style sheet for the HTML output of Docutils.
+*/
+
+/* "! important" is used here to override other ``margin-top`` and
+ ``margin-bottom`` styles that are later in the stylesheet or
+ more specific. See http://www.w3.org/TR/CSS1#the-cascade */
+.first {
+ margin-top: 0 ! important }
+
+.last, .with-subtitle {
+ margin-bottom: 0 ! important }
+
+.hidden {
+ display: none }
+
+a.toc-backref {
+ text-decoration: none ;
+ color: black }
+
+blockquote.epigraph {
+ margin: 2em 5em ; }
+
+dl.docutils dd {
+ margin-bottom: 0.5em }
+
+/* Uncomment (and remove this text!) to get bold-faced definition list terms
+dl.docutils dt {
+ font-weight: bold }
+*/
+
+div.abstract {
+ margin: 2em 5em }
+
+div.abstract p.topic-title {
+ font-weight: bold ;
+ text-align: center }
+
+div.admonition, div.attention, div.caution, div.danger, div.error,
+div.hint, div.important, div.note, div.tip, div.warning {
+ margin: 2em ;
+ border: medium outset ;
+ padding: 1em }
+
+div.admonition p.admonition-title, div.hint p.admonition-title,
+div.important p.admonition-title, div.note p.admonition-title,
+div.tip p.admonition-title {
+ font-weight: bold ;
+ font-family: sans-serif }
+
+div.attention p.admonition-title, div.caution p.admonition-title,
+div.danger p.admonition-title, div.error p.admonition-title,
+div.warning p.admonition-title {
+ color: red ;
+ font-weight: bold ;
+ font-family: sans-serif }
+
+/* Uncomment (and remove this text!) to get reduced vertical space in
+ compound paragraphs.
+div.compound .compound-first, div.compound .compound-middle {
+ margin-bottom: 0.5em }
+
+div.compound .compound-last, div.compound .compound-middle {
+ margin-top: 0.5em }
+*/
+
+div.dedication {
+ margin: 2em 5em ;
+ text-align: center ;
+ font-style: italic }
+
+div.dedication p.topic-title {
+ font-weight: bold ;
+ font-style: normal }
+
+div.figure {
+ margin-left: 2em }
+
+div.footer, div.header {
+ clear: both;
+ font-size: smaller }
+
+div.line-block {
+ display: block ;
+ margin-top: 1em ;
+ margin-bottom: 1em }
+
+div.line-block div.line-block {
+ margin-top: 0 ;
+ margin-bottom: 0 ;
+ margin-left: 1.5em }
+
+div.sidebar {
+ margin-left: 1em ;
+ border: medium outset ;
+ padding: 1em ;
+ background-color: #ffffee ;
+ width: 40% ;
+ float: right ;
+ clear: right }
+
+div.sidebar p.rubric {
+ font-family: sans-serif ;
+ font-size: medium }
+
+div.system-messages {
+ margin: 5em }
+
+div.system-messages h1 {
+ color: red }
+
+div.system-message {
+ border: medium outset ;
+ padding: 1em }
+
+div.system-message p.system-message-title {
+ color: red ;
+ font-weight: bold }
+
+div.topic {
+ margin: 2em }
+
+h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
+h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
+ margin-top: 0.4em }
+
+h1.title {
+ text-align: center }
+
+h2.subtitle {
+ text-align: center }
+
+hr.docutils {
+ width: 75% }
+
+img.align-left {
+ clear: left }
+
+img.align-right {
+ clear: right }
+
+img.borderless {
+ border: 0 }
+
+ol.simple, ul.simple {
+ margin-bottom: 1em }
+
+ol.arabic {
+ list-style: decimal }
+
+ol.loweralpha {
+ list-style: lower-alpha }
+
+ol.upperalpha {
+ list-style: upper-alpha }
+
+ol.lowerroman {
+ list-style: lower-roman }
+
+ol.upperroman {
+ list-style: upper-roman }
+
+p.attribution {
+ text-align: right ;
+ margin-left: 50% }
+
+p.caption {
+ font-style: italic }
+
+p.credits {
+ font-style: italic ;
+ font-size: smaller }
+
+p.label {
+ white-space: nowrap }
+
+p.rubric {
+ font-weight: bold ;
+ font-size: larger ;
+ color: maroon ;
+ text-align: center }
+
+p.sidebar-title {
+ font-family: sans-serif ;
+ font-weight: bold ;
+ font-size: larger }
+
+p.sidebar-subtitle {
+ font-family: sans-serif ;
+ font-weight: bold }
+
+p.topic-title {
+ font-weight: bold }
+
+pre.address {
+ margin-bottom: 0 ;
+ margin-top: 0 ;
+ font-family: serif ;
+ font-size: 100% }
+
+pre.line-block {
+ font-family: serif ;
+ font-size: 100% }
+
+pre.literal-block, pre.doctest-block {
+ margin-left: 2em ;
+ margin-right: 2em ;
+ background-color: #eeeeee }
+
+span.classifier {
+ font-family: sans-serif ;
+ font-style: oblique }
+
+span.classifier-delimiter {
+ font-family: sans-serif ;
+ font-weight: bold }
+
+span.interpreted {
+ font-family: sans-serif }
+
+span.option {
+ white-space: nowrap }
+
+span.pre {
+ white-space: pre }
+
+span.problematic {
+ color: red }
+
+span.section-subtitle {
+ /* font-size relative to parent (h1..h6 element) */
+ font-size: 80% }
+
+table.citation {
+ border-left: solid thin gray }
+
+table.docinfo {
+ margin: 2em 4em }
+
+table.docutils {
+ margin-top: 0.5em ;
+ margin-bottom: 0.5em }
+
+table.footnote {
+ border-left: solid thin black }
+
+table.docutils td, table.docutils th,
+table.docinfo td, table.docinfo th {
+ padding-left: 0.5em ;
+ padding-right: 0.5em ;
+ vertical-align: top }
+
+table.docutils th.field-name, table.docinfo th.docinfo-name {
+ font-weight: bold ;
+ text-align: left ;
+ white-space: nowrap ;
+ padding-left: 0 }
+
+h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
+h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
+ font-size: 100% }
+
+tt.docutils {
+ background-color: #eeeeee }
+
+ul.auto-toc {
+ list-style-type: none }
+
+</style>
+</head>
+<body>
+<div class="document" id="lecture-1-loops-i-e-iterators-generators">
+<h1 class="title">Lecture 1: Loops (i.e. iterators & generators)</h1>
+<div class="section" id="part-i-iterators">
+<h1><a name="part-i-iterators">Part I: iterators</a></h1>
+<div class="section" id="iterators-are-everywhere">
+<h2><a name="iterators-are-everywhere">Iterators are everywhere</a></h2>
+<pre class="doctest-block">
+>>> for i in 1, 2, 3:
+... print i
+1
+2
+3
+</pre>
+<p>The 'for' loop is using <em>iterators</em> internally:</p>
+<pre class="literal-block">
+it = iter((1,2,3))
+while True:
+ try:
+ print it.next()
+ except StopIteration:
+ break
+</pre>
+</div>
+<div class="section" id="iterables-and-iterators">
+<h2><a name="iterables-and-iterators">Iterables and iterators</a></h2>
+<p><em>Iterable</em> = anything you can loop over = any sequence + any object with an __iter__ method;</p>
+<p>Not every sequence has an __iter__ method:</p>
+<pre class="doctest-block">
+>>> "hello".__iter__()
+Traceback (most recent call last):
+ ...
+AttributeError: 'str' object has no attribute '__iter__'
+</pre>
+<p><em>Iterator</em> = any object with a .next method and an __iter__ method returning self</p>
+</div>
+<div class="section" id="simpler-way-to-get-an-iterator">
+<h2><a name="simpler-way-to-get-an-iterator">Simpler way to get an iterator</a></h2>
+<pre class="doctest-block">
+>>> it = iter("hello")
+>>> it.next()
+'h'
+>>> it.next()
+'e'
+>>> it.next()
+'l'
+>>> it.next()
+'l'
+>>> it.next()
+'o'
+>>> it.next()
+Traceback (most recent call last):
+ ...
+StopIteration
+</pre>
+</div>
+<div class="section" id="sentinel-syntax-iter-callable-sentinel">
+<h2><a name="sentinel-syntax-iter-callable-sentinel">Sentinel syntax iter(callable, sentinel)</a></h2>
+<p>Example:</p>
+<pre class="literal-block">
+$ echo -e "value1\nvalue2\nEND\n" > data.txt
+$ python -c "print list(iter(file('data.txt').readline, 'END\n'))"
+['value1\n', 'value2\n']
+</pre>
+<p>Beware of infinite iterators:</p>
+<pre class="doctest-block">
+>>> repeat = iter(lambda : "some value", "")
+>>> repeat.next()
+'some value'
+</pre>
+</div>
+<div class="section" id="second-simpler-way-to-get-an-iterator-generator-expressions">
+<h2><a name="second-simpler-way-to-get-an-iterator-generator-expressions">Second simpler way to get an iterator: generator-expressions</a></h2>
+<pre class="doctest-block">
+>>> squares = (i*i for i in range(1,11))
+>>> list(squares)
+[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
+</pre>
+<p>Excessive parenthesis can be skipped, so use</p>
+<pre class="doctest-block">
+>>> dict((i, i*i) for i in range(1,11))
+{1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81, 10: 100}
+</pre>
+<p>instead of</p>
+<pre class="doctest-block">
+>>> dict([(i, i*i) for i in range(1,11)])
+{1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81, 10: 100}
+</pre>
+<p>(as usual, the most elegant version is the most efficient).</p>
+</div>
+<div class="section" id="iteration-caveats">
+<h2><a name="iteration-caveats">Iteration caveats</a></h2>
+<pre class="doctest-block">
+>>> ls = [i for i in (1,2,3)]
+>>> i
+3
+</pre>
+<pre class="doctest-block">
+>>> it = (j for j in (1,2,3))
+>>> j
+Traceback (most recent call last):
+ ...
+NameError: name 'j' is not defined
+</pre>
+<p>A subtler example:</p>
+<pre class="doctest-block">
+>>> ls = [lambda :i for i in (1,2,3)]
+>>> ls[0]()
+3
+</pre>
+<p>instead</p>
+<pre class="doctest-block">
+>>> it = (lambda :i for i in (1,2,3))
+>>> it.next()()
+1
+>>> it.next()()
+2
+>>> it.next()()
+3
+</pre>
+<p><em>seems</em> to be working but it is not really the case:</p>
+<pre class="doctest-block">
+>>> it = (lambda :i for i in (1,2,3))
+>>> f1 = it.next()
+>>> f2 = it.next()
+>>> f3 = it.next()
+>>> f1()
+3
+</pre>
+<p>The reason is that Python does LATE binding <em>always</em>. The solution is ugly:</p>
+<pre class="doctest-block">
+>>> it = list(lambda i=i:i for i in (1,2,3))
+>>> it[0]()
+1
+>>> it[1]()
+2
+>>> it[2]()
+3
+</pre>
+</div>
+</div>
+<div class="section" id="part-ii-generators">
+<h1><a name="part-ii-generators">Part II: generators</a></h1>
+<p>Trivial example:</p>
+<pre class="doctest-block">
+>>> def gen123(): # "function" which returns an iterator over the values 1, 2, 3
+... yield 1
+... yield 2
+... yield 3
+...
+>>> it = gen123()
+>>> it.next()
+1
+>>> it.next()
+2
+>>> it.next()
+3
+>>> it.next()
+Traceback (most recent call last):
+ ...
+StopIteration
+</pre>
+<p>Real life example: using generators to generate HTML tables</p>
+<pre class="literal-block">
+#<htmltable.py>
+
+def HTMLTablegen(table):
+ yield "<table>"
+ for row in table:
+ yield "<tr>"
+ for col in row:
+ yield "<td>%s</td>" % col
+ yield "</tr>"
+ yield "</table>"
+
+def test():
+ return "\n".join(HTMLTablegen([["Row", "City"],
+ [1,'London'], [2, 'Oxford']]))
+
+if __name__ == "__main__": # example
+ print test()
+
+#</htmltable.py>
+</pre>
+<pre class="doctest-block">
+>>> from htmltable import test
+>>> print test()
+<table>
+<tr>
+<td>Row</td>
+<td>City</td>
+</tr>
+<tr>
+<td>1</td>
+<td>London</td>
+</tr>
+<tr>
+<td>2</td>
+<td>Oxford</td>
+</tr>
+</table>
+</pre>
+<div class="section" id="a-simple-recipe-skip-redundant">
+<h2><a name="a-simple-recipe-skip-redundant">A simple recipe: skip redundant</a></h2>
+<p>How to remove duplicates by keeping the order:</p>
+<pre class="literal-block">
+#<skip_redundant.py>
+
+def skip_redundant(iterable, skipset=None):
+ "Redundant items are repeated items or items in the original skipset."
+ if skipset is None: skipset = set()
+ for item in iterable:
+ if item not in skipset:
+ skipset.add(item)
+ yield item
+
+#</skip_redundant.py>
+</pre>
+<pre class="doctest-block">
+>>> from skip_redundant import skip_redundant
+>>> print list(skip_redundant("<hello, world>", skipset=set("<>")))
+['h', 'e', 'l', 'o', ',', ' ', 'w', 'r', 'd']
+</pre>
+</div>
+<div class="section" id="another-real-life-example-working-with-nested-structures">
+<h2><a name="another-real-life-example-working-with-nested-structures">Another real life example: working with nested structures</a></h2>
+<pre class="literal-block">
+#<walk.py>
+
+def walk(iterable, level=0):
+ for obj in iterable:
+ if not hasattr(obj, "__iter__"): # atomic object
+ yield obj, level
+ else: # composed object: iterate again
+ for subobj, lvl in walk(obj, level + 1):
+ yield subobj, lvl
+
+def flatten(iterable):
+ return (obj for obj, level in walk(iterable))
+
+def pprint(iterable):
+ for obj, level in walk(iterable):
+ print " "*level, obj
+
+#</walk.py>
+</pre>
+<pre class="doctest-block">
+>>> from walk import flatten, pprint
+>>> nested_ls = [1,[2,[3,[[[4,5],6]]]],7]
+>>> pprint(nested_ls)
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+>>> pprint(flatten(nested_ls))
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+</pre>
+</div>
+<div class="section" id="another-typical-use-case-for-generators-parsers">
+<h2><a name="another-typical-use-case-for-generators-parsers">Another typical use case for generators: parsers</a></h2>
+<p>A very stripped down parser for nested expressions</p>
+<pre class="literal-block">
+#<sexpr2indent.py>
+"""A simple s-expression formatter."""
+
+import re
+
+def parse(sexpr):
+ position = 0
+ nesting_level = 0
+ paren = re.compile(r"(?P<paren_beg>\()|(?P<paren_end>\))")
+ while True:
+ match = paren.search(sexpr, position)
+ if match:
+ yield nesting_level, sexpr[position: match.start()]
+ if match.lastgroup == "paren_beg":
+ nesting_level += 1
+ elif match.lastgroup == "paren_end":
+ nesting_level -= 1
+ position = match.end()
+ else:
+ break
+
+def sexpr_indent(sexpr):
+ for nesting, text in parse(sexpr.replace("\n", "")):
+ if text.strip(): print " "*nesting, text
+
+#</sexpr2indent.py>
+</pre>
+<pre class="doctest-block">
+>>> from sexpr2indent import sexpr_indent
+>>> sexpr_indent("""\
+... (html (head (title Example)) (body (h1 s-expr formatter example)
+... (a (@ (href http://www.example.com)) A link)))""")
+... #doctest: +NORMALIZE_WHITESPACE
+ html
+ head
+ title Example
+ body
+ h1 s-expr formatter example
+ a
+ @
+ href http://www.example.com
+ A link
+</pre>
+</div>
+<div class="section" id="other-kinds-of-iterables">
+<h2><a name="other-kinds-of-iterables">Other kinds of iterables</a></h2>
+<p>The following class generates iterable which are not iterators:</p>
+<pre class="literal-block">
+#<reiterable.py>
+
+class ReIter(object):
+ "A re-iterable object."
+ def __iter__(self):
+ yield 1
+ yield 2
+ yield 3
+
+#</reiterable.py>
+</pre>
+<pre class="doctest-block">
+>>> from reiterable import ReIter
+>>> rit = ReIter()
+>>> list(rit)
+[1, 2, 3]
+>>> list(rit) # it is reiterable!
+[1, 2, 3]
+</pre>
+</div>
+<div class="section" id="the-itertools-module">
+<h2><a name="the-itertools-module">The itertools module</a></h2>
+<blockquote>
+<ul class="simple">
+<li>count([n]) --> n, n+1, n+2, ...</li>
+<li>cycle(p) --> p0, p1, ... plast, p0, p1, ...</li>
+<li>repeat(elem [,n]) --> elem, elem, elem, ... endlessly or up to n times</li>
+<li>izip(p, q, ...) --> (p[0], q[0]), (p[1], q[1]), ...</li>
+<li>ifilter(pred, seq) --> elements of seq where pred(elem) is True</li>
+<li>ifilterfalse(pred, seq) --> elements of seq where pred(elem) is False</li>
+<li>islice(seq, [start,] stop [, step]) --> elements from seq[start:stop:step]</li>
+<li>imap(fun, p, q, ...) --> fun(p0, q0), fun(p1, q1), ...</li>
+<li>starmap(fun, seq) --> fun(*seq[0]), fun(*seq[1]), ...</li>
+<li>tee(it, n=2) --> (it1, it2 , ... itn) splits one iterator into n</li>
+<li>chain(p, q, ...) --> p0, p1, ... plast, q0, q1, ...</li>
+<li>takewhile(pred, seq) --> seq[0], seq[1], until pred fails</li>
+<li>dropwhile(pred, seq) --> seq[n], seq[n+1], starting when pred fails</li>
+<li>groupby(iterable[, keyfunc]) --> sub-iterators grouped by value of keyfunc(v)</li>
+</ul>
+</blockquote>
+</div>
+<div class="section" id="anytrue">
+<h2><a name="anytrue">anyTrue</a></h2>
+<pre class="doctest-block">
+>>> import itertools
+>>> def anyTrue(predicate, iterable):
+... return True in itertools.imap(predicate, iterable)
+...
+>>> fname = "picture.gif"
+>>> anyTrue(fname.endswith, ".jpg .gif .png".split())
+True
+</pre>
+<p>AnyTrue does <em>short-circuit</em>:</p>
+<pre class="doctest-block">
+>>> def is3(i):
+... print "i=%s" % i
+... return i == 3
+</pre>
+<pre class="doctest-block">
+>>> anyTrue(is3, range(10))
+i=0
+i=1
+i=2
+i=3
+True
+</pre>
+</div>
+<div class="section" id="chop">
+<h2><a name="chop">chop</a></h2>
+<p>You want to chop an iterable in batches of a given size:</p>
+<pre class="doctest-block">
+>>> from chop import chop
+>>> list(chop([1, 2, 3, 4], 2))
+[[1, 2], [3, 4]]
+>>> list(chop([1, 2, 3, 4, 5, 6, 7],3))
+[[1, 2, 3], [4, 5, 6], [7]]
+</pre>
+<p>Here is a possible implementation:</p>
+<pre class="literal-block">
+#<chop.py>
+
+# see also http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/303279
+
+import itertools
+
+def chop(iterable, batchsize):
+ it = iter(iterable)
+ while True:
+ batch = list(itertools.islice(it, batchsize))
+ if batch: yield batch
+ else: break
+
+#</chop.py>
+</pre>
+<p>For people thinking Python is too readable, here is a one-liner:</p>
+<pre class="doctest-block">
+>>> chop = lambda it, n : itertools.izip(*(iter(it),)*n)
+...
+>>> list(chop([1,2,3,4], 2))
+[(1, 2), (3, 4)]
+</pre>
+</div>
+<div class="section" id="tee">
+<h2><a name="tee">tee</a></h2>
+<p>To make copies of iterables; typically used in parsers:</p>
+<pre class="doctest-block">
+>>> from itertools import tee, chain, izip
+>>> chars, prevs = tee("abc")
+>>> prevs = chain([None], prevs)
+>>> for char, prev in izip(chars, prevs):
+... print char, prev
+...
+a None
+b a
+c b
+</pre>
+</div>
+<div class="section" id="grouping-and-sorting">
+<h2><a name="grouping-and-sorting">Grouping and sorting</a></h2>
+<pre class="doctest-block">
+>>> from itertools import groupby
+>>> from operator import itemgetter
+</pre>
+<pre class="doctest-block">
+>>> NAME, AGE = 0, 1
+>>> query_result = ("Smith", 34), ("Donaldson", 34), ("Lee", 22), ("Orr", 22)
+</pre>
+<p>Grouping together people of the same age:</p>
+<pre class="doctest-block">
+>>> for k, g in groupby(query_result, key=itemgetter(AGE)):
+... print k, list(g)
+...
+34 [('Smith', 34), ('Donaldson', 34)]
+22 [('Lee', 22), ('Orr', 22)]
+</pre>
+<p>Sorting by name:</p>
+<pre class="doctest-block">
+>>> for tup in sorted(query_result, key=itemgetter(NAME)):
+... print tup
+('Donaldson', 34)
+('Lee', 22)
+('Orr', 22)
+('Smith', 34)
+</pre>
+</div>
+</div>
+</div>
+</body>
+</html>
diff --git a/pypers/oxford/loops.txt b/pypers/oxford/loops.txt new file mode 100755 index 0000000..cb0bfcb --- /dev/null +++ b/pypers/oxford/loops.txt @@ -0,0 +1,457 @@ +Lecture 1: Loops (i.e. iterators & generators) +============================================== + +Part I: iterators ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Iterators are everywhere +-------------------------------- + +>>> for i in 1, 2, 3: +... print i +1 +2 +3 + +The 'for' loop is using *iterators* internally:: + + it = iter((1,2,3)) + while True: + try: + print it.next() + except StopIteration: + break + +Iterables and iterators +-------------------------- + +*Iterable* = anything you can loop over = any sequence + any object with an __iter__ method; + +Not every sequence has an __iter__ method: + +>>> "hello".__iter__() +Traceback (most recent call last): + ... +AttributeError: 'str' object has no attribute '__iter__' + +*Iterator* = any object with a .next method and an __iter__ method returning self + +Simpler way to get an iterator +-------------------------------------------------------- + +>>> it = iter("hello") +>>> it.next() +'h' +>>> it.next() +'e' +>>> it.next() +'l' +>>> it.next() +'l' +>>> it.next() +'o' +>>> it.next() +Traceback (most recent call last): + ... +StopIteration + +Sentinel syntax iter(callable, sentinel) +-------------------------------------------- + +Example:: + + $ echo -e "value1\nvalue2\nEND\n" > data.txt + $ python -c "print list(iter(file('data.txt').readline, 'END\n'))" + ['value1\n', 'value2\n'] + +Beware of infinite iterators: + +>>> repeat = iter(lambda : "some value", "") +>>> repeat.next() +'some value' + +Second simpler way to get an iterator: generator-expressions +------------------------------------------------------------- + +>>> squares = (i*i for i in range(1,11)) +>>> list(squares) +[1, 4, 9, 16, 25, 36, 49, 64, 81, 100] + +Excessive parenthesis can be skipped, so use + +>>> dict((i, i*i) for i in range(1,11)) +{1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81, 10: 100} + +instead of + +>>> dict([(i, i*i) for i in range(1,11)]) +{1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81, 10: 100} + +(as usual, the most elegant version is the most efficient). + +Iteration caveats +-------------------------- + +>>> ls = [i for i in (1,2,3)] +>>> i +3 + +>>> it = (j for j in (1,2,3)) +>>> j +Traceback (most recent call last): + ... +NameError: name 'j' is not defined + +A subtler example: + +>>> ls = [lambda :i for i in (1,2,3)] +>>> ls[0]() +3 + +instead + +>>> it = (lambda :i for i in (1,2,3)) +>>> it.next()() +1 +>>> it.next()() +2 +>>> it.next()() +3 + +*seems* to be working but it is not really the case: + +>>> it = (lambda :i for i in (1,2,3)) +>>> f1 = it.next() +>>> f2 = it.next() +>>> f3 = it.next() +>>> f1() +3 + +The reason is that Python does LATE binding *always*. The solution is ugly: + +>>> it = list(lambda i=i:i for i in (1,2,3)) +>>> it[0]() +1 +>>> it[1]() +2 +>>> it[2]() +3 + +Part II: generators +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Trivial example: + +>>> def gen123(): # "function" which returns an iterator over the values 1, 2, 3 +... yield 1 +... yield 2 +... yield 3 +... +>>> it = gen123() +>>> it.next() +1 +>>> it.next() +2 +>>> it.next() +3 +>>> it.next() +Traceback (most recent call last): + ... +StopIteration + +Real life example: using generators to generate HTML tables + +:: + + #<htmltable.py> + + def HTMLTablegen(table): + yield "<table>" + for row in table: + yield "<tr>" + for col in row: + yield "<td>%s</td>" % col + yield "</tr>" + yield "</table>" + + def test(): + return "\n".join(HTMLTablegen([["Row", "City"], + [1,'London'], [2, 'Oxford']])) + + if __name__ == "__main__": # example + print test() + + #</htmltable.py> + +>>> from htmltable import test +>>> print test() +<table> +<tr> +<td>Row</td> +<td>City</td> +</tr> +<tr> +<td>1</td> +<td>London</td> +</tr> +<tr> +<td>2</td> +<td>Oxford</td> +</tr> +</table> + +A simple recipe: skip redundant +--------------------------------- + +How to remove duplicates by keeping the order:: + + #<skip_redundant.py> + + def skip_redundant(iterable, skipset=None): + "Redundant items are repeated items or items in the original skipset." + if skipset is None: skipset = set() + for item in iterable: + if item not in skipset: + skipset.add(item) + yield item + + #</skip_redundant.py> + +>>> from skip_redundant import skip_redundant +>>> print list(skip_redundant("<hello, world>", skipset=set("<>"))) +['h', 'e', 'l', 'o', ',', ' ', 'w', 'r', 'd'] + +Another real life example: working with nested structures +---------------------------------------------------------- + +:: + + #<walk.py> + + def walk(iterable, level=0): + for obj in iterable: + if not hasattr(obj, "__iter__"): # atomic object + yield obj, level + else: # composed object: iterate again + for subobj, lvl in walk(obj, level + 1): + yield subobj, lvl + + def flatten(iterable): + return (obj for obj, level in walk(iterable)) + + def pprint(iterable): + for obj, level in walk(iterable): + print " "*level, obj + + #</walk.py> + +>>> from walk import flatten, pprint +>>> nested_ls = [1,[2,[3,[[[4,5],6]]]],7] +>>> pprint(nested_ls) + 1 + 2 + 3 + 4 + 5 + 6 + 7 +>>> pprint(flatten(nested_ls)) + 1 + 2 + 3 + 4 + 5 + 6 + 7 + +Another typical use case for generators: parsers +--------------------------------------------------------- + +A very stripped down parser for nested expressions + +:: + + #<sexpr2indent.py> + """A simple s-expression formatter.""" + + import re + + def parse(sexpr): + position = 0 + nesting_level = 0 + paren = re.compile(r"(?P<paren_beg>\()|(?P<paren_end>\))") + while True: + match = paren.search(sexpr, position) + if match: + yield nesting_level, sexpr[position: match.start()] + if match.lastgroup == "paren_beg": + nesting_level += 1 + elif match.lastgroup == "paren_end": + nesting_level -= 1 + position = match.end() + else: + break + + def sexpr_indent(sexpr): + for nesting, text in parse(sexpr.replace("\n", "")): + if text.strip(): print " "*nesting, text + + #</sexpr2indent.py> + +>>> from sexpr2indent import sexpr_indent +>>> sexpr_indent("""\ +... (html (head (title Example)) (body (h1 s-expr formatter example) +... (a (@ (href http://www.example.com)) A link)))""") +... #doctest: +NORMALIZE_WHITESPACE + html + head + title Example + body + h1 s-expr formatter example + a + @ + href http://www.example.com + A link + + +Other kinds of iterables +------------------------------------------------ + +The following class generates iterable which are not iterators: +:: + + #<reiterable.py> + + class ReIter(object): + "A re-iterable object." + def __iter__(self): + yield 1 + yield 2 + yield 3 + + #</reiterable.py> + +>>> from reiterable import ReIter +>>> rit = ReIter() +>>> list(rit) +[1, 2, 3] +>>> list(rit) # it is reiterable! +[1, 2, 3] + +The itertools module +---------------------------------------------------- + + - count([n]) --> n, n+1, n+2, ... + - cycle(p) --> p0, p1, ... plast, p0, p1, ... + - repeat(elem [,n]) --> elem, elem, elem, ... endlessly or up to n times + - izip(p, q, ...) --> (p[0], q[0]), (p[1], q[1]), ... + - ifilter(pred, seq) --> elements of seq where pred(elem) is True + - ifilterfalse(pred, seq) --> elements of seq where pred(elem) is False + - islice(seq, [start,] stop [, step]) --> elements from seq[start:stop:step] + - imap(fun, p, q, ...) --> fun(p0, q0), fun(p1, q1), ... + - starmap(fun, seq) --> fun(\*seq[0]), fun(\*seq[1]), ... + - tee(it, n=2) --> (it1, it2 , ... itn) splits one iterator into n + - chain(p, q, ...) --> p0, p1, ... plast, q0, q1, ... + - takewhile(pred, seq) --> seq[0], seq[1], until pred fails + - dropwhile(pred, seq) --> seq[n], seq[n+1], starting when pred fails + - groupby(iterable[, keyfunc]) --> sub-iterators grouped by value of keyfunc(v) + +anyTrue +------------------------------ + +>>> import itertools +>>> def anyTrue(predicate, iterable): +... return True in itertools.imap(predicate, iterable) +... +>>> fname = "picture.gif" +>>> anyTrue(fname.endswith, ".jpg .gif .png".split()) +True + +AnyTrue does *short-circuit*: + +>>> def is3(i): +... print "i=%s" % i +... return i == 3 + +>>> anyTrue(is3, range(10)) +i=0 +i=1 +i=2 +i=3 +True + +chop +---------------------- + +You want to chop an iterable in batches of a given size: + +>>> from chop import chop +>>> list(chop([1, 2, 3, 4], 2)) +[[1, 2], [3, 4]] +>>> list(chop([1, 2, 3, 4, 5, 6, 7],3)) +[[1, 2, 3], [4, 5, 6], [7]] + +Here is a possible implementation:: + + #<chop.py> + + # see also http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/303279 + + import itertools + + def chop(iterable, batchsize): + it = iter(iterable) + while True: + batch = list(itertools.islice(it, batchsize)) + if batch: yield batch + else: break + + #</chop.py> + +For people thinking Python is too readable, here is a one-liner: + +>>> chop = lambda it, n : itertools.izip(*(iter(it),)*n) +... +>>> list(chop([1,2,3,4], 2)) +[(1, 2), (3, 4)] + +tee +----------------------- + +To make copies of iterables; typically used in parsers: + +>>> from itertools import tee, chain, izip +>>> chars, prevs = tee("abc") +>>> prevs = chain([None], prevs) +>>> for char, prev in izip(chars, prevs): +... print char, prev +... +a None +b a +c b + +Grouping and sorting +---------------------- + +>>> from itertools import groupby +>>> from operator import itemgetter + +>>> NAME, AGE = 0, 1 +>>> query_result = ("Smith", 34), ("Donaldson", 34), ("Lee", 22), ("Orr", 22) + +Grouping together people of the same age: + +>>> for k, g in groupby(query_result, key=itemgetter(AGE)): +... print k, list(g) +... +34 [('Smith', 34), ('Donaldson', 34)] +22 [('Lee', 22), ('Orr', 22)] + +Sorting by name: + +>>> for tup in sorted(query_result, key=itemgetter(NAME)): +... print tup +('Donaldson', 34) +('Lee', 22) +('Orr', 22) +('Smith', 34) diff --git a/pypers/oxford/magic.html b/pypers/oxford/magic.html new file mode 100755 index 0000000..048c237 --- /dev/null +++ b/pypers/oxford/magic.html @@ -0,0 +1,717 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> +<meta name="generator" content="Docutils 0.3.7: http://docutils.sourceforge.net/" /> +<title>Lecture 3: Magic (i.e. decorators and metaclasses)</title> +<link rel="stylesheet" href="default.css" type="text/css" /> +</head> +<body> +<div class="document" id="lecture-3-magic-i-e-decorators-and-metaclasses"> +<h1 class="title">Lecture 3: Magic (i.e. decorators and metaclasses)</h1> +<div class="section" id="part-i-decorators"> +<h1><a name="part-i-decorators">Part I: decorators</a></h1> +<p>Decorators are just sugar: their functionality was already in the language</p> +<pre class="doctest-block"> +>>> def s(): pass +>>> s = staticmethod(s) +</pre> +<pre class="doctest-block"> +>>> @staticmethod +... def s(): pass +... +</pre> +<p>However sugar <em>does</em> matter.</p> +<div class="section" id="a-typical-decorator-traced"> +<h2><a name="a-typical-decorator-traced">A typical decorator: traced</a></h2> +<pre class="literal-block"> +#<traced.py> + +def traced(func): + def tracedfunc(*args, **kw): + print "calling %s.%s" % (func.__module__, func.__name__) + return func(*args, **kw) + tracedfunc.__name__ = func.__name__ + return tracedfunc + +@traced +def f(): pass + +#</traced.py> +</pre> +<pre class="doctest-block"> +>>> from traced import f +>>> f() +calling traced.f +</pre> +</div> +<div class="section" id="a-decorator-factory-timed"> +<h2><a name="a-decorator-factory-timed">A decorator factory: Timed</a></h2> +<pre class="literal-block"> +#<timed.py> + +import sys, time + +class Timed(object): + """Decorator factory: each decorator object wraps a function and + executes it many times (default 100 times). + The average time spent in one iteration, expressed in milliseconds, + is stored in the attributes wrappedfunc.time and wrappedfunc.clocktime, + and displayed into a log file which defaults to stdout. + """ + def __init__(self, repeat=100, logfile=sys.stdout): + self.repeat = repeat + self.logfile = logfile + def __call__(self, func): + def wrappedfunc(*args, **kw): + fullname = "%s.%s ..." % (func.__module__, func.func_name) + print >> self.logfile, 'Executing %s' % fullname.ljust(30), + time1 = time.time() + clocktime1 = time.clock() + for i in xrange(self.repeat): + res = func(*args,**kw) # executes func self.repeat times + time2 = time.time() + clocktime2 = time.clock() + wrappedfunc.time = 1000*(time2-time1)/self.repeat + wrappedfunc.clocktime = 1000*(clocktime2 - clocktime1)/self.repeat + print >> self.logfile, \ + 'Real time: %s ms;' % self.rounding(wrappedfunc.time), + print >> self.logfile, \ + 'Clock time: %s ms' % self.rounding(wrappedfunc.clocktime) + return res + wrappedfunc.func_name = func.func_name + wrappedfunc.__module__ = func.__module__ + return wrappedfunc + @staticmethod + def rounding(float_): + "Three digits rounding for small numbers, 1 digit rounding otherwise." + if float_ < 10.: + return "%5.3f" % float_ + else: + return "%5.1f" % float_ + +#</timed.py> +</pre> +<pre class="doctest-block"> +>>> from timed import Timed +>>> from random import sample +>>> example_ls = sample(xrange(1000000), 1000) +>>> @Timed() +... def list_sort(ls): +... ls.sort() +... +>>> list_sort(example_ls) #doctest: +ELLIPSIS +Executing __main__.list_sort ... Real time: 0... ms; Clock time: 0... ms +</pre> +</div> +<div class="section" id="towards-decorator-patterns"> +<h2><a name="towards-decorator-patterns">Towards decorator patterns</a></h2> +<pre class="literal-block"> +#<traced_function2.py> + +from decorators import decorator + +def trace(f, *args, **kw): + print "calling %s with args %s, %s" % (f.func_name, args, kw) + return f(*args, **kw) + +traced_function = decorator(trace) + +@traced_function +def f1(x): + pass + +@traced_function +def f2(x, y): + pass + +#</traced_function2.py> +</pre> +<pre class="doctest-block"> +>>> from traced_function2 import traced_function, f1, f2 +>>> f1(1) +calling f1 with args (1,), {} +>>> f2(1,2) +calling f2 with args (1, 2), {} +</pre> +<p>works with pydoc:</p> +<pre class="literal-block"> +$ pydoc2.4 traced_function2.f2 +Help on function f1 in traced_function2: + +traced_function2.f1 = f1(x) + +$ pydoc2.4 traced_function2.f2 +Help on function f2 in traced_function2: + +traced_function2.f2 = f2(x, y) +</pre> +<p>Here is the source code:</p> +<pre class="literal-block"> +#<decorators.py> + +import inspect + +def _signature_gen(func, rm_defaults=False): + argnames, varargs, varkwargs, defaults = inspect.getargspec(func) + argdefs = defaults or () + n_args = func.func_code.co_argcount + n_default_args = len(argdefs) + n_non_default_args = n_args - n_default_args + non_default_names = argnames[:n_non_default_args] + default_names = argnames[n_non_default_args:] + for name in non_default_names: + yield "%s" % name + for i, name in enumerate(default_names): + if rm_defaults: + yield name + else: + yield "%s = arg[%s]" % (name, i) + if varargs: + yield "*%s" % varargs + if varkwargs: + yield "**%s" % varkwargs + +def _decorate(func, caller): + signature = ", ".join(_signature_gen(func)) + variables = ", ".join(_signature_gen(func, rm_defaults=True)) + lambda_src = "lambda %s: call(func, %s)" % (signature, variables) + evaldict = dict(func=func, call=caller, arg=func.func_defaults or ()) + dec_func = eval(lambda_src, evaldict) + dec_func.__name__ = func.__name__ + dec_func.__doc__ = func.__doc__ + dec_func.__dict__ = func.__dict__ # copy if you want to avoid sharing + return dec_func + +class decorator(object): + """General purpose decorator factory, taking a caller function as + input. A caller function is any function like this: + + def caller(func, *args, **kw): + # do something + return func(*args, **kw) + """ + def __init__(self, caller): + self.caller = caller + def __call__(self, func): + return _decorate(func, self.caller) + +#</decorators.py> +</pre> +<p>The possibilities of this pattern are endless:</p> +<pre class="literal-block"> +#<deferred.py> + +import threading +from decorators import decorator + +def deferred(nsec): + def inner_deferred(func, *args, **kw): + return threading.Timer(nsec, func, args, kw).start() + return decorator(inner_deferred) + +@deferred(2) +def hello(): + print "hello" + +#<deferred.py> +</pre> +<p>Show an example of an experimental decorator based web framework +(doctester_frontend).</p> +</div> +</div> +<div class="section" id="part-ii-metaclasses"> +<h1><a name="part-ii-metaclasses">Part II: metaclasses</a></h1> +<p>Metaclasses are there! Consider this example from a recent post on c.l.py:</p> +<pre class="literal-block"> +#<BaseClass.py> + +class BaseClass(object): + "Do something" + +#</BaseClass.py> +</pre> +<pre class="doctest-block"> +>>> import BaseClass # instead of 'from BaseClass import BaseClass' +>>> class C(BaseClass): pass +... +Traceback (most recent call last): + ... +TypeError: Error when calling the metaclass bases + module.__init__() takes at most 2 arguments (3 given) +</pre> +<p>The reason for the error is that class <tt class="docutils literal"><span class="pre">C(BaseClass):</span> <span class="pre">pass</span></tt> is +actually calling the <tt class="docutils literal"><span class="pre">type</span></tt> metaclass with three arguments:</p> +<pre class="literal-block"> +C = type("C", (BaseClass,), {}) +</pre> +<p><tt class="docutils literal"><span class="pre">type.__new__</span></tt> tries to use <tt class="docutils literal"><span class="pre">type(BaseClass)</span></tt> as metaclass, +but since BaseClass here is a module, and <tt class="docutils literal"><span class="pre">ModuleType</span></tt> is not +a metaclass, it cannot work. The error message reflects a conflict with +the signature of ModuleType which requires two parameters and not three.</p> +<p>So even if you don't use them, you may want to know they exist.</p> +<div class="section" id="rejuvenating-old-style-classes"> +<h2><a name="rejuvenating-old-style-classes">Rejuvenating old-style classes</a></h2> +<pre class="doctest-block"> +>>> class Old: pass +>>> print type(Old) +<type 'classobj'> +</pre> +<pre class="doctest-block"> +>>> __metaclass__ = type # to rejuvenate class +>>> class NotOld: pass +... +>>> print NotOld.__class__ +<type 'type'> +</pre> +</div> +<div class="section" id="a-typical-metaclass-example-metatracer"> +<h2><a name="a-typical-metaclass-example-metatracer">A typical metaclass example: MetaTracer</a></h2> +<pre class="literal-block"> +#<metatracer.py> +import inspect + +def trace(meth, cls): + def traced(*args, **kw): + modname = meth.__module__ or cls.__module__ + print "calling %s.%s.%s" % (modname, cls.__name__, meth.__name__) + return meth(*args, **kw) + traced.__name__ = meth.__name__ + return traced + +class MetaTracer(type): + def __init__(cls, name, bases, dic): + super(MetaTracer, cls).__init__(name, bases, dic) + for k, v in dic.iteritems(): + if inspect.isfunction(v): + setattr(cls, k, trace(v, cls)) + +#</metatracer.py> +</pre> +<p>Usage: exploring classes in the standard library</p> +<pre class="literal-block"> +#<dictmixin.py> + +from metatracer import MetaTracer +from UserDict import DictMixin + +class TracedDM(DictMixin, object): + __metaclass__ = MetaTracer + def __getitem__(self, item): + return item + def keys(self): + return [1,2,3] + +#</dictmixin.py> +</pre> +<pre class="doctest-block"> +>>> from dictmixin import TracedDM +>>> print TracedDM() +calling dictmixin.TracedDM.keys +calling dictmixin.TracedDM.__getitem__ +calling dictmixin.TracedDM.__getitem__ +calling dictmixin.TracedDM.__getitem__ +{1: 1, 2: 2, 3: 3} +</pre> +</div> +<div class="section" id="real-life-example-check-overriding"> +<h2><a name="real-life-example-check-overriding">Real life example: check overriding</a></h2> +<pre class="literal-block"> +#<check_overriding.py> + +class Base(object): + a = 0 + +class CheckOverriding(type): + "Prints a message if we are overriding a name." + def __new__(mcl, name, bases, dic): + for name, val in dic.iteritems(): + if name.startswith("__") and name.endswith("__"): + continue # ignore special names + a_base_has_name = True in (hasattr(base, name) for base in bases) + if a_base_has_name: + print "AlreadyDefinedNameWarning: " + name + return super(CheckOverriding, mcl).__new__(mcl, name, bases, dic) + +class MyClass(Base): + __metaclass__ = CheckOverriding + a = 1 + +class ChildClass(MyClass): + a = 2 +</pre> +<p>#</check_overriding.py></p> +<pre class="doctest-block"> +>>> import check_overriding +AlreadyDefinedNameWarning: a +AlreadyDefinedNameWarning: a +</pre> +</div> +<div class="section" id="logfile"> +<h2><a name="logfile">LogFile</a></h2> +<pre class="literal-block"> +#<logfile.py> + +import subprocess + +def memoize(func): + memoize_dic = {} + def wrapped_func(*args): + if args in memoize_dic: + return memoize_dic[args] + else: + result = func(*args) + memoize_dic[args] = result + return result + wrapped_func.__name__ = func.__name__ + wrapped_func.__doc__ = func.__doc__ + wrapped_func.__dict__ = func.__dict__ + return wrapped_func + +class Memoize(type): # Singleton is a special case of Memoize + @memoize + def __call__(cls, *args): + return super(Memoize, cls).__call__(*args) + +class LogFile(file): + """Open a file for append.""" + __metaclass__ = Memoize + def __init__(self, name = "/tmp/err.log"): + self.viewer_cmd = 'xterm -e less'.split() + super(LogFile, self).__init__(name, "a") + + def display(self, *ls): + "Use 'less' to display the log file in a separate xterm." + print >> self, "\n".join(map(str, ls)); self.flush() + subprocess.call(self.viewer_cmd + [self.name]) + + def reset(self): + "Erase the log file." + print >> file(self.name, "w") + +if __name__ == "__main__": # test + print >> LogFile(), "hello" + print >> LogFile(), "world" + LogFile().display() + +#</logfile.py> + +$ python logfile.py +</pre> +</div> +<div class="section" id="cooperative-hierarchies"> +<h2><a name="cooperative-hierarchies">Cooperative hierarchies</a></h2> +<pre class="literal-block"> +#<cooperative_init.py> + +"""Given a hierarchy, makes __init__ cooperative. +The only change needed is to add a line + + __metaclass__ = CooperativeInit + +to the base class of your hierarchy.""" + +from decorators import decorator + +def make_cooperative_init(cls, name, bases, dic): + + def call_cooperatively(__init__, self, *args, **kw): + super(cls, self).__init__(*args, **kw) + __init__(self, *args, **kw) + + __init__ = cls.__dict__.get("__init__") + if __init__: + cls.__init__ = decorator(call_cooperatively)(__init__) + +class CooperativeInit(type): + __init__ = make_cooperative_init + +class Base: + __metaclass__ = CooperativeInit + def __init__(self): + print "B.__init__" + +class C1(Base): + def __init__(self): + print "C1.__init__" + +class C2(Base): + def __init__(self): + print "C2.__init__" + +class D(C1, C2): + def __init__(self): + print "D.__init__" + +#</cooperative_init.py> +</pre> +<pre class="doctest-block"> +>>> from cooperative_init import D +>>> d = D() +B.__init__ +C2.__init__ +C1.__init__ +D.__init__ +</pre> +</div> +<div class="section" id="metaclass-enhanced-modules"> +<h2><a name="metaclass-enhanced-modules">Metaclass-enhanced modules</a></h2> +<pre class="literal-block"> +#<import_with_metaclass.py> +""" +``import_with_metaclass(metaclass, modulepath)`` generates +a new module from and old module, by enhancing all of its classes. +This is not perfect, but it should give you a start.""" + +import os, sys, inspect, types + +def import_with_metaclass(metaclass, modulepath): + modname = os.path.basename(modulepath)[:-3] # simplistic + mod = types.ModuleType(modname) + locs = dict( + __module__ = modname, + __file__ = modulepath, + __metaclass__ = metaclass, + object = metaclass("object", (), {})) + execfile(modulepath, locs) + for k, v in locs.iteritems(): + setattr(mod, k, v) + return mod +</pre> +<p>#</import_with_metaclass.py></p> +<pre class="doctest-block"> +>>> from import_with_metaclass import import_with_metaclass +>>> from tracer import MetaTracer +>>> traced_optparse = import_with_metaclass(MetaTracer, +... "/usr/lib/python2.4/optparse.py") +>>> op = traced_optparse.OptionParser() +calling __main__.OptionParser.__init__ +calling __main__.OptionContainer.__init__ +calling __main__.OptionParser._create_option_list +calling __main__.OptionContainer._create_option_mappings +calling __main__.OptionContainer.set_conflict_handler +calling __main__.OptionContainer.set_description +calling __main__.OptionParser.set_usage +calling __main__.IndentedHelpFormatter.__init__ +calling __main__.HelpFormatter.__init__ +calling __main__.HelpFormatter.set_parser +calling __main__.OptionParser._populate_option_list +calling __main__.OptionParser._add_help_option +calling __main__.OptionContainer.add_option +calling __main__.Option.__init__ +calling __main__.Option._check_opt_strings +calling __main__.Option._set_opt_strings +calling __main__.Option._set_attrs +calling __main__.OptionContainer._check_conflict +calling __main__.OptionParser._init_parsing_state +</pre> +</div> +<div class="section" id="magic-properties"> +<h2><a name="magic-properties">Magic properties</a></h2> +<pre class="literal-block"> +#<magicprop.py> + +class MagicProperties(type): + def __init__(cls, name, bases, dic): + prop_names = set(name[3:] for name in dic + if name.startswith("get") + or name.startswith("set")) + for name in prop_names: + getter = getattr(cls, "get" + name, None) + setter = getattr(cls, "set" + name, None) + setattr(cls, name, property(getter, setter)) + +class Base(object): + __metaclass__ = MagicProperties + def getx(self): + return self._x + def setx(self, value): + self._x = value + +class Child(Base): + def getx(self): + print "getting x" + return super(Child, self).getx() + def setx(self, value): + print "setting x" + super(Child, self).setx(value) + +#</magicprop.py> +</pre> +<pre class="doctest-block"> +>>> from magicprop import Child +>>> c = Child() +>>> c.x = 1 +setting x +>>> print c.x +getting x +1 +</pre> +</div> +<div class="section" id="hack-evil-properties"> +<h2><a name="hack-evil-properties">Hack: evil properties</a></h2> +<pre class="literal-block"> +#<evilprop.py> + +def convert2property(name, bases, d): + return property(d.get('get'), d.get('set'), + d.get('del'),d.get('__doc__')) + +class C(object): + class x: + """An evil test property""" + __metaclass__ = convert2property + def get(self): + print 'Getting %s' % self._x + return self._x + def set(self, value): + self._x = value + print 'Setting to', value + +#</evilprop.py> +</pre> +<pre class="doctest-block"> +>>> from evilprop import C +>>> c = C() +>>> c.x = 5 +Setting to 5 +>>> c.x +Getting 5 +5 +>>> print C.x.__doc__ +An evil test property +</pre> +</div> +<div class="section" id="why-i-suggest-not-to-use-metaclasses-in-production-code"> +<h2><a name="why-i-suggest-not-to-use-metaclasses-in-production-code">Why I suggest <em>not</em> to use metaclasses in production code</a></h2> +<blockquote> +<ul class="simple"> +<li>there are very few good use case for metaclasses in production code +(i.e. 99% of time you don't need them)</li> +<li>they put a cognitive burden on the developer;</li> +<li>a design without metaclasses is less magic and likely more robust;</li> +<li>a design with metaclasses makes it difficult to use other metaclasses +for debugging.</li> +</ul> +</blockquote> +<p>As far as I know, string.Template is the only metaclass-enhanced class +in the standard library; the metaclass is used to give the possibility to +change the defaults:</p> +<pre class="literal-block"> +delimiter = '$' +idpattern = r'[_a-z][_a-z0-9]*' +</pre> +<p>in subclasses of Template.</p> +<pre class="doctest-block"> +>>> from string import Template +>>> from tracer import MetaTracer +>>> class TracedTemplate(Template): +... __metaclass__ = MetaTracer +... +Traceback (most recent call last): + ... +TypeError: Error when calling the metaclass bases + metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases +</pre> +<p>Solution: use a consistent metaclass</p> +<pre class="doctest-block"> +>>> class GoodMeta(MetaTracer, type(Template)): pass +... +>>> class TracedTemplate(Template): +... __metaclass__ = GoodMeta +</pre> +</div> +<div class="section" id="is-there-an-automatic-way-of-solving-the-conflict"> +<h2><a name="is-there-an-automatic-way-of-solving-the-conflict">Is there an automatic way of solving the conflict?</a></h2> +<p>Yes, but you really need to be a metaclass wizard.</p> +<p><a class="reference" href="http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/204197">http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/204197</a></p> +<pre class="doctest-block"> +>>> from noconflict import classmaker +>>> class TracedTemplate(Template): +... __metaclass__ = classmaker((MetaTracer,)) +>>> print type(TracedTemplate) +<class 'noconflict._MetaTracer_TemplateMetaclass'> +</pre> +<pre class="literal-block"> +#<noconflict.py> + +import inspect, types, __builtin__ +from skip_redundant import skip_redundant + +memoized_metaclasses_map = {} + +# utility function +def remove_redundant(metaclasses): + skipset = set([types.ClassType]) + for meta in metaclasses: # determines the metaclasses to be skipped + skipset.update(inspect.getmro(meta)[1:]) + return tuple(skip_redundant(metaclasses, skipset)) + +################################################################## +## now the core of the module: two mutually recursive functions ## +################################################################## + +def get_noconflict_metaclass(bases, left_metas, right_metas): + """Not intended to be used outside of this module, unless you know + what you are doing.""" + # make tuple of needed metaclasses in specified priority order + metas = left_metas + tuple(map(type, bases)) + right_metas + needed_metas = remove_redundant(metas) + + # return existing confict-solving meta, if any + if needed_metas in memoized_metaclasses_map: + return memoized_metaclasses_map[needed_metas] + # nope: compute, memoize and return needed conflict-solving meta + elif not needed_metas: # wee, a trivial case, happy us + meta = type + elif len(needed_metas) == 1: # another trivial case + meta = needed_metas[0] + # check for recursion, can happen i.e. for Zope ExtensionClasses + elif needed_metas == bases: + raise TypeError("Incompatible root metatypes", needed_metas) + else: # gotta work ... + metaname = '_' + ''.join([m.__name__ for m in needed_metas]) + meta = classmaker()(metaname, needed_metas, {}) + memoized_metaclasses_map[needed_metas] = meta + return meta + +def classmaker(left_metas=(), right_metas=()): + def make_class(name, bases, adict): + metaclass = get_noconflict_metaclass(bases, left_metas, right_metas) + return metaclass(name, bases, adict) + return make_class + +################################################################# +## and now a conflict-safe replacement for 'type' ## +################################################################# + +__type__=__builtin__.type # the aboriginal 'type' +# left available in case you decide to rebind __builtin__.type + +class safetype(__type__): + # this is REALLY DEEP MAGIC + """Overrides the ``__new__`` method of the ``type`` metaclass, making the + generation of classes conflict-proof.""" + def __new__(mcl, *args): + nargs = len(args) + if nargs == 1: # works as __builtin__.type + return __type__(args[0]) + elif nargs == 3: # creates the class using the appropriate metaclass + n, b, d = args # name, bases and dictionary + meta = get_noconflict_metaclass(b, (mcl,), ()) + if meta is mcl: # meta is trivial, dispatch to the default __new__ + return super(safetype, mcl).__new__(mcl, n, b, d) + else: # non-trivial metaclass, dispatch to the right __new__ + # (it will take a second round) # print mcl, meta + return super(mcl, meta).__new__(meta, n, b, d) + else: + raise TypeError('%s() takes 1 or 3 arguments' % mcl.__name__) + +#</noconflict.py> +</pre> +</div> +</div> +</div> +</body> +</html> diff --git a/pypers/oxford/magic.txt b/pypers/oxford/magic.txt new file mode 100755 index 0000000..9437ac5 --- /dev/null +++ b/pypers/oxford/magic.txt @@ -0,0 +1,783 @@ +Lecture 3: Magic (i.e. decorators and metaclasses) +================================================================ + +Part I: decorators ++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Decorators are just sugar: their functionality was already in the language + +>>> def s(): pass +>>> s = staticmethod(s) + +>>> @staticmethod +... def s(): pass +... + +However sugar *does* matter. + +A typical decorator: traced +----------------------------- +:: + + #<traced.py> + + def traced(func): + def tracedfunc(*args, **kw): + print "calling %s.%s" % (func.__module__, func.__name__) + return func(*args, **kw) + tracedfunc.__name__ = func.__name__ + return tracedfunc + + @traced + def f(): pass + + #</traced.py> + +>>> from traced import f +>>> f() +calling traced.f + +A decorator factory: Timed +------------------------------------------ + +:: + + #<timed.py> + + import sys, time + + class Timed(object): + """Decorator factory: each decorator object wraps a function and + executes it many times (default 100 times). + The average time spent in one iteration, expressed in milliseconds, + is stored in the attributes wrappedfunc.time and wrappedfunc.clocktime, + and displayed into a log file which defaults to stdout. + """ + def __init__(self, repeat=100, logfile=sys.stdout): + self.repeat = repeat + self.logfile = logfile + def __call__(self, func): + def wrappedfunc(*args, **kw): + fullname = "%s.%s ..." % (func.__module__, func.func_name) + print >> self.logfile, 'Executing %s' % fullname.ljust(30), + time1 = time.time() + clocktime1 = time.clock() + for i in xrange(self.repeat): + res = func(*args,**kw) # executes func self.repeat times + time2 = time.time() + clocktime2 = time.clock() + wrappedfunc.time = 1000*(time2-time1)/self.repeat + wrappedfunc.clocktime = 1000*(clocktime2 - clocktime1)/self.repeat + print >> self.logfile, \ + 'Real time: %s ms;' % self.rounding(wrappedfunc.time), + print >> self.logfile, \ + 'Clock time: %s ms' % self.rounding(wrappedfunc.clocktime) + return res + wrappedfunc.func_name = func.func_name + wrappedfunc.__module__ = func.__module__ + return wrappedfunc + @staticmethod + def rounding(float_): + "Three digits rounding for small numbers, 1 digit rounding otherwise." + if float_ < 10.: + return "%5.3f" % float_ + else: + return "%5.1f" % float_ + + #</timed.py> + +>>> from timed import Timed +>>> from random import sample +>>> example_ls = sample(xrange(1000000), 1000) +>>> @Timed() +... def list_sort(ls): +... ls.sort() +... +>>> list_sort(example_ls) #doctest: +ELLIPSIS +Executing __main__.list_sort ... Real time: 0... ms; Clock time: 0... ms + + +A powerful decorator pattern +-------------------------------- +:: + + #<traced_function2.py> + + from decorators import decorator + + def trace(f, *args, **kw): + print "calling %s with args %s, %s" % (f.func_name, args, kw) + return f(*args, **kw) + + traced_function = decorator(trace) + + @traced_function + def f1(x): + pass + + @traced_function + def f2(x, y): + pass + + #</traced_function2.py> + +>>> from traced_function2 import traced_function, f1, f2 +>>> f1(1) +calling f1 with args (1,), {} +>>> f2(1,2) +calling f2 with args (1, 2), {} + +works with pydoc:: + + $ pydoc2.4 traced_function2.f2 + Help on function f1 in traced_function2: + + traced_function2.f1 = f1(x) + + $ pydoc2.4 traced_function2.f2 + Help on function f2 in traced_function2: + + traced_function2.f2 = f2(x, y) + +Here is the source code:: + + #<decorators.py> + + import inspect, itertools + + def getinfo(func): + """Return an info dictionary containing: + - name (the name of the function : str) + - argnames (the names of the arguments : list) + - defarg (the values of the default arguments : list) + - fullsign (the full signature : str) + - shortsign (the short signature : str) + - arg0 ... argn (shortcuts for the names of the arguments) + + >> def f(self, x=1, y=2, *args, **kw): pass + + >> info = getinfo(f) + + >> info["name"] + 'f' + >> info["argnames"] + ['self', 'x', 'y', 'args', 'kw'] + + >> info["defarg"] + (1, 2) + + >> info["shortsign"] + 'self, x, y, *args, **kw' + + >> info["fullsign"] + 'self, x=defarg[0], y=defarg[1], *args, **kw' + + >> info["arg0"], info["arg1"], info["arg2"], info["arg3"], info["arg4"] + ('self', 'x', 'y', 'args', 'kw') + """ + assert inspect.ismethod(func) or inspect.isfunction(func) + regargs, varargs, varkwargs, defaults = inspect.getargspec(func) + argnames = list(regargs) + if varargs: argnames.append(varargs) + if varkwargs: argnames.append(varkwargs) + counter = itertools.count() + fullsign = inspect.formatargspec( + regargs, varargs, varkwargs, defaults, + formatvalue=lambda value: "=defarg[%i]" % counter.next())[1:-1] + shortsign = inspect.formatargspec( + regargs, varargs, varkwargs, defaults, + formatvalue=lambda value: "")[1:-1] + dic = dict(("arg%s" % n, name) for n, name in enumerate(argnames)) + dic.update(name=func.__name__, argnames=argnames, shortsign=shortsign, + fullsign = fullsign, defarg = func.func_defaults or ()) + return dic + + def _contains_reserved_names(dic): # helper + return "_call_" in dic or "_func_" in dic + + def _decorate(func, caller): + """Takes a function and a caller and returns the function + decorated with that caller. The decorated function is obtained + by evaluating a lambda function with the correct signature. + """ + infodict = getinfo(func) + assert not _contains_reserved_names(infodict["argnames"]), \ + "You cannot use _call_ or _func_ as argument names!" + execdict=dict(_func_=func, _call_=caller, defarg=func.func_defaults or ()) + if func.__name__ == "<lambda>": + lambda_src = "lambda %(fullsign)s: _call_(_func_, %(shortsign)s)" \ + % infodict + dec_func = eval(lambda_src, execdict) + else: + func_src = """def %(name)s(%(fullsign)s): + return _call_(_func_, %(shortsign)s)""" % infodict + exec func_src in execdict + dec_func = execdict[func.__name__] + dec_func.__doc__ = func.__doc__ + dec_func.__dict__ = func.__dict__ + return dec_func + + class decorator(object): + """General purpose decorator factory: takes a caller function as + input and returns a decorator. A caller function is any function like this:: + + def caller(func, *args, **kw): + # do something + return func(*args, **kw) + + Here is an example of usage: + + >> @decorator + .. def chatty(f, *args, **kw): + .. print "Calling %r" % f.__name__ + .. return f(*args, **kw) + + >> @chatty + .. def f(): pass + .. + >> f() + Calling 'f' + """ + def __init__(self, caller): + self.caller = caller + def __call__(self, func): + return _decorate(func, self.caller) + + + #</decorators.py> + +The possibilities of this pattern are endless. + +A deferred decorator +----------------------- + +You want to execute a procedure only after a certain time delay (for instance +for use within an asyncronous Web framework):: + + + #<deferred.py> + "Deferring the execution of a procedure (function returning None)" + + import threading + from decorators import decorator + + def deferred(nsec): + def call_later(func, *args, **kw): + return threading.Timer(nsec, func, args, kw).start() + return decorator(call_later) + + @deferred(2) + def hello(): + print "hello" + + if __name__ == "__main__": + hello() + print "Calling hello() ..." + + + #</deferred.py> + + $ python deferred.py + +Show an example of an experimental decorator based web framework +(doctester_frontend). + +Part II: metaclasses +++++++++++++++++++++++++++++++++++++++++++++++++++ + +Metaclasses are there! Consider this example from a recent post on c.l.py:: + + #<BaseClass.py> + + class BaseClass(object): + "Do something" + + #</BaseClass.py> + +>>> import BaseClass # instead of 'from BaseClass import BaseClass' +>>> class C(BaseClass): pass +... +Traceback (most recent call last): + ... +TypeError: Error when calling the metaclass bases + module.__init__() takes at most 2 arguments (3 given) + +The reason for the error is that class ``C(BaseClass): pass`` is +actually calling the ``type`` metaclass with three arguments:: + + C = type("C", (BaseClass,), {}) + +``type.__new__`` tries to use ``type(BaseClass)`` as metaclass, +but since BaseClass here is a module, and ``ModuleType`` is not +a metaclass, it cannot work. The error message reflects a conflict with +the signature of ModuleType which requires two parameters and not three. + +So even if you don't use them, you may want to know they exist. + +Rejuvenating old-style classes +-------------------------------------------- + +>>> class Old: pass +>>> print type(Old) +<type 'classobj'> + +>>> __metaclass__ = type # to rejuvenate class +>>> class NotOld: pass +... +>>> print NotOld.__class__ +<type 'type'> + +A typical metaclass example: MetaTracer +---------------------------------------- + +:: + + #<metatracer.py> + + import inspect + from decorators import decorator + + @decorator + def traced(meth, *args, **kw): + cls = meth.__cls__ + modname = meth.__module__ or cls.__module__ + print "calling %s.%s.%s" % (modname, cls.__name__, meth.__name__) + return meth(*args, **kw) + + class MetaTracer(type): + def __init__(cls, name, bases, dic): + super(MetaTracer, cls).__init__(name, bases, dic) + for k, v in dic.iteritems(): + if inspect.isfunction(v): + v.__cls__ = cls # so we know in which class v was defined + setattr(cls, k, traced(v)) + + #</metatracer.py> + +Usage: exploring classes in the standard library + +:: + + #<dictmixin.py> + + from metatracer import MetaTracer + from UserDict import DictMixin + + class TracedDM(DictMixin, object): + __metaclass__ = MetaTracer + def __getitem__(self, item): + return item + def keys(self): + return [1,2,3] + + #</dictmixin.py> + +>>> from dictmixin import TracedDM +>>> print TracedDM() +calling dictmixin.TracedDM.keys +calling dictmixin.TracedDM.__getitem__ +calling dictmixin.TracedDM.__getitem__ +calling dictmixin.TracedDM.__getitem__ +{1: 1, 2: 2, 3: 3} + +Real life example: check overriding +------------------------------------- +:: + + #<check_overriding.py> + + class Base(object): + a = 0 + + class CheckOverriding(type): + "Prints a message if we are overriding a name." + def __new__(mcl, name, bases, dic): + for name, val in dic.iteritems(): + if name.startswith("__") and name.endswith("__"): + continue # ignore special names + a_base_has_name = True in (hasattr(base, name) for base in bases) + if a_base_has_name: + print "AlreadyDefinedNameWarning: " + name + return super(CheckOverriding, mcl).__new__(mcl, name, bases, dic) + + class MyClass(Base): + __metaclass__ = CheckOverriding + a = 1 + + class ChildClass(MyClass): + a = 2 + +#</check_overriding.py> + +>>> import check_overriding +AlreadyDefinedNameWarning: a +AlreadyDefinedNameWarning: a + +LogFile +--------------------------------------------- +:: + + #<logfile.py> + + import subprocess + + def memoize(func): + memoize_dic = {} + def wrapped_func(*args): + if args in memoize_dic: + return memoize_dic[args] + else: + result = func(*args) + memoize_dic[args] = result + return result + wrapped_func.__name__ = func.__name__ + wrapped_func.__doc__ = func.__doc__ + wrapped_func.__dict__ = func.__dict__ + return wrapped_func + + class Memoize(type): # Singleton is a special case of Memoize + @memoize + def __call__(cls, *args): + return super(Memoize, cls).__call__(*args) + + class LogFile(file): + """Open a file for append.""" + __metaclass__ = Memoize + def __init__(self, name = "/tmp/err.log"): + self.viewer_cmd = 'xterm -e less'.split() + super(LogFile, self).__init__(name, "a") + + def display(self, *ls): + "Use 'less' to display the log file in a separate xterm." + print >> self, "\n".join(map(str, ls)); self.flush() + subprocess.call(self.viewer_cmd + [self.name]) + + def reset(self): + "Erase the log file." + print >> file(self.name, "w") + + if __name__ == "__main__": # test + print >> LogFile(), "hello" + print >> LogFile(), "world" + LogFile().display() + + #</logfile.py> + + $ python logfile.py + +Cooperative hierarchies +------------------------------ + +:: + + #<cooperative_init.py> + + """Given a hierarchy, makes __init__ cooperative. + The only change needed is to add a line + + __metaclass__ = CooperativeInit + + to the base class of your hierarchy.""" + + from decorators import decorator + + class CooperativeInit(type): + def __init__(cls, name, bases, dic): + + @decorator + def make_cooperative(__init__, self, *args, **kw): + super(cls, self).__init__(*args, **kw) + __init__(self, *args, **kw) + + __init__ = dic.get("__init__") + if __init__: + cls.__init__ = make_cooperative(__init__) + + class Base: + __metaclass__ = CooperativeInit + def __init__(self): + print "B.__init__" + + class C1(Base): + def __init__(self): + print "C1.__init__" + + class C2(Base): + def __init__(self): + print "C2.__init__" + + class D(C1, C2): + def __init__(self): + print "D.__init__" + + #</cooperative_init.py> + +>>> from cooperative_init import D +>>> d = D() +B.__init__ +C2.__init__ +C1.__init__ +D.__init__ + +Metaclass-enhanced modules +---------------------------------------------------------------- + +:: + + #<import_with_metaclass.py> + """ + ``import_with_metaclass(metaclass, modulepath)`` generates + a new module from and old module, by enhancing all of its classes. + This is not perfect, but it should give you a start.""" + + import os, sys, inspect, types + + def import_with_metaclass(metaclass, modulepath): + modname = os.path.basename(modulepath)[:-3] # simplistic + mod = types.ModuleType(modname) + locs = dict( + __module__ = modname, + __metaclass__ = metaclass, + object = metaclass("object", (), {})) + execfile(modulepath, locs) + for k, v in locs.iteritems(): + if inspect.isclass(v): # otherwise it would be "__builtin__" + v.__module__ = "__dynamic__" + setattr(mod, k, v) + return mod + +#</import_with_metaclass.py> + +>>> from import_with_metaclass import import_with_metaclass +>>> from metatracer import MetaTracer +>>> traced_optparse = import_with_metaclass(MetaTracer, +... "/usr/lib/python2.4/optparse.py") +>>> op = traced_optparse.OptionParser() +calling __dynamic__.OptionParser.__init__ +calling __dynamic__.OptionContainer.__init__ +calling __dynamic__.OptionParser._create_option_list +calling __dynamic__.OptionContainer._create_option_mappings +calling __dynamic__.OptionContainer.set_conflict_handler +calling __dynamic__.OptionContainer.set_description +calling __dynamic__.OptionParser.set_usage +calling __dynamic__.IndentedHelpFormatter.__init__ +calling __dynamic__.HelpFormatter.__init__ +calling __dynamic__.HelpFormatter.set_parser +calling __dynamic__.OptionParser._populate_option_list +calling __dynamic__.OptionParser._add_help_option +calling __dynamic__.OptionContainer.add_option +calling __dynamic__.Option.__init__ +calling __dynamic__.Option._check_opt_strings +calling __dynamic__.Option._set_opt_strings +calling __dynamic__.Option._set_attrs +calling __dynamic__.OptionContainer._check_conflict +calling __dynamic__.OptionParser._init_parsing_state + +traced_optparse is a dynamically generated module not leaving in the +file system. + +Magic properties +-------------------- +:: + + #<magicprop.py> + + class MagicProperties(type): + def __init__(cls, name, bases, dic): + prop_names = set(name[3:] for name in dic + if name.startswith("get") + or name.startswith("set")) + for name in prop_names: + getter = getattr(cls, "get" + name, None) + setter = getattr(cls, "set" + name, None) + setattr(cls, name, property(getter, setter)) + + class Base(object): + __metaclass__ = MagicProperties + def getx(self): + return self._x + def setx(self, value): + self._x = value + + class Child(Base): + def getx(self): + print "getting x" + return super(Child, self).getx() + def setx(self, value): + print "setting x" + super(Child, self).setx(value) + + #</magicprop.py> + +>>> from magicprop import Child +>>> c = Child() +>>> c.x = 1 +setting x +>>> print c.x +getting x +1 + +Hack: evil properties +------------------------------------ + +:: + + #<evilprop.py> + + def convert2property(name, bases, d): + return property(d.get('get'), d.get('set'), + d.get('del'),d.get('__doc__')) + + class C(object): + class x: + """An evil test property""" + __metaclass__ = convert2property + def get(self): + print 'Getting %s' % self._x + return self._x + def set(self, value): + self._x = value + print 'Setting to', value + + #</evilprop.py> + +>>> from evilprop import C +>>> c = C() +>>> c.x = 5 +Setting to 5 +>>> c.x +Getting 5 +5 +>>> print C.x.__doc__ +An evil test property + +Why I suggest *not* to use metaclasses in production code +--------------------------------------------------------- + + + there are very few good use case for metaclasses in production code + (i.e. 99% of time you don't need them) + + + they put a cognitive burden on the developer; + + + a design without metaclasses is less magic and likely more robust; + + + a design with metaclasses makes it difficult to use other metaclasses + for debugging. + +As far as I know, string.Template is the only metaclass-enhanced class +in the standard library; the metaclass is used to give the possibility to +change the defaults:: + + delimiter = '$' + idpattern = r'[_a-z][_a-z0-9]*' + +in subclasses of Template. + +>>> from string import Template +>>> from metatracer import MetaTracer +>>> class TracedTemplate(Template): +... __metaclass__ = MetaTracer +... +Traceback (most recent call last): + ... +TypeError: Error when calling the metaclass bases + metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases + +Solution: use a consistent metaclass + +>>> class GoodMeta(MetaTracer, type(Template)): pass +... +>>> class TracedTemplate(Template): +... __metaclass__ = GoodMeta + + +Is there an automatic way of solving the conflict? +--------------------------------------------------------------------- + +Yes, but you really need to be a metaclass wizard. + +http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/204197 + +>>> from noconflict import classmaker +>>> class TracedTemplate(Template): +... __metaclass__ = classmaker((MetaTracer,)) +>>> print type(TracedTemplate) +<class 'noconflict._MetaTracer_TemplateMetaclass'> + +:: + + #<noconflict.py> + + import inspect, types, __builtin__ + from skip_redundant import skip_redundant + + memoized_metaclasses_map = {} + + # utility function + def remove_redundant(metaclasses): + skipset = set([types.ClassType]) + for meta in metaclasses: # determines the metaclasses to be skipped + skipset.update(inspect.getmro(meta)[1:]) + return tuple(skip_redundant(metaclasses, skipset)) + + ################################################################## + ## now the core of the module: two mutually recursive functions ## + ################################################################## + + def get_noconflict_metaclass(bases, left_metas, right_metas): + """Not intended to be used outside of this module, unless you know + what you are doing.""" + # make tuple of needed metaclasses in specified priority order + metas = left_metas + tuple(map(type, bases)) + right_metas + needed_metas = remove_redundant(metas) + + # return existing confict-solving meta, if any + if needed_metas in memoized_metaclasses_map: + return memoized_metaclasses_map[needed_metas] + # nope: compute, memoize and return needed conflict-solving meta + elif not needed_metas: # wee, a trivial case, happy us + meta = type + elif len(needed_metas) == 1: # another trivial case + meta = needed_metas[0] + # check for recursion, can happen i.e. for Zope ExtensionClasses + elif needed_metas == bases: + raise TypeError("Incompatible root metatypes", needed_metas) + else: # gotta work ... + metaname = '_' + ''.join([m.__name__ for m in needed_metas]) + meta = classmaker()(metaname, needed_metas, {}) + memoized_metaclasses_map[needed_metas] = meta + return meta + + def classmaker(left_metas=(), right_metas=()): + def make_class(name, bases, adict): + metaclass = get_noconflict_metaclass(bases, left_metas, right_metas) + return metaclass(name, bases, adict) + return make_class + + ################################################################# + ## and now a conflict-safe replacement for 'type' ## + ################################################################# + + __type__=__builtin__.type # the aboriginal 'type' + # left available in case you decide to rebind __builtin__.type + + class safetype(__type__): + # this is REALLY DEEP MAGIC + """Overrides the ``__new__`` method of the ``type`` metaclass, making the + generation of classes conflict-proof.""" + def __new__(mcl, *args): + nargs = len(args) + if nargs == 1: # works as __builtin__.type + return __type__(args[0]) + elif nargs == 3: # creates the class using the appropriate metaclass + n, b, d = args # name, bases and dictionary + meta = get_noconflict_metaclass(b, (mcl,), ()) + if meta is mcl: # meta is trivial, dispatch to the default __new__ + return super(safetype, mcl).__new__(mcl, n, b, d) + else: # non-trivial metaclass, dispatch to the right __new__ + # (it will take a second round) # print mcl, meta + return super(mcl, meta).__new__(meta, n, b, d) + else: + raise TypeError('%s() takes 1 or 3 arguments' % mcl.__name__) + + #</noconflict.py> diff --git a/pypers/oxford/magicprop.py b/pypers/oxford/magicprop.py new file mode 100755 index 0000000..c536b27 --- /dev/null +++ b/pypers/oxford/magicprop.py @@ -0,0 +1,28 @@ +# magicprop.py
+
+class MagicProperties(type):
+ def __init__(cls, name, bases, dic):
+ prop_names = set(name[3:] for name in dic
+ if name.startswith("get")
+ or name.startswith("set"))
+ for name in prop_names:
+ getter = getattr(cls, "get" + name, None)
+ setter = getattr(cls, "set" + name, None)
+ setattr(cls, name, property(getter, setter))
+
+class Base(object):
+ __metaclass__ = MagicProperties
+ def getx(self):
+ return self._x
+ def setx(self, value):
+ self._x = value
+
+class Child(Base):
+ def getx(self):
+ print "getting x"
+ return super(Child, self).getx()
+ def setx(self, value):
+ print "setting x"
+ super(Child, self).setx(value)
+
+
diff --git a/pypers/oxford/magicsuper.py b/pypers/oxford/magicsuper.py new file mode 100755 index 0000000..0842f82 --- /dev/null +++ b/pypers/oxford/magicsuper.py @@ -0,0 +1,104 @@ +"""MagicSuper: an example of metaclass recompiling the source code. +This provides Python with a ``callsupermethod`` macro simplifying +the cooperative call syntax. +Examples: + +from magicsuper import object + +class B(object): + def __new__(cls, *args, **kw): + print "B.__new__" + return callsupermethod(cls) + def __init__(self, *args, **kw): + print "B.__init__" + callsupermethod(*args, **kw) + @staticmethod + def sm(): + print "B.sm" + @classmethod + def cm(cls): + print cls.__name__ + + +class C(B): + def __new__(cls, *args, **kw): + print args, kw + return callsupermethod(cls, *args, **kw) + @staticmethod + def sm(): + callsupermethod() + @classmethod + def cm(cls): + callsupermethod() + +c = C() +c.cm() +c.sm() +""" + +import inspect, textwrap + +class MagicSuper(type): + def __init__(cls, clsname, bases, dic): + clsmodule = __import__(cls.__module__) #assume cls is defined in source + for name, value in dic.iteritems(): + # __new__ is seen as a function in the dic, so it has + # to be converted explicitly into a staticmethod; + # ordinary staticmethods don't type-dispatch on their + # first argument, so use 'super(cls, cls)' for them. + was_staticmethod = False + if isinstance(value, staticmethod): + value = value.__get__("dummy") # convert to function + was_staticmethod = True + elif isinstance(value, classmethod): + value = value.__get__("dummy").im_func # convert to function + if inspect.isfunction(value): + if was_staticmethod: + first_arg = clsname + else: + first_arg = inspect.getargspec(value)[0][0] + source = textwrap.dedent(inspect.getsource(value)) + if not 'callsupermethod' in source: continue + source = source.replace( + 'callsupermethod', 'super(%s, %s).%s' + % (clsname, first_arg, name)) + #print source # debug + exec source in clsmodule.__dict__, dic # modifies dic + if name == "__new__": + dic[name] = staticmethod(dic[name]) + setattr(cls, name, dic[name]) + +object = MagicSuper("object", (), {}) +type = MagicSuper("type", (type,), {}) + +# example: + +class B(object): + def __new__(cls, *args, **kw): + return callsupermethod(cls) + def __init__(self, *args, **kw): + print "B.__init__" + callsupermethod(*args, **kw) + @staticmethod + def sm(): + print "B.sm" + @classmethod + def cm(cls): + print cls.__name__ + + +class C(B): + def __new__(cls, *args, **kw): + print args, kw + return callsupermethod(cls, *args, **kw) + @staticmethod + def sm(): + callsupermethod() + @classmethod + def cm(cls): + callsupermethod() + +if __name__ == "__main__": + c = C(1, x=2) + c.sm() + c.cm() diff --git a/pypers/oxford/martelli.txt b/pypers/oxford/martelli.txt new file mode 100755 index 0000000..76a5926 --- /dev/null +++ b/pypers/oxford/martelli.txt @@ -0,0 +1,37 @@ +Design Patterns, Idioms, and Other Wonders of Today's Python +presented by Alex Martelli and Anna Ravenscroft + +The Second Edition of the Python Cookbook is out -- just +in time for the ACCU conference! Of course, we couldn't fit in the +book all the in-depth analysis and explanations we'd have liked +to. So, for this seminar, we picked some of our favourite stuff from +the book, and beefed it up with a thorough grounding in the relevant +language mechanisms, examples big and small, and related materials. We +explore in depth a variety of design choices that today's Python makes +available to you. + +Learn about Design Patterns and other Object-Oriented idioms and +mechanisms. Python is a multi-paradigm language, but OOP is its core +paradigm. Understand the pros and cons of your alternatives: When +should you use closures, rather than callable instances? When is +inheritance OK, and when is it better to hold-and-delegate? What +classical Design Patterns are built-in to Python, and which others are +appropriate to consider, when? + +Iterators and Generators underlie Python's new approach to looping -- +it's not your grandparents' loop any more! Learn how to encapsulate +the underlying logic of your control structures and make it +reusable. See how itertools can turn the "abstraction penalty" typical +of other languages into an abstraction _bonus_, making your code +faster at the same time as more abstract and general. + +Descriptors and Metaclasses are the underpinnings of today's Python's +OOP -- Python exposes them and lets you customize them for your own +purposes. Add Decorators, the new syntax just introduced in Python 2.4 +(a systematic application of a crucial use case for higher-order +functions), and you'll see why the working title of that chapter was +"Black Magic"... Learn important use cases for each of these advanced +mechanisms. + +Prerequisites: you need a solid grasp of Python fundamentals to start +with. Course objectives: you'll walk out of this a Python wizard! diff --git a/pypers/oxford/metatracer.py b/pypers/oxford/metatracer.py new file mode 100755 index 0000000..253fb7c --- /dev/null +++ b/pypers/oxford/metatracer.py @@ -0,0 +1,21 @@ +# metatracer.py
+
+import inspect
+from decorators import decorator
+
+@decorator
+def traced(meth, *args, **kw):
+ cls = meth.__cls__
+ modname = meth.__module__ or cls.__module__
+ print "calling %s.%s.%s" % (modname, cls.__name__, meth.__name__)
+ return meth(*args, **kw)
+
+class MetaTracer(type):
+ def __init__(cls, name, bases, dic):
+ super(MetaTracer, cls).__init__(name, bases, dic)
+ for k, v in dic.iteritems():
+ if inspect.isfunction(v):
+ v.__cls__ = cls # so we know in which class v was defined
+ setattr(cls, k, traced(v))
+
+
diff --git a/pypers/oxford/metatracer2.py b/pypers/oxford/metatracer2.py new file mode 100755 index 0000000..602b964 --- /dev/null +++ b/pypers/oxford/metatracer2.py @@ -0,0 +1,28 @@ + +from ms.lang_utils import WrapMethods, decorator + +# to be called from the metaclass +@decorator +def tracer(func, *args, **kw): + cls = func.__cls__ + modname = func.__module__ or cls.__module__ + print "calling %s.%s.%s" % (modname, cls.__name__, func.__name__) + return func(*args, **kw) + +class Traced(tracer.metaclass): + def __init__(cls, name, bases, dic): + for name, attr in dic.iteritems(): + if not name.startswith("__"): + setattr(cls, name, attr) + +print tracer.metaclass + +class C: + __metaclass__ = Traced + def __init__(self): + pass + def f(self): + pass + +c = C() +c.f() diff --git a/pypers/oxford/mro.html b/pypers/oxford/mro.html new file mode 100755 index 0000000..d49609c --- /dev/null +++ b/pypers/oxford/mro.html @@ -0,0 +1,788 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> +<meta name="generator" content="Docutils 0.3.7: http://docutils.sourceforge.net/" /> +<title>The Python 2.3 Method Resolution Order</title> +<meta name="author" content="Michele Simionato" /> +<link rel="stylesheet" href="default.css" type="text/css" /> +</head> +<body> +<div class="document" id="the-python-2-3-method-resolution-order"> +<h1 class="title">The Python 2.3 Method Resolution Order</h1> +<table class="docinfo" frame="void" rules="none"> +<col class="docinfo-name" /> +<col class="docinfo-content" /> +<tbody valign="top"> +<tr><th class="docinfo-name">Version:</th> +<td>1.4</td></tr> +<tr><th class="docinfo-name">Author:</th> +<td>Michele Simionato</td></tr> +<tr class="field"><th class="docinfo-name">E-mail:</th><td class="field-body"><a class="reference" href="mailto:michelesimionato@libero.it">michelesimionato@libero.it</a></td> +</tr> +<tr><th class="docinfo-name">Address:</th> +<td><pre class="address"> +Department of Physics and Astronomy +210 Allen Hall Pittsburgh PA 15260 U.S.A. +</pre> +</td></tr> +<tr class="field"><th class="docinfo-name">Home-page:</th><td class="field-body"><a class="reference" href="http://www.phyast.pitt.edu/~micheles/">http://www.phyast.pitt.edu/~micheles/</a></td> +</tr> +</tbody> +</table> +<div class="abstract topic"> +<p class="topic-title first">Abstract</p> +<p><em>This document is intended for Python programmers who want to +understand the C3 Method Resolution Order used in Python 2.3. +Although it is not intended for newbies, it is quite pedagogical with +many worked out examples. I am not aware of other publicly available +documents with the same scope, therefore it should be useful.</em></p> +</div> +<p>Disclaimer:</p> +<blockquote> +I donate this document to the Python Software Foundation, under the +Python 2.3 license. As usual in these circumstances, I warn the +reader that what follows <em>should</em> be correct, but I don't give any +warranty. Use it at your own risk and peril!</blockquote> +<p>Acknowledgments:</p> +<blockquote> +All the people of the Python mailing list who sent me their support. +Paul Foley who pointed out various imprecisions and made me to add the +part on local precedence ordering. David Goodger for help with the +formatting in reStructuredText. David Mertz for help with the editing. +Joan G. Stark for the pythonic pictures. Finally, Guido van Rossum who +enthusiastically added this document to the official Python 2.3 home-page.</blockquote> +<hr class="docutils" /> +<blockquote> +<pre class="literal-block"> + .-=-. .--. + __ .' '. / " ) + _ .' '. / .-. \ / .-'\ + ( \ / .-. \ / / \ \ / / ^ + \ `-` / \ `-' / \ `-` / +jgs`-.-` '.____.' `.____.' +</pre> +</blockquote> +<div class="section" id="the-beginning"> +<h1><a name="the-beginning">The beginning</a></h1> +<blockquote> +<em>Felix qui potuit rerum cognoscere causas</em> -- Virgilius</blockquote> +<p>Everything started with a post by Samuele Pedroni to the Python +development mailing list <a class="footnote-reference" href="#id4" id="id1" name="id1">[1]</a>. In his post, Samuele showed that the +Python 2.2 method resolution order is not monotonic and he proposed to +replace it with the C3 method resolution order. Guido agreed with his +arguments and therefore now Python 2.3 uses C3. The C3 method itself +has nothing to do with Python, since it was invented by people working +on Dylan and it is described in a paper intended for lispers <a class="footnote-reference" href="#id5" id="id2" name="id2">[2]</a>. The +present paper gives a (hopefully) readable discussion of the C3 +algorithm for Pythonistas who want to understand the reasons for the +change.</p> +<p>First of all, let me point out that what I am going to say only applies +to the <em>new style classes</em> introduced in Python 2.2: <em>classic classes</em> +maintain their old method resolution order, depth first and then left to +right. Therefore, there is no breaking of old code for classic classes; +and even if in principle there could be breaking of code for Python 2.2 +new style classes, in practice the cases in which the C3 resolution +order differs from the Python 2.2 method resolution order are so rare +that no real breaking of code is expected. Therefore:</p> +<blockquote> +<em>Don't be scared!</em></blockquote> +<p>Moreover, unless you make strong use of multiple inheritance and you +have non-trivial hierarchies, you don't need to understand the C3 +algorithm, and you can easily skip this paper. On the other hand, if +you really want to know how multiple inheritance works, then this paper +is for you. The good news is that things are not as complicated as you +might expect.</p> +<p>Let me begin with some basic definitions.</p> +<ol class="arabic simple"> +<li>Given a class C in a complicated multiple inheritance hierarchy, it +is a non-trivial task to specify the order in which methods are +overridden, i.e. to specify the order of the ancestors of C.</li> +<li>The list of the ancestors of a class C, including the class itself, +ordered from the nearest ancestor to the furthest, is called the +class precedence list or the <em>linearization</em> of C.</li> +<li>The <em>Method Resolution Order</em> (MRO) is the set of rules that +construct the linearization. In the Python literature, the idiom +"the MRO of C" is also used as a synonymous for the linearization of +the class C.</li> +<li>For instance, in the case of single inheritance hierarchy, if C is a +subclass of C1, and C1 is a subclass of C2, then the linearization of +C is simply the list [C, C1 , C2]. However, with multiple +inheritance hierarchies, the construction of the linearization is +more cumbersome, since it is more difficult to construct a +linearization that respects <em>local precedence ordering</em> and +<em>monotonicity</em>.</li> +<li>I will discuss the local precedence ordering later, but I can give +the definition of monotonicity here. A MRO is monotonic when the +following is true: <em>if C1 precedes C2 in the linearization of C, +then C1 precedes C2 in the linearization of any subclass of C</em>. +Otherwise, the innocuous operation of deriving a new class could +change the resolution order of methods, potentially introducing very +subtle bugs. Examples where this happens will be shown later.</li> +<li>Not all classes admit a linearization. There are cases, in +complicated hierarchies, where it is not possible to derive a class +such that its linearization respects all the desired properties.</li> +</ol> +<p>Here I give an example of this situation. Consider the hierarchy</p> +<blockquote> +<pre class="doctest-block"> +>>> O = object +>>> class X(O): pass +>>> class Y(O): pass +>>> class A(X,Y): pass +>>> class B(Y,X): pass +</pre> +</blockquote> +<p>which can be represented with the following inheritance graph, where I +have denoted with O the <tt class="docutils literal"><span class="pre">object</span></tt> class, which is the beginning of any +hierarchy for new style classes:</p> +<blockquote> +<pre class="literal-block"> + ----------- +| | +| O | +| / \ | + - X Y / + | / | / + | / |/ + A B + \ / + ? +</pre> +</blockquote> +<p>In this case, it is not possible to derive a new class C from A and B, +since X precedes Y in A, but Y precedes X in B, therefore the method +resolution order would be ambiguous in C.</p> +<p>Python 2.3 raises an exception in this situation (TypeError: MRO +conflict among bases Y, X) forbidding the naive programmer from creating +ambiguous hierarchies. Python 2.2 instead does not raise an exception, +but chooses an <em>ad hoc</em> ordering (CABXYO in this case).</p> +<hr class="docutils" /> +<blockquote> +<pre class="literal-block"> + _ .-=-. .-==-. + { } __ .' O o '. / -<' ) + { } .' O'. / o .-. O \ / .--v` + { } / .-. o\ /O / \ o\ /O / + \ `-` / \ O`-'o / \ O`-`o / +jgs `-.-` '.____.' `.____.' +</pre> +</blockquote> +</div> +<div class="section" id="the-c3-method-resolution-order"> +<h1><a name="the-c3-method-resolution-order">The C3 Method Resolution Order</a></h1> +<p>Let me introduce a few simple notations which will be useful for the +following discussion. I will use the shortcut notation</p> +<blockquote> +C1 C2 ... CN</blockquote> +<p>to indicate the list of classes [C1, C2, ... , CN].</p> +<p>The <em>head</em> of the list is its first element:</p> +<blockquote> +head = C1</blockquote> +<p>whereas the <em>tail</em> is the rest of the list:</p> +<blockquote> +tail = C2 ... CN.</blockquote> +<p>I shall also use the notation</p> +<blockquote> +C + (C1 C2 ... CN) = C C1 C2 ... CN</blockquote> +<p>to denote the sum of the lists [C] + [C1, C2, ... ,CN].</p> +<p>Now I can explain how the MRO works in Python 2.3.</p> +<p>Consider a class C in a multiple inheritance hierarchy, with C +inheriting from the base classes B1, B2, ... , BN. We want to +compute the linearization L[C] of the class C. The rule is the +following:</p> +<blockquote> +<em>the linearization of C is the sum of C plus the merge of the +linearizations of the parents and the list of the parents.</em></blockquote> +<p>In symbolic notation:</p> +<blockquote> +L[C(B1 ... BN)] = C + merge(L[B1] ... L[BN], B1 ... BN)</blockquote> +<p>In particular, if C is the <tt class="docutils literal"><span class="pre">object</span></tt> class, which has no parents, the +linearization is trivial:</p> +<blockquote> +L[object] = object.</blockquote> +<p>However, in general one has to compute the merge according to the following +prescription:</p> +<blockquote> +<em>take the head of the first list, i.e L[B1][0]; if this head is not in +the tail of any of the other lists, then add it to the linearization +of C and remove it from the lists in the merge, otherwise look at the +head of the next list and take it, if it is a good head. Then repeat +the operation until all the class are removed or it is impossible to +find good heads. In this case, it is impossible to construct the +merge, Python 2.3 will refuse to create the class C and will raise an +exception.</em></blockquote> +<p>This prescription ensures that the merge operation <em>preserves</em> the +ordering, if the ordering can be preserved. On the other hand, if the +order cannot be preserved (as in the example of serious order +disagreement discussed above) then the merge cannot be computed.</p> +<p>The computation of the merge is trivial if C has only one parent +(single inheritance); in this case</p> +<blockquote> +L[C(B)] = C + merge(L[B],B) = C + L[B]</blockquote> +<p>However, in the case of multiple inheritance things are more cumbersome +and I don't expect you can understand the rule without a couple of +examples ;-)</p> +<hr class="docutils" /> +<blockquote> +<pre class="literal-block"> + .-'-. + /' `\ + /' _.-.-._ `\ + | (|) (|) | + | \__"__/ | + \ |v.v| / + \ | | | / + `\ |=^-| /' + `|=-=|' + | - | + |= | + |-=-| + _.-=-=|= -|=-=-._ + ( |___| ) + ( `-=-=-=-=-=-=-=-` ) + (`-=-=-=-=-=-=-=-=-`) + (`-=-=-=-=-=-=-=-=-`) + (`-=-=-=-=-=-=-=-`) + (`-=-=-=-=-=-=-`) +jgs `-=-=-=-=-=-=-` +</pre> +</blockquote> +</div> +<div class="section" id="examples"> +<h1><a name="examples">Examples</a></h1> +<p>First example. Consider the following hierarchy:</p> +<blockquote> +<pre class="doctest-block"> +>>> O = object +>>> class F(O): pass +>>> class E(O): pass +>>> class D(O): pass +>>> class C(D,F): pass +>>> class B(D,E): pass +>>> class A(B,C): pass +</pre> +</blockquote> +<p>In this case the inheritance graph can be drawn as</p> +<blockquote> +<pre class="literal-block"> + 6 + --- +Level 3 | O | (more general) + / --- \ + / | \ | + / | \ | + / | \ | + --- --- --- | +Level 2 3 | D | 4| E | | F | 5 | + --- --- --- | + \ \ _ / | | + \ / \ _ | | + \ / \ | | + --- --- | +Level 1 1 | B | | C | 2 | + --- --- | + \ / | + \ / \ / + --- +Level 0 0 | A | (more specialized) + --- +</pre> +</blockquote> +<p>The linearizations of O,D,E and F are trivial:</p> +<blockquote> +<pre class="literal-block"> +L[O] = O +L[D] = D O +L[E] = E O +L[F] = F O +</pre> +</blockquote> +<p>The linearization of B can be computed as</p> +<blockquote> +<pre class="literal-block"> +L[B] = B + merge(DO, EO, DE) +</pre> +</blockquote> +<p>We see that D is a good head, therefore we take it and we are reduced to +compute <tt class="docutils literal"><span class="pre">merge(O,EO,E)</span></tt>. Now O is not a good head, since it is in the +tail of the sequence EO. In this case the rule says that we have to +skip to the next sequence. Then we see that E is a good head; we take +it and we are reduced to compute <tt class="docutils literal"><span class="pre">merge(O,O)</span></tt> which gives O. Therefore</p> +<blockquote> +<pre class="literal-block"> +L[B] = B D E O +</pre> +</blockquote> +<p>Using the same procedure one finds:</p> +<blockquote> +<pre class="literal-block"> +L[C] = C + merge(DO,FO,DF) + = C + D + merge(O,FO,F) + = C + D + F + merge(O,O) + = C D F O +</pre> +</blockquote> +<p>Now we can compute:</p> +<blockquote> +<pre class="literal-block"> +L[A] = A + merge(BDEO,CDFO,BC) + = A + B + merge(DEO,CDFO,C) + = A + B + C + merge(DEO,DFO) + = A + B + C + D + merge(EO,FO) + = A + B + C + D + E + merge(O,FO) + = A + B + C + D + E + F + merge(O,O) + = A B C D E F O +</pre> +</blockquote> +<p>In this example, the linearization is ordered in a pretty nice way +according to the inheritance level, in the sense that lower levels (i.e. +more specialized classes) have higher precedence (see the inheritance +graph). However, this is not the general case.</p> +<p>I leave as an exercise for the reader to compute the linearization for +my second example:</p> +<blockquote> +<pre class="doctest-block"> +>>> O = object +>>> class F(O): pass +>>> class E(O): pass +>>> class D(O): pass +>>> class C(D,F): pass +>>> class B(E,D): pass +>>> class A(B,C): pass +</pre> +</blockquote> +<p>The only difference with the previous example is the change B(D,E) --> +B(E,D); however even such a little modification completely changes the +ordering of the hierarchy</p> +<blockquote> +<pre class="literal-block"> + 6 + --- +Level 3 | O | + / --- \ + / | \ + / | \ + / | \ + --- --- --- +Level 2 2 | E | 4 | D | | F | 5 + --- --- --- + \ / \ / + \ / \ / + \ / \ / + --- --- +Level 1 1 | B | | C | 3 + --- --- + \ / + \ / + --- +Level 0 0 | A | + --- +</pre> +</blockquote> +<p>Notice that the class E, which is in the second level of the hierarchy, +precedes the class C, which is in the first level of the hierarchy, i.e. +E is more specialized than C, even if it is in a higher level.</p> +<p>A lazy programmer can obtain the MRO directly from Python 2.2, since in +this case it coincides with the Python 2.3 linearization. It is enough +to invoke the .mro() method of class A:</p> +<blockquote> +<pre class="doctest-block"> +>>> A.mro() +(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, +<class '__main__.C'>, <class '__main__.D'>, <class '__main__.F'>, +<type 'object'>) +</pre> +</blockquote> +<p>Finally, let me consider the example discussed in the first section, +involving a serious order disagreement. In this case, it is +straightforward to compute the linearizations of O, X, Y, A and B:</p> +<blockquote> +<pre class="literal-block"> +L[O] = 0 +L[X] = X O +L[Y] = Y O +L[A] = A X Y O +L[B] = B Y X O +</pre> +</blockquote> +<p>However, it is impossible to compute the linearization for a class C +that inherits from A and B:</p> +<blockquote> +<pre class="literal-block"> +L[C] = C + merge(AXYO, BYXO, AB) + = C + A + merge(XYO, BYXO, B) + = C + A + B + merge(XYO, YXO) +</pre> +</blockquote> +<p>At this point we cannot merge the lists XYO and YXO, since X is in the +tail of YXO whereas Y is in the tail of XYO: therefore there are no +good heads and the C3 algorithm stops. Python 2.3 raises an error and +refuses to create the class C.</p> +<hr class="docutils" /> +<blockquote> +<pre class="literal-block"> + __ + (\ .-. .-. /_") + \\_//^\\_//^\\_// +jgs `"` `"` `"` +</pre> +</blockquote> +</div> +<div class="section" id="bad-method-resolution-orders"> +<h1><a name="bad-method-resolution-orders">Bad Method Resolution Orders</a></h1> +<p>A MRO is <em>bad</em> when it breaks such fundamental properties as local +precedence ordering and monotonicity. In this section, I will show +that both the MRO for classic classes and the MRO for new style classes +in Python 2.2 are bad.</p> +<p>It is easier to start with the local precedence ordering. Consider the +following example:</p> +<blockquote> +<pre class="doctest-block"> +>>> F=type('Food',(),{'remember2buy':'spam'}) +>>> E=type('Eggs',(F,),{'remember2buy':'eggs'}) +>>> G=type('GoodFood',(F,E),{}) # under Python 2.3 this is an error! +</pre> +</blockquote> +<p>with inheritance diagram</p> +<blockquote> +<pre class="literal-block"> + O + | +(buy spam) F + | \ + | E (buy eggs) + | / + G + + (buy eggs or spam ?) +</pre> +</blockquote> +<p>We see that class G inherits from F and E, with F <em>before</em> E: therefore +we would expect the attribute <em>G.remember2buy</em> to be inherited by +<em>F.rembermer2buy</em> and not by <em>E.remember2buy</em>: nevertheless Python 2.2 +gives</p> +<blockquote> +<pre class="doctest-block"> +>>> G.remember2buy +'eggs' +</pre> +</blockquote> +<p>This is a breaking of local precedence ordering since the order in the +local precedence list, i.e. the list of the parents of G, is not +preserved in the Python 2.2 linearization of G:</p> +<blockquote> +<pre class="literal-block"> +L[G,P22]= G E F object # F *follows* E +</pre> +</blockquote> +<p>One could argue that the reason why F follows E in the Python 2.2 +linearization is that F is less specialized than E, since F is the +superclass of E; nevertheless the breaking of local precedence ordering +is quite non-intuitive and error prone. This is particularly true since +it is a different from old style classes:</p> +<blockquote> +<pre class="doctest-block"> +>>> class F: remember2buy='spam' +>>> class E(F): remember2buy='eggs' +>>> class G(F,E): pass +>>> G.remember2buy +'spam' +</pre> +</blockquote> +<p>In this case the MRO is GFEF and the local precedence ordering is +preserved.</p> +<p>As a general rule, hierarchies such as the previous one should be +avoided, since it is unclear if F should override E or viceversa. +Python 2.3 solves the ambiguity by raising an exception in the creation +of class G, effectively stopping the programmer from generating +ambiguous hierarchies. The reason for that is that the C3 algorithm +fails when the merge</p> +<blockquote> +<pre class="literal-block"> +merge(FO,EFO,FE) +</pre> +</blockquote> +<p>cannot be computed, because F is in the tail of EFO and E is in the tail +of FE.</p> +<p>The real solution is to design a non-ambiguous hierarchy, i.e. to derive +G from E and F (the more specific first) and not from F and E; in this +case the MRO is GEF without any doubt.</p> +<blockquote> +<pre class="literal-block"> + O + | + F (spam) + / | +(eggs) E | + \ | + G + (eggs, no doubt) +</pre> +</blockquote> +<p>Python 2.3 forces the programmer to write good hierarchies (or, at +least, less error-prone ones).</p> +<p>On a related note, let me point out that the Python 2.3 algorithm is +smart enough to recognize obvious mistakes, as the duplication of +classes in the list of parents:</p> +<blockquote> +<pre class="doctest-block"> +>>> class A(object): pass +>>> class C(A,A): pass # error +Traceback (most recent call last): + File "<stdin>", line 1, in ? +TypeError: duplicate base class A +</pre> +</blockquote> +<p>Python 2.2 (both for classic classes and new style classes) in this +situation, would not raise any exception.</p> +<p>Finally, I would like to point out two lessons we have learned from this +example:</p> +<ol class="arabic simple"> +<li>despite the name, the MRO determines the resolution order of +attributes, not only of methods;</li> +<li>the default food for Pythonistas is spam ! (but you already knew +that ;-)</li> +</ol> +<hr class="docutils" /> +<blockquote> +<pre class="literal-block"> + __ + (\ .-. .-. /_") + \\_//^\\_//^\\_// +jgs `"` `"` `"` +</pre> +</blockquote> +<p>Having discussed the issue of local precedence ordering, let me now +consider the issue of monotonicity. My goal is to show that neither the +MRO for classic classes nor that for Python 2.2 new style classes is +monotonic.</p> +<p>To prove that the MRO for classic classes is non-monotonic is rather +trivial, it is enough to look at the diamond diagram:</p> +<blockquote> +<pre class="literal-block"> + C + / \ + / \ +A B + \ / + \ / + D +</pre> +</blockquote> +<p>One easily discerns the inconsistency:</p> +<blockquote> +<pre class="literal-block"> +L[B,P21] = B C # B precedes C : B's methods win +L[D,P21] = D A C B C # B follows C : C's methods win! +</pre> +</blockquote> +<p>On the other hand, there are no problems with the Python 2.2 and 2.3 +MROs, they give both</p> +<blockquote> +<pre class="literal-block"> +L[D] = D A B C +</pre> +</blockquote> +<p>Guido points out in his essay <a class="footnote-reference" href="#id6" id="id3" name="id3">[3]</a> that the classic MRO is not so bad in +practice, since one can typically avoids diamonds for classic classes. +But all new style classes inherit from <tt class="docutils literal"><span class="pre">object</span></tt>, therefore diamonds are +unavoidable and inconsistencies shows up in every multiple inheritance +graph.</p> +<p>The MRO of Python 2.2 makes breaking monotonicity difficult, but not +impossible. The following example, originally provided by Samuele +Pedroni, shows that the MRO of Python 2.2 is non-monotonic:</p> +<blockquote> +<pre class="doctest-block"> +>>> class A(object): pass +>>> class B(object): pass +>>> class C(object): pass +>>> class D(object): pass +>>> class E(object): pass +>>> class K1(A,B,C): pass +>>> class K2(D,B,E): pass +>>> class K3(D,A): pass +>>> class Z(K1,K2,K3): pass +</pre> +</blockquote> +<p>Here are the linearizations according to the C3 MRO (the reader should +verify these linearizations as an exercise and draw the inheritance +diagram ;-)</p> +<blockquote> +<pre class="literal-block"> +L[A] = A O +L[B] = B O +L[C] = C O +L[D] = D O +L[E] = E O +L[K1]= K1 A B C O +L[K2]= K2 D B E O +L[K3]= K3 D A O +L[Z] = Z K1 K2 K3 D A B C E O +</pre> +</blockquote> +<p>Python 2.2 gives exactly the same linearizations for A, B, C, D, E, K1, +K2 and K3, but a different linearization for Z:</p> +<blockquote> +<pre class="literal-block"> +L[Z,P22] = Z K1 K3 A K2 D B C E O +</pre> +</blockquote> +<p>It is clear that this linearization is <em>wrong</em>, since A comes before D +whereas in the linearization of K3 A comes <em>after</em> D. In other words, in +K3 methods derived by D override methods derived by A, but in Z, which +still is a subclass of K3, methods derived by A override methods derived +by D! This is a violation of monotonicity. Moreover, the Python 2.2 +linearization of Z is also inconsistent with local precedence ordering, +since the local precedence list of the class Z is [K1, K2, K3] (K2 +precedes K3), whereas in the linearization of Z K2 <em>follows</em> K3. These +problems explain why the 2.2 rule has been dismissed in favor of the C3 +rule.</p> +<hr class="docutils" /> +<blockquote> +<pre class="literal-block"> + __ + (\ .-. .-. .-. .-. .-. .-. .-. .-. /_") + \\_//^\\_//^\\_//^\\_//^\\_//^\\_//^\\_//^\\_//^\\_// +jgs `"` `"` `"` `"` `"` `"` `"` `"` `"` +</pre> +</blockquote> +</div> +<div class="section" id="the-end"> +<h1><a name="the-end">The end</a></h1> +<p>This section is for the impatient reader, who skipped all the previous +sections and jumped immediately to the end. This section is for the +lazy programmer too, who didn't want to exercise her/his brain. +Finally, it is for the programmer with some hubris, otherwise s/he would +not be reading a paper on the C3 method resolution order in multiple +inheritance hierarchies ;-) These three virtues taken all together (and +<em>not</em> separately) deserve a prize: the prize is a short Python 2.2 +script that allows you to compute the 2.3 MRO without risk to your +brain. Simply change the last line to play with the various examples I +have discussed in this paper.</p> +<blockquote> +<pre class="literal-block"> +#<mro.py> + +"""C3 algorithm by Samuele Pedroni (with readability enhanced by me).""" + +class __metaclass__(type): + "All classes are metamagically modified to be nicely printed" + __repr__ = lambda cls: cls.__name__ + +class ex_2: + "Serious order disagreement" #From Guido + class O: pass + class X(O): pass + class Y(O): pass + class A(X,Y): pass + class B(Y,X): pass + try: + class Z(A,B): pass #creates Z(A,B) in Python 2.2 + except TypeError: + pass # Z(A,B) cannot be created in Python 2.3 + +class ex_5: + "My first example" + class O: pass + class F(O): pass + class E(O): pass + class D(O): pass + class C(D,F): pass + class B(D,E): pass + class A(B,C): pass + +class ex_6: + "My second example" + class O: pass + class F(O): pass + class E(O): pass + class D(O): pass + class C(D,F): pass + class B(E,D): pass + class A(B,C): pass + +class ex_9: + "Difference between Python 2.2 MRO and C3" #From Samuele + class O: pass + class A(O): pass + class B(O): pass + class C(O): pass + class D(O): pass + class E(O): pass + class K1(A,B,C): pass + class K2(D,B,E): pass + class K3(D,A): pass + class Z(K1,K2,K3): pass + +def merge(seqs): + print '\n\nCPL[%s]=%s' % (seqs[0][0],seqs), + res = []; i=0 + while 1: + nonemptyseqs=[seq for seq in seqs if seq] + if not nonemptyseqs: return res + i+=1; print '\n',i,'round: candidates...', + for seq in nonemptyseqs: # find merge candidates among seq heads + cand = seq[0]; print ' ',cand, + nothead=[s for s in nonemptyseqs if cand in s[1:]] + if nothead: cand=None #reject candidate + else: break + if not cand: raise "Inconsistent hierarchy" + res.append(cand) + for seq in nonemptyseqs: # remove cand + if seq[0] == cand: del seq[0] + +def mro(C): + "Compute the class precedence list (mro) according to C3" + return merge([[C]]+map(mro,C.__bases__)+[list(C.__bases__)]) + +def print_mro(C): + print '\nMRO[%s]=%s' % (C,mro(C)) + print '\nP22 MRO[%s]=%s' % (C,C.mro()) + +print_mro(ex_9.Z) + +#</mro.py> +</pre> +</blockquote> +<p>That's all folks,</p> +<blockquote> +enjoy !</blockquote> +<hr class="docutils" /> +<blockquote> +<pre class="literal-block"> + __ + ("_\ .-. .-. .-. .-. .-. .-. .-. .-. /) + \\_//^\\_//^\\_//^\\_//^\\_//^\\_//^\\_//^\\_//^\\_// +jgs `"` `"` `"` `"` `"` `"` `"` `"` `"` +</pre> +</blockquote> +</div> +<div class="section" id="resources"> +<h1><a name="resources">Resources</a></h1> +<table class="docutils footnote" frame="void" id="id4" rules="none"> +<colgroup><col class="label" /><col /></colgroup> +<tbody valign="top"> +<tr><td class="label"><a class="fn-backref" href="#id1" name="id4">[1]</a></td><td>The thread on python-dev started by Samuele Pedroni: +<a class="reference" href="http://mail.python.org/pipermail/python-dev/2002-October/029035.html">http://mail.python.org/pipermail/python-dev/2002-October/029035.html</a></td></tr> +</tbody> +</table> +<table class="docutils footnote" frame="void" id="id5" rules="none"> +<colgroup><col class="label" /><col /></colgroup> +<tbody valign="top"> +<tr><td class="label"><a class="fn-backref" href="#id2" name="id5">[2]</a></td><td>The paper <em>A Monotonic Superclass Linearization for Dylan</em>: +<a class="reference" href="http://www.webcom.com/haahr/dylan/linearization-oopsla96.html">http://www.webcom.com/haahr/dylan/linearization-oopsla96.html</a></td></tr> +</tbody> +</table> +<table class="docutils footnote" frame="void" id="id6" rules="none"> +<colgroup><col class="label" /><col /></colgroup> +<tbody valign="top"> +<tr><td class="label"><a class="fn-backref" href="#id3" name="id6">[3]</a></td><td>Guido van Rossum's essay, <em>Unifying types and classes in Python 2.2</em>: +<a class="reference" href="http://www.python.org/2.2.2/descrintro.html">http://www.python.org/2.2.2/descrintro.html</a></td></tr> +</tbody> +</table> +</div> +</div> +</body> +</html> diff --git a/pypers/oxford/mro.tex b/pypers/oxford/mro.tex new file mode 100755 index 0000000..1ad82c4 --- /dev/null +++ b/pypers/oxford/mro.tex @@ -0,0 +1,988 @@ +\documentclass[10pt,a4paper,english]{article} +\usepackage{babel} +\usepackage{ae} +\usepackage{aeguill} +\usepackage{shortvrb} +\usepackage[latin1]{inputenc} +\usepackage{tabularx} +\usepackage{longtable} +\setlength{\extrarowheight}{2pt} +\usepackage{amsmath} +\usepackage{graphicx} +\usepackage{color} +\usepackage{multirow} +\usepackage{ifthen} +\usepackage[colorlinks=true,linkcolor=blue,urlcolor=blue]{hyperref} +\usepackage[DIV12]{typearea} +%% generator Docutils: http://docutils.sourceforge.net/ +\newlength{\admonitionwidth} +\setlength{\admonitionwidth}{0.9\textwidth} +\newlength{\docinfowidth} +\setlength{\docinfowidth}{0.9\textwidth} +\newlength{\locallinewidth} +\newcommand{\optionlistlabel}[1]{\bf #1 \hfill} +\newenvironment{optionlist}[1] +{\begin{list}{} + {\setlength{\labelwidth}{#1} + \setlength{\rightmargin}{1cm} + \setlength{\leftmargin}{\rightmargin} + \addtolength{\leftmargin}{\labelwidth} + \addtolength{\leftmargin}{\labelsep} + \renewcommand{\makelabel}{\optionlistlabel}} +}{\end{list}} +\newlength{\lineblockindentation} +\setlength{\lineblockindentation}{2.5em} +\newenvironment{lineblock}[1] +{\begin{list}{} + {\setlength{\partopsep}{\parskip} + \addtolength{\partopsep}{\baselineskip} + \topsep0pt\itemsep0.15\baselineskip\parsep0pt + \leftmargin#1} + \raggedright} +{\end{list}} +% begin: floats for footnotes tweaking. +\setlength{\floatsep}{0.5em} +\setlength{\textfloatsep}{\fill} +\addtolength{\textfloatsep}{3em} +\renewcommand{\textfraction}{0.5} +\renewcommand{\topfraction}{0.5} +\renewcommand{\bottomfraction}{0.5} +\setcounter{totalnumber}{50} +\setcounter{topnumber}{50} +\setcounter{bottomnumber}{50} +% end floats for footnotes +% some commands, that could be overwritten in the style file. +\newcommand{\rubric}[1]{\subsection*{~\hfill {\it #1} \hfill ~}} +\newcommand{\titlereference}[1]{\textsl{#1}} +% end of "some commands" +\input{style.tex} +\title{The Python 2.3 Method Resolution Order} +\author{} +\date{} +\hypersetup{ +pdftitle={The Python 2.3 Method Resolution Order}, +pdfauthor={Michele Simionato} +} +\raggedbottom +\begin{document} +\maketitle + +%___________________________________________________________________________ +\begin{center} +\begin{tabularx}{\docinfowidth}{lX} +\textbf{Version}: & + 1.4 \\ +\textbf{Author}: & + Michele Simionato \\ +\textbf{E-mail}: & + michelesimionato@libero.it \\ +\textbf{Address}: & + {\raggedright +Department of Physics and Astronomy~\\ +210 Allen Hall Pittsburgh PA 15260 U.S.A. } \\ +\textbf{Home-page}: & + http://www.phyast.pitt.edu/{\textasciitilde}micheles/ \\ +\end{tabularx} +\end{center} + +\setlength{\locallinewidth}{\linewidth} + + +\subsubsection*{~\hfill Abstract\hfill ~} + +\emph{This document is intended for Python programmers who want to +understand the C3 Method Resolution Order used in Python 2.3. +Although it is not intended for newbies, it is quite pedagogical with +many worked out examples. I am not aware of other publicly available +documents with the same scope, therefore it should be useful.} + + +Disclaimer: +\begin{quote} + +I donate this document to the Python Software Foundation, under the +Python 2.3 license. As usual in these circumstances, I warn the +reader that what follows \emph{should} be correct, but I don't give any +warranty. Use it at your own risk and peril! +\end{quote} + +Acknowledgments: +\begin{quote} + +All the people of the Python mailing list who sent me their support. +Paul Foley who pointed out various imprecisions and made me to add the +part on local precedence ordering. David Goodger for help with the +formatting in reStructuredText. David Mertz for help with the editing. +Joan G. Stark for the pythonic pictures. Finally, Guido van Rossum who +enthusiastically added this document to the official Python 2.3 home-page. +\end{quote} + + +%___________________________________________________________________________ +\hspace*{\fill}\hrulefill\hspace*{\fill} + +\begin{quote} +\begin{quote}{\ttfamily \raggedright \noindent +~~~~~~~~~~~~~~~~~~~~~~.-=-.~~~~~~~~~~.-{}-.~\\ +~~~~~~~~~~{\_}{\_}~~~~~~~~.'~~~~~'.~~~~~~~/~~"~)~\\ +~~{\_}~~~~~.'~~'.~~~~~/~~~.-.~~~{\textbackslash}~~~~~/~~.-'{\textbackslash}~\\ +~(~{\textbackslash}~~~/~.-.~~{\textbackslash}~~~/~~~/~~~{\textbackslash}~~~{\textbackslash}~~~/~~/~~~~{\textasciicircum}~\\ +~~{\textbackslash}~`-`~/~~~{\textbackslash}~~`-'~~~/~~~~~{\textbackslash}~~~`-`~~/~\\ +jgs`-.-`~~~~~'.{\_}{\_}{\_}{\_}.'~~~~~~~`.{\_}{\_}{\_}{\_}.' +}\end{quote} +\end{quote} + + +%___________________________________________________________________________ + +\hypertarget{the-beginning}{} +\pdfbookmark[0]{The beginning}{the-beginning} +\section*{The beginning} +\begin{quote} + +\emph{Felix qui potuit rerum cognoscere causas} -{}- Virgilius +\end{quote} + +Everything started with a post by Samuele Pedroni to the Python +development mailing list [\hyperlink{id4}{1}]. In his post, Samuele showed that the +Python 2.2 method resolution order is not monotonic and he proposed to +replace it with the C3 method resolution order. Guido agreed with his +arguments and therefore now Python 2.3 uses C3. The C3 method itself +has nothing to do with Python, since it was invented by people working +on Dylan and it is described in a paper intended for lispers [\hyperlink{id5}{2}]. The +present paper gives a (hopefully) readable discussion of the C3 +algorithm for Pythonistas who want to understand the reasons for the +change. + +First of all, let me point out that what I am going to say only applies +to the \emph{new style classes} introduced in Python 2.2: \emph{classic classes} +maintain their old method resolution order, depth first and then left to +right. Therefore, there is no breaking of old code for classic classes; +and even if in principle there could be breaking of code for Python 2.2 +new style classes, in practice the cases in which the C3 resolution +order differs from the Python 2.2 method resolution order are so rare +that no real breaking of code is expected. Therefore: +\begin{quote} + +\emph{Don't be scared!} +\end{quote} + +Moreover, unless you make strong use of multiple inheritance and you +have non-trivial hierarchies, you don't need to understand the C3 +algorithm, and you can easily skip this paper. On the other hand, if +you really want to know how multiple inheritance works, then this paper +is for you. The good news is that things are not as complicated as you +might expect. + +Let me begin with some basic definitions. +\newcounter{listcnt1} +\begin{list}{\arabic{listcnt1})} +{ +\usecounter{listcnt1} +\setlength{\rightmargin}{\leftmargin} +} +\item {} +Given a class C in a complicated multiple inheritance hierarchy, it +is a non-trivial task to specify the order in which methods are +overridden, i.e. to specify the order of the ancestors of C. + +\item {} +The list of the ancestors of a class C, including the class itself, +ordered from the nearest ancestor to the furthest, is called the +class precedence list or the \emph{linearization} of C. + +\item {} +The \emph{Method Resolution Order} (MRO) is the set of rules that +construct the linearization. In the Python literature, the idiom +``the MRO of C'' is also used as a synonymous for the linearization of +the class C. + +\item {} +For instance, in the case of single inheritance hierarchy, if C is a +subclass of C1, and C1 is a subclass of C2, then the linearization of +C is simply the list {[}C, C1 , C2]. However, with multiple +inheritance hierarchies, the construction of the linearization is +more cumbersome, since it is more difficult to construct a +linearization that respects \emph{local precedence ordering} and +\emph{monotonicity}. + +\item {} +I will discuss the local precedence ordering later, but I can give +the definition of monotonicity here. A MRO is monotonic when the +following is true: \emph{if C1 precedes C2 in the linearization of C, +then C1 precedes C2 in the linearization of any subclass of C}. +Otherwise, the innocuous operation of deriving a new class could +change the resolution order of methods, potentially introducing very +subtle bugs. Examples where this happens will be shown later. + +\item {} +Not all classes admit a linearization. There are cases, in +complicated hierarchies, where it is not possible to derive a class +such that its linearization respects all the desired properties. + +\end{list} + +Here I give an example of this situation. Consider the hierarchy +\begin{quote} +\begin{verbatim}>>> O = object +>>> class X(O): pass +>>> class Y(O): pass +>>> class A(X,Y): pass +>>> class B(Y,X): pass\end{verbatim} +\end{quote} + +which can be represented with the following inheritance graph, where I +have denoted with O the \texttt{object} class, which is the beginning of any +hierarchy for new style classes: +\begin{quote} +\begin{quote}{\ttfamily \raggedright \noindent +~-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-~\\ +|~~~~~~~~~~~|~\\ +|~~~~O~~~~~~|~\\ +|~~/~~~{\textbackslash}~~~~|~\\ +~-~X~~~~Y~~/~\\ +~~~|~~/~|~/~\\ +~~~|~/~~|/~\\ +~~~A~~~~B~\\ +~~~{\textbackslash}~~~/~\\ +~~~~~? +}\end{quote} +\end{quote} + +In this case, it is not possible to derive a new class C from A and B, +since X precedes Y in A, but Y precedes X in B, therefore the method +resolution order would be ambiguous in C. + +Python 2.3 raises an exception in this situation (TypeError: MRO +conflict among bases Y, X) forbidding the naive programmer from creating +ambiguous hierarchies. Python 2.2 instead does not raise an exception, +but chooses an \emph{ad hoc} ordering (CABXYO in this case). + + +%___________________________________________________________________________ +\hspace*{\fill}\hrulefill\hspace*{\fill} + +\begin{quote} +\begin{quote}{\ttfamily \raggedright \noindent +~~~~{\_}~~~~~~~~~~~~~~~~~~~.-=-.~~~~~~~~~~.-==-.~\\ +~~~{\{}~{\}}~~~~~~{\_}{\_}~~~~~~~~.'~O~o~'.~~~~~~~/~~-<'~)~\\ +~~~{\{}~{\}}~~~~.'~O'.~~~~~/~o~.-.~O~{\textbackslash}~~~~~/~~.-{}-v`~\\ +~~~{\{}~{\}}~~~/~.-.~o{\textbackslash}~~~/O~~/~~~{\textbackslash}~~o{\textbackslash}~~~/O~/~\\ +~~~~{\textbackslash}~`-`~/~~~{\textbackslash}~O`-'o~~/~~~~~{\textbackslash}~~O`-`o~/~\\ +jgs~~`-.-`~~~~~'.{\_}{\_}{\_}{\_}.'~~~~~~~`.{\_}{\_}{\_}{\_}.' +}\end{quote} +\end{quote} + + +%___________________________________________________________________________ + +\hypertarget{the-c3-method-resolution-order}{} +\pdfbookmark[0]{The C3 Method Resolution Order}{the-c3-method-resolution-order} +\section*{The C3 Method Resolution Order} + +Let me introduce a few simple notations which will be useful for the +following discussion. I will use the shortcut notation +\begin{quote} + +C1 C2 ... CN +\end{quote} + +to indicate the list of classes {[}C1, C2, ... , CN]. + +The \emph{head} of the list is its first element: +\begin{quote} + +head = C1 +\end{quote} + +whereas the \emph{tail} is the rest of the list: +\begin{quote} + +tail = C2 ... CN. +\end{quote} + +I shall also use the notation +\begin{quote} + +C + (C1 C2 ... CN) = C C1 C2 ... CN +\end{quote} + +to denote the sum of the lists {[}C] + {[}C1, C2, ... ,CN]. + +Now I can explain how the MRO works in Python 2.3. + +Consider a class C in a multiple inheritance hierarchy, with C +inheriting from the base classes B1, B2, ... , BN. We want to +compute the linearization L{[}C] of the class C. The rule is the +following: +\begin{quote} + +\emph{the linearization of C is the sum of C plus the merge of the +linearizations of the parents and the list of the parents.} +\end{quote} + +In symbolic notation: +\begin{quote} + +L{[}C(B1 ... BN)] = C + merge(L{[}B1] ... L{[}BN], B1 ... BN) +\end{quote} + +In particular, if C is the \texttt{object} class, which has no parents, the +linearization is trivial: +\begin{quote} + +L{[}object] = object. +\end{quote} + +However, in general one has to compute the merge according to the following +prescription: +\begin{quote} + +\emph{take the head of the first list, i.e L{[}B1]{[}0]; if this head is not in +the tail of any of the other lists, then add it to the linearization +of C and remove it from the lists in the merge, otherwise look at the +head of the next list and take it, if it is a good head. Then repeat +the operation until all the class are removed or it is impossible to +find good heads. In this case, it is impossible to construct the +merge, Python 2.3 will refuse to create the class C and will raise an +exception.} +\end{quote} + +This prescription ensures that the merge operation \emph{preserves} the +ordering, if the ordering can be preserved. On the other hand, if the +order cannot be preserved (as in the example of serious order +disagreement discussed above) then the merge cannot be computed. + +The computation of the merge is trivial if C has only one parent +(single inheritance); in this case +\begin{quote} + +L{[}C(B)] = C + merge(L{[}B],B) = C + L{[}B] +\end{quote} + +However, in the case of multiple inheritance things are more cumbersome +and I don't expect you can understand the rule without a couple of +examples ;-) + + +%___________________________________________________________________________ +\hspace*{\fill}\hrulefill\hspace*{\fill} + +\begin{quote} +\begin{quote}{\ttfamily \raggedright \noindent +~~~~~~~~~~.-'-.~\\ +~~~~~~~~/'~~~~~`{\textbackslash}~\\ +~~~~~~/'~{\_}.-.-.{\_}~`{\textbackslash}~\\ +~~~~~|~~(|)~~~(|)~~|~\\ +~~~~~|~~~{\textbackslash}{\_}{\_}"{\_}{\_}/~~~|~\\ +~~~~~{\textbackslash}~~~~|v.v|~~~~/~\\ +~~~~~~{\textbackslash}~~~|~|~|~~~/~\\ +~~~~~~~`{\textbackslash}~|={\textasciicircum}-|~/'~\\ +~~~~~~~~~`|=-=|'~\\ +~~~~~~~~~~|~-~|~\\ +~~~~~~~~~~|=~~|~\\ +~~~~~~~~~~|-=-|~\\ +~~~~{\_}.-=-=|=~-|=-=-.{\_}~\\ +~~~(~~~~~~|{\_}{\_}{\_}|~~~~~~)~\\ +~~(~`-=-=-=-=-=-=-=-`~)~\\ +~~(`-=-=-=-=-=-=-=-=-`)~\\ +~~(`-=-=-=-=-=-=-=-=-`)~\\ +~~~(`-=-=-=-=-=-=-=-`)~\\ +~~~~(`-=-=-=-=-=-=-`)~\\ +jgs~~`-=-=-=-=-=-=-` +}\end{quote} +\end{quote} + + +%___________________________________________________________________________ + +\hypertarget{examples}{} +\pdfbookmark[0]{Examples}{examples} +\section*{Examples} + +First example. Consider the following hierarchy: +\begin{quote} +\begin{verbatim}>>> O = object +>>> class F(O): pass +>>> class E(O): pass +>>> class D(O): pass +>>> class C(D,F): pass +>>> class B(D,E): pass +>>> class A(B,C): pass\end{verbatim} +\end{quote} + +In this case the inheritance graph can be drawn as +\begin{quote} +\begin{quote}{\ttfamily \raggedright \noindent +~~~~~~~~~~~~~~~~~~~~~~~~~~6~\\ +~~~~~~~~~~~~~~~~~~~~~~~~~-{}-{}-~\\ +Level~3~~~~~~~~~~~~~~~~~|~O~|~~~~~~~~~~~~~~~~~~(more~general)~\\ +~~~~~~~~~~~~~~~~~~~~~~/~~-{}-{}-~~{\textbackslash}~\\ +~~~~~~~~~~~~~~~~~~~~~/~~~~|~~~~{\textbackslash}~~~~~~~~~~~~~~~~~~~~~~|~\\ +~~~~~~~~~~~~~~~~~~~~/~~~~~|~~~~~{\textbackslash}~~~~~~~~~~~~~~~~~~~~~|~\\ +~~~~~~~~~~~~~~~~~~~/~~~~~~|~~~~~~{\textbackslash}~~~~~~~~~~~~~~~~~~~~|~\\ +~~~~~~~~~~~~~~~~~~-{}-{}-~~~~-{}-{}-~~~~-{}-{}-~~~~~~~~~~~~~~~~~~~|~\\ +Level~2~~~~~~~~3~|~D~|~4|~E~|~~|~F~|~5~~~~~~~~~~~~~~~~|~\\ +~~~~~~~~~~~~~~~~~~-{}-{}-~~~~-{}-{}-~~~~-{}-{}-~~~~~~~~~~~~~~~~~~~|~\\ +~~~~~~~~~~~~~~~~~~~{\textbackslash}~~{\textbackslash}~{\_}~/~~~~~~~|~~~~~~~~~~~~~~~~~~~|~\\ +~~~~~~~~~~~~~~~~~~~~{\textbackslash}~~~~/~{\textbackslash}~{\_}~~~~|~~~~~~~~~~~~~~~~~~~|~\\ +~~~~~~~~~~~~~~~~~~~~~{\textbackslash}~~/~~~~~~{\textbackslash}~~|~~~~~~~~~~~~~~~~~~~|~\\ +~~~~~~~~~~~~~~~~~~~~~~-{}-{}-~~~~~~-{}-{}-~~~~~~~~~~~~~~~~~~~~|~\\ +Level~1~~~~~~~~~~~~1~|~B~|~~~~|~C~|~2~~~~~~~~~~~~~~~~~|~\\ +~~~~~~~~~~~~~~~~~~~~~~-{}-{}-~~~~~~-{}-{}-~~~~~~~~~~~~~~~~~~~~|~\\ +~~~~~~~~~~~~~~~~~~~~~~~~{\textbackslash}~~~~~~/~~~~~~~~~~~~~~~~~~~~~~|~\\ +~~~~~~~~~~~~~~~~~~~~~~~~~{\textbackslash}~~~~/~~~~~~~~~~~~~~~~~~~~~~{\textbackslash}~/~\\ +~~~~~~~~~~~~~~~~~~~~~~~~~~~-{}-{}-~\\ +Level~0~~~~~~~~~~~~~~~~~0~|~A~|~~~~~~~~~~~~~~~~(more~specialized)~\\ +~~~~~~~~~~~~~~~~~~~~~~~~~~~-{}-{}- +}\end{quote} +\end{quote} + +The linearizations of O,D,E and F are trivial: +\begin{quote} +\begin{quote}{\ttfamily \raggedright \noindent +L{[}O]~=~O~\\ +L{[}D]~=~D~O~\\ +L{[}E]~=~E~O~\\ +L{[}F]~=~F~O +}\end{quote} +\end{quote} + +The linearization of B can be computed as +\begin{quote} +\begin{quote}{\ttfamily \raggedright \noindent +L{[}B]~=~B~+~merge(DO,~EO,~DE) +}\end{quote} +\end{quote} + +We see that D is a good head, therefore we take it and we are reduced to +compute \texttt{merge(O,EO,E)}. Now O is not a good head, since it is in the +tail of the sequence EO. In this case the rule says that we have to +skip to the next sequence. Then we see that E is a good head; we take +it and we are reduced to compute \texttt{merge(O,O)} which gives O. Therefore +\begin{quote} +\begin{quote}{\ttfamily \raggedright \noindent +L{[}B]~=~~B~D~E~O +}\end{quote} +\end{quote} + +Using the same procedure one finds: +\begin{quote} +\begin{quote}{\ttfamily \raggedright \noindent +L{[}C]~=~C~+~merge(DO,FO,DF)~\\ +~~~~~=~C~+~D~+~merge(O,FO,F)~\\ +~~~~~=~C~+~D~+~F~+~merge(O,O)~\\ +~~~~~=~C~D~F~O +}\end{quote} +\end{quote} + +Now we can compute: +\begin{quote} +\begin{quote}{\ttfamily \raggedright \noindent +L{[}A]~=~A~+~merge(BDEO,CDFO,BC)~\\ +~~~~~=~A~+~B~+~merge(DEO,CDFO,C)~\\ +~~~~~=~A~+~B~+~C~+~merge(DEO,DFO)~\\ +~~~~~=~A~+~B~+~C~+~D~+~merge(EO,FO)~\\ +~~~~~=~A~+~B~+~C~+~D~+~E~+~merge(O,FO)~\\ +~~~~~=~A~+~B~+~C~+~D~+~E~+~F~+~merge(O,O)~\\ +~~~~~=~A~B~C~D~E~F~O +}\end{quote} +\end{quote} + +In this example, the linearization is ordered in a pretty nice way +according to the inheritance level, in the sense that lower levels (i.e. +more specialized classes) have higher precedence (see the inheritance +graph). However, this is not the general case. + +I leave as an exercise for the reader to compute the linearization for +my second example: +\begin{quote} +\begin{verbatim}>>> O = object +>>> class F(O): pass +>>> class E(O): pass +>>> class D(O): pass +>>> class C(D,F): pass +>>> class B(E,D): pass +>>> class A(B,C): pass\end{verbatim} +\end{quote} + +The only difference with the previous example is the change B(D,E) -{}-{\textgreater} +B(E,D); however even such a little modification completely changes the +ordering of the hierarchy +\begin{quote} +\begin{quote}{\ttfamily \raggedright \noindent +~~~~~~~~~~~~~~~~~~~~~~~~~~~6~\\ +~~~~~~~~~~~~~~~~~~~~~~~~~~-{}-{}-~\\ +Level~3~~~~~~~~~~~~~~~~~~|~O~|~\\ +~~~~~~~~~~~~~~~~~~~~~~~/~~-{}-{}-~~{\textbackslash}~\\ +~~~~~~~~~~~~~~~~~~~~~~/~~~~|~~~~{\textbackslash}~\\ +~~~~~~~~~~~~~~~~~~~~~/~~~~~|~~~~~{\textbackslash}~\\ +~~~~~~~~~~~~~~~~~~~~/~~~~~~|~~~~~~{\textbackslash}~\\ +~~~~~~~~~~~~~~~~~~-{}-{}-~~~~~-{}-{}-~~~~-{}-{}-~\\ +Level~2~~~~~~~~2~|~E~|~4~|~D~|~~|~F~|~5~\\ +~~~~~~~~~~~~~~~~~~-{}-{}-~~~~~-{}-{}-~~~~-{}-{}-~\\ +~~~~~~~~~~~~~~~~~~~{\textbackslash}~~~~~~/~{\textbackslash}~~~~~/~\\ +~~~~~~~~~~~~~~~~~~~~{\textbackslash}~~~~/~~~{\textbackslash}~~~/~\\ +~~~~~~~~~~~~~~~~~~~~~{\textbackslash}~~/~~~~~{\textbackslash}~/~\\ +~~~~~~~~~~~~~~~~~~~~~~-{}-{}-~~~~~-{}-{}-~\\ +Level~1~~~~~~~~~~~~1~|~B~|~~~|~C~|~3~\\ +~~~~~~~~~~~~~~~~~~~~~~-{}-{}-~~~~~-{}-{}-~\\ +~~~~~~~~~~~~~~~~~~~~~~~{\textbackslash}~~~~~~~/~\\ +~~~~~~~~~~~~~~~~~~~~~~~~{\textbackslash}~~~~~/~\\ +~~~~~~~~~~~~~~~~~~~~~~~~~~-{}-{}-~\\ +Level~0~~~~~~~~~~~~~~~~0~|~A~|~\\ +~~~~~~~~~~~~~~~~~~~~~~~~~~-{}-{}- +}\end{quote} +\end{quote} + +Notice that the class E, which is in the second level of the hierarchy, +precedes the class C, which is in the first level of the hierarchy, i.e. +E is more specialized than C, even if it is in a higher level. + +A lazy programmer can obtain the MRO directly from Python 2.2, since in +this case it coincides with the Python 2.3 linearization. It is enough +to invoke the .mro() method of class A: +\begin{quote} +\begin{verbatim}>>> A.mro() +(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, +<class '__main__.C'>, <class '__main__.D'>, <class '__main__.F'>, +<type 'object'>)\end{verbatim} +\end{quote} + +Finally, let me consider the example discussed in the first section, +involving a serious order disagreement. In this case, it is +straightforward to compute the linearizations of O, X, Y, A and B: +\begin{quote} +\begin{quote}{\ttfamily \raggedright \noindent +L{[}O]~=~0~\\ +L{[}X]~=~X~O~\\ +L{[}Y]~=~Y~O~\\ +L{[}A]~=~A~X~Y~O~\\ +L{[}B]~=~B~Y~X~O +}\end{quote} +\end{quote} + +However, it is impossible to compute the linearization for a class C +that inherits from A and B: +\begin{quote} +\begin{quote}{\ttfamily \raggedright \noindent +L{[}C]~=~C~+~merge(AXYO,~BYXO,~AB)~\\ +~~~~~=~C~+~A~+~merge(XYO,~BYXO,~B)~\\ +~~~~~=~C~+~A~+~B~+~merge(XYO,~YXO) +}\end{quote} +\end{quote} + +At this point we cannot merge the lists XYO and YXO, since X is in the +tail of YXO whereas Y is in the tail of XYO: therefore there are no +good heads and the C3 algorithm stops. Python 2.3 raises an error and +refuses to create the class C. + + +%___________________________________________________________________________ +\hspace*{\fill}\hrulefill\hspace*{\fill} + +\begin{quote} +\begin{quote}{\ttfamily \raggedright \noindent +~~~~~~~~~~~~~~~~~~~~~~{\_}{\_}~\\ +~~~~({\textbackslash}~~~.-.~~~.-.~~~/{\_}")~\\ +~~~~~{\textbackslash}{\textbackslash}{\_}//{\textasciicircum}{\textbackslash}{\textbackslash}{\_}//{\textasciicircum}{\textbackslash}{\textbackslash}{\_}//~\\ +jgs~~~`"`~~~`"`~~~`"` +}\end{quote} +\end{quote} + + +%___________________________________________________________________________ + +\hypertarget{bad-method-resolution-orders}{} +\pdfbookmark[0]{Bad Method Resolution Orders}{bad-method-resolution-orders} +\section*{Bad Method Resolution Orders} + +A MRO is \emph{bad} when it breaks such fundamental properties as local +precedence ordering and monotonicity. In this section, I will show +that both the MRO for classic classes and the MRO for new style classes +in Python 2.2 are bad. + +It is easier to start with the local precedence ordering. Consider the +following example: +\begin{quote} +\begin{verbatim}>>> F=type('Food',(),{'remember2buy':'spam'}) +>>> E=type('Eggs',(F,),{'remember2buy':'eggs'}) +>>> G=type('GoodFood',(F,E),{}) # under Python 2.3 this is an error!\end{verbatim} +\end{quote} + +with inheritance diagram +\begin{quote} +\begin{quote}{\ttfamily \raggedright \noindent +~~~~~~~~~~~~~O~\\ +~~~~~~~~~~~~~|~\\ +(buy~spam)~~~F~\\ +~~~~~~~~~~~~~|~{\textbackslash}~\\ +~~~~~~~~~~~~~|~E~~~(buy~eggs)~\\ +~~~~~~~~~~~~~|~/~\\ +~~~~~~~~~~~~~G~\\ +~\\ +~~~~~~(buy~eggs~or~spam~?) +}\end{quote} +\end{quote} + +We see that class G inherits from F and E, with F \emph{before} E: therefore +we would expect the attribute \emph{G.remember2buy} to be inherited by +\emph{F.rembermer2buy} and not by \emph{E.remember2buy}: nevertheless Python 2.2 +gives +\begin{quote} +\begin{verbatim}>>> G.remember2buy +'eggs'\end{verbatim} +\end{quote} + +This is a breaking of local precedence ordering since the order in the +local precedence list, i.e. the list of the parents of G, is not +preserved in the Python 2.2 linearization of G: +\begin{quote} +\begin{quote}{\ttfamily \raggedright \noindent +L{[}G,P22]=~G~E~F~object~~~{\#}~F~*follows*~E +}\end{quote} +\end{quote} + +One could argue that the reason why F follows E in the Python 2.2 +linearization is that F is less specialized than E, since F is the +superclass of E; nevertheless the breaking of local precedence ordering +is quite non-intuitive and error prone. This is particularly true since +it is a different from old style classes: +\begin{quote} +\begin{verbatim}>>> class F: remember2buy='spam' +>>> class E(F): remember2buy='eggs' +>>> class G(F,E): pass +>>> G.remember2buy +'spam'\end{verbatim} +\end{quote} + +In this case the MRO is GFEF and the local precedence ordering is +preserved. + +As a general rule, hierarchies such as the previous one should be +avoided, since it is unclear if F should override E or viceversa. +Python 2.3 solves the ambiguity by raising an exception in the creation +of class G, effectively stopping the programmer from generating +ambiguous hierarchies. The reason for that is that the C3 algorithm +fails when the merge +\begin{quote} +\begin{quote}{\ttfamily \raggedright \noindent +merge(FO,EFO,FE) +}\end{quote} +\end{quote} + +cannot be computed, because F is in the tail of EFO and E is in the tail +of FE. + +The real solution is to design a non-ambiguous hierarchy, i.e. to derive +G from E and F (the more specific first) and not from F and E; in this +case the MRO is GEF without any doubt. +\begin{quote} +\begin{quote}{\ttfamily \raggedright \noindent +~~~~~~~~~~~O~\\ +~~~~~~~~~~~|~\\ +~~~~~~~~~~~F~(spam)~\\ +~~~~~~~~~/~|~\\ +(eggs)~~~E~|~\\ +~~~~~~~~~{\textbackslash}~|~\\ +~~~~~~~~~~~G~\\ +~~~~~~~~~~~~~(eggs,~no~doubt) +}\end{quote} +\end{quote} + +Python 2.3 forces the programmer to write good hierarchies (or, at +least, less error-prone ones). + +On a related note, let me point out that the Python 2.3 algorithm is +smart enough to recognize obvious mistakes, as the duplication of +classes in the list of parents: +\begin{quote} +\begin{verbatim}>>> class A(object): pass +>>> class C(A,A): pass # error +Traceback (most recent call last): + File "<stdin>", line 1, in ? +TypeError: duplicate base class A\end{verbatim} +\end{quote} + +Python 2.2 (both for classic classes and new style classes) in this +situation, would not raise any exception. + +Finally, I would like to point out two lessons we have learned from this +example: +\newcounter{listcnt2} +\begin{list}{\arabic{listcnt2}.} +{ +\usecounter{listcnt2} +\setlength{\rightmargin}{\leftmargin} +} +\item {} +despite the name, the MRO determines the resolution order of +attributes, not only of methods; + +\item {} +the default food for Pythonistas is spam ! (but you already knew +that ;-) + +\end{list} + + +%___________________________________________________________________________ +\hspace*{\fill}\hrulefill\hspace*{\fill} + +\begin{quote} +\begin{quote}{\ttfamily \raggedright \noindent +~~~~~~~~~~~~~~~~~~~~~~{\_}{\_}~\\ +~~~~({\textbackslash}~~~.-.~~~.-.~~~/{\_}")~\\ +~~~~~{\textbackslash}{\textbackslash}{\_}//{\textasciicircum}{\textbackslash}{\textbackslash}{\_}//{\textasciicircum}{\textbackslash}{\textbackslash}{\_}//~\\ +jgs~~~`"`~~~`"`~~~`"` +}\end{quote} +\end{quote} + +Having discussed the issue of local precedence ordering, let me now +consider the issue of monotonicity. My goal is to show that neither the +MRO for classic classes nor that for Python 2.2 new style classes is +monotonic. + +To prove that the MRO for classic classes is non-monotonic is rather +trivial, it is enough to look at the diamond diagram: +\begin{quote} +\begin{quote}{\ttfamily \raggedright \noindent +~~~C~\\ +~~/~{\textbackslash}~\\ +~/~~~{\textbackslash}~\\ +A~~~~~B~\\ +~{\textbackslash}~~~/~\\ +~~{\textbackslash}~/~\\ +~~~D +}\end{quote} +\end{quote} + +One easily discerns the inconsistency: +\begin{quote} +\begin{quote}{\ttfamily \raggedright \noindent +L{[}B,P21]~=~B~C~~~~~~~~{\#}~B~precedes~C~:~B's~methods~win~\\ +L{[}D,P21]~=~D~A~C~B~C~~{\#}~B~follows~C~~:~C's~methods~win! +}\end{quote} +\end{quote} + +On the other hand, there are no problems with the Python 2.2 and 2.3 +MROs, they give both +\begin{quote} +\begin{quote}{\ttfamily \raggedright \noindent +L{[}D]~=~D~A~B~C +}\end{quote} +\end{quote} + +Guido points out in his essay [\hyperlink{id6}{3}] that the classic MRO is not so bad in +practice, since one can typically avoids diamonds for classic classes. +But all new style classes inherit from \texttt{object}, therefore diamonds are +unavoidable and inconsistencies shows up in every multiple inheritance +graph. + +The MRO of Python 2.2 makes breaking monotonicity difficult, but not +impossible. The following example, originally provided by Samuele +Pedroni, shows that the MRO of Python 2.2 is non-monotonic: +\begin{quote} +\begin{verbatim}>>> class A(object): pass +>>> class B(object): pass +>>> class C(object): pass +>>> class D(object): pass +>>> class E(object): pass +>>> class K1(A,B,C): pass +>>> class K2(D,B,E): pass +>>> class K3(D,A): pass +>>> class Z(K1,K2,K3): pass\end{verbatim} +\end{quote} + +Here are the linearizations according to the C3 MRO (the reader should +verify these linearizations as an exercise and draw the inheritance +diagram ;-) +\begin{quote} +\begin{quote}{\ttfamily \raggedright \noindent +L{[}A]~=~A~O~\\ +L{[}B]~=~B~O~\\ +L{[}C]~=~C~O~\\ +L{[}D]~=~D~O~\\ +L{[}E]~=~E~O~\\ +L{[}K1]=~K1~A~B~C~O~\\ +L{[}K2]=~K2~D~B~E~O~\\ +L{[}K3]=~K3~D~A~O~\\ +L{[}Z]~=~Z~K1~K2~K3~D~A~B~C~E~O +}\end{quote} +\end{quote} + +Python 2.2 gives exactly the same linearizations for A, B, C, D, E, K1, +K2 and K3, but a different linearization for Z: +\begin{quote} +\begin{quote}{\ttfamily \raggedright \noindent +L{[}Z,P22]~=~Z~K1~K3~A~K2~D~B~C~E~O +}\end{quote} +\end{quote} + +It is clear that this linearization is \emph{wrong}, since A comes before D +whereas in the linearization of K3 A comes \emph{after} D. In other words, in +K3 methods derived by D override methods derived by A, but in Z, which +still is a subclass of K3, methods derived by A override methods derived +by D! This is a violation of monotonicity. Moreover, the Python 2.2 +linearization of Z is also inconsistent with local precedence ordering, +since the local precedence list of the class Z is {[}K1, K2, K3] (K2 +precedes K3), whereas in the linearization of Z K2 \emph{follows} K3. These +problems explain why the 2.2 rule has been dismissed in favor of the C3 +rule. + + +%___________________________________________________________________________ +\hspace*{\fill}\hrulefill\hspace*{\fill} + +\begin{quote} +\begin{quote}{\ttfamily \raggedright \noindent +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{\_}{\_}~\\ +~~~({\textbackslash}~~~.-.~~~.-.~~~.-.~~~.-.~~~.-.~~~.-.~~~.-.~~~.-.~~~/{\_}")~\\ +~~~~{\textbackslash}{\textbackslash}{\_}//{\textasciicircum}{\textbackslash}{\textbackslash}{\_}//{\textasciicircum}{\textbackslash}{\textbackslash}{\_}//{\textasciicircum}{\textbackslash}{\textbackslash}{\_}//{\textasciicircum}{\textbackslash}{\textbackslash}{\_}//{\textasciicircum}{\textbackslash}{\textbackslash}{\_}//{\textasciicircum}{\textbackslash}{\textbackslash}{\_}//{\textasciicircum}{\textbackslash}{\textbackslash}{\_}//{\textasciicircum}{\textbackslash}{\textbackslash}{\_}//~\\ +jgs~~`"`~~~`"`~~~`"`~~~`"`~~~`"`~~~`"`~~~`"`~~~`"`~~~`"` +}\end{quote} +\end{quote} + + +%___________________________________________________________________________ + +\hypertarget{the-end}{} +\pdfbookmark[0]{The end}{the-end} +\section*{The end} + +This section is for the impatient reader, who skipped all the previous +sections and jumped immediately to the end. This section is for the +lazy programmer too, who didn't want to exercise her/his brain. +Finally, it is for the programmer with some hubris, otherwise s/he would +not be reading a paper on the C3 method resolution order in multiple +inheritance hierarchies ;-) These three virtues taken all together (and +\emph{not} separately) deserve a prize: the prize is a short Python 2.2 +script that allows you to compute the 2.3 MRO without risk to your +brain. Simply change the last line to play with the various examples I +have discussed in this paper. +\begin{quote} +\begin{quote}{\ttfamily \raggedright \noindent +{\#}<mro.py>~\\ +~\\ +"{}"{}"C3~algorithm~by~Samuele~Pedroni~(with~readability~enhanced~by~me)."{}"{}"~\\ +~\\ +class~{\_}{\_}metaclass{\_}{\_}(type):~\\ +~~~~"All~classes~are~metamagically~modified~to~be~nicely~printed"~\\ +~~~~{\_}{\_}repr{\_}{\_}~=~lambda~cls:~cls.{\_}{\_}name{\_}{\_}~\\ +~\\ +class~ex{\_}2:~\\ +~~~~"Serious~order~disagreement"~{\#}From~Guido~\\ +~~~~class~O:~pass~\\ +~~~~class~X(O):~pass~\\ +~~~~class~Y(O):~pass~\\ +~~~~class~A(X,Y):~pass~\\ +~~~~class~B(Y,X):~pass~\\ +~~~~try:~\\ +~~~~~~~~class~Z(A,B):~pass~{\#}creates~Z(A,B)~in~Python~2.2~\\ +~~~~except~TypeError:~\\ +~~~~~~~~pass~{\#}~Z(A,B)~cannot~be~created~in~Python~2.3~\\ +~\\ +class~ex{\_}5:~\\ +~~~~"My~first~example"~\\ +~~~~class~O:~pass~\\ +~~~~class~F(O):~pass~\\ +~~~~class~E(O):~pass~\\ +~~~~class~D(O):~pass~\\ +~~~~class~C(D,F):~pass~\\ +~~~~class~B(D,E):~pass~\\ +~~~~class~A(B,C):~pass~\\ +~\\ +class~ex{\_}6:~\\ +~~~~"My~second~example"~\\ +~~~~class~O:~pass~\\ +~~~~class~F(O):~pass~\\ +~~~~class~E(O):~pass~\\ +~~~~class~D(O):~pass~\\ +~~~~class~C(D,F):~pass~\\ +~~~~class~B(E,D):~pass~\\ +~~~~class~A(B,C):~pass~\\ +~\\ +class~ex{\_}9:~\\ +~~~~"Difference~between~Python~2.2~MRO~and~C3"~{\#}From~Samuele~\\ +~~~~class~O:~pass~\\ +~~~~class~A(O):~pass~\\ +~~~~class~B(O):~pass~\\ +~~~~class~C(O):~pass~\\ +~~~~class~D(O):~pass~\\ +~~~~class~E(O):~pass~\\ +~~~~class~K1(A,B,C):~pass~\\ +~~~~class~K2(D,B,E):~pass~\\ +~~~~class~K3(D,A):~pass~\\ +~~~~class~Z(K1,K2,K3):~pass~\\ +~\\ +def~merge(seqs):~\\ +~~~~print~'{\textbackslash}n{\textbackslash}nCPL{[}{\%}s]={\%}s'~{\%}~(seqs{[}0]{[}0],seqs),~\\ +~~~~res~=~{[}];~i=0~\\ +~~~~while~1:~\\ +~~~~~~nonemptyseqs={[}seq~for~seq~in~seqs~if~seq]~\\ +~~~~~~if~not~nonemptyseqs:~return~res~\\ +~~~~~~i+=1;~print~'{\textbackslash}n',i,'round:~candidates...',~\\ +~~~~~~for~seq~in~nonemptyseqs:~{\#}~find~merge~candidates~among~seq~heads~\\ +~~~~~~~~~~cand~=~seq{[}0];~print~'~',cand,~\\ +~~~~~~~~~~nothead={[}s~for~s~in~nonemptyseqs~if~cand~in~s{[}1:]]~\\ +~~~~~~~~~~if~nothead:~cand=None~{\#}reject~candidate~\\ +~~~~~~~~~~else:~break~\\ +~~~~~~if~not~cand:~raise~"Inconsistent~hierarchy"~\\ +~~~~~~res.append(cand)~\\ +~~~~~~for~seq~in~nonemptyseqs:~{\#}~remove~cand~\\ +~~~~~~~~~~if~seq{[}0]~==~cand:~del~seq{[}0]~\\ +~\\ +def~mro(C):~\\ +~~~~"Compute~the~class~precedence~list~(mro)~according~to~C3"~\\ +~~~~return~merge({[}{[}C]]+map(mro,C.{\_}{\_}bases{\_}{\_})+{[}list(C.{\_}{\_}bases{\_}{\_})])~\\ +~\\ +def~print{\_}mro(C):~\\ +~~~~print~'{\textbackslash}nMRO{[}{\%}s]={\%}s'~{\%}~(C,mro(C))~\\ +~~~~print~'{\textbackslash}nP22~MRO{[}{\%}s]={\%}s'~{\%}~(C,C.mro())~\\ +~\\ +print{\_}mro(ex{\_}9.Z)~\\ +~\\ +{\#}</mro.py> +}\end{quote} +\end{quote} + +That's all folks, +\begin{quote} + +enjoy ! +\end{quote} + + +%___________________________________________________________________________ +\hspace*{\fill}\hrulefill\hspace*{\fill} + +\begin{quote} +\begin{quote}{\ttfamily \raggedright \noindent +~~~~{\_}{\_}~\\ +~~~("{\_}{\textbackslash}~~~.-.~~~.-.~~~.-.~~~.-.~~~.-.~~~.-.~~~.-.~~~.-.~~~/)~\\ +~~~~~~{\textbackslash}{\textbackslash}{\_}//{\textasciicircum}{\textbackslash}{\textbackslash}{\_}//{\textasciicircum}{\textbackslash}{\textbackslash}{\_}//{\textasciicircum}{\textbackslash}{\textbackslash}{\_}//{\textasciicircum}{\textbackslash}{\textbackslash}{\_}//{\textasciicircum}{\textbackslash}{\textbackslash}{\_}//{\textasciicircum}{\textbackslash}{\textbackslash}{\_}//{\textasciicircum}{\textbackslash}{\textbackslash}{\_}//{\textasciicircum}{\textbackslash}{\textbackslash}{\_}//~\\ +jgs~~~~`"`~~~`"`~~~`"`~~~`"`~~~`"`~~~`"`~~~`"`~~~`"`~~~`"` +}\end{quote} +\end{quote} + + +%___________________________________________________________________________ + +\hypertarget{resources}{} +\pdfbookmark[0]{Resources}{resources} +\section*{Resources} +\begin{figure}[b]\hypertarget{id4}[1] +The thread on python-dev started by Samuele Pedroni: +\href{http://mail.python.org/pipermail/python-dev/2002-October/029035.html}{http://mail.python.org/pipermail/python-dev/2002-October/029035.html} +\end{figure} +\begin{figure}[b]\hypertarget{id5}[2] +The paper \emph{A Monotonic Superclass Linearization for Dylan}: +\href{http://www.webcom.com/haahr/dylan/linearization-oopsla96.html}{http://www.webcom.com/haahr/dylan/linearization-oopsla96.html} +\end{figure} +\begin{figure}[b]\hypertarget{id6}[3] +Guido van Rossum's essay, \emph{Unifying types and classes in Python 2.2}: +\href{http://www.python.org/2.2.2/descrintro.html}{http://www.python.org/2.2.2/descrintro.html} +\end{figure} + +\end{document} + diff --git a/pypers/oxford/mro.txt b/pypers/oxford/mro.txt new file mode 100755 index 0000000..9fb59ff --- /dev/null +++ b/pypers/oxford/mro.txt @@ -0,0 +1,787 @@ +The Python 2.3 Method Resolution Order
+======================================
+
+:Version: 1.4
+:Author: Michele Simionato
+:E-mail: michelesimionato@libero.it
+:Address: Department of Physics and Astronomy
+ 210 Allen Hall Pittsburgh PA 15260 U.S.A.
+:Home-page: http://www.phyast.pitt.edu/~micheles/
+
+:Abstract:
+
+ *This document is intended for Python programmers who want to
+ understand the C3 Method Resolution Order used in Python 2.3.
+ Although it is not intended for newbies, it is quite pedagogical with
+ many worked out examples. I am not aware of other publicly available
+ documents with the same scope, therefore it should be useful.*
+
+Disclaimer:
+
+ I donate this document to the Python Software Foundation, under the
+ Python 2.3 license. As usual in these circumstances, I warn the
+ reader that what follows *should* be correct, but I don't give any
+ warranty. Use it at your own risk and peril!
+
+Acknowledgments:
+
+ All the people of the Python mailing list who sent me their support.
+ Paul Foley who pointed out various imprecisions and made me to add the
+ part on local precedence ordering. David Goodger for help with the
+ formatting in reStructuredText. David Mertz for help with the editing.
+ Joan G. Stark for the pythonic pictures. Finally, Guido van Rossum who
+ enthusiastically added this document to the official Python 2.3 home-page.
+
+----
+
+ ::
+
+
+
+ .-=-. .--.
+ __ .' '. / " )
+ _ .' '. / .-. \ / .-'\
+ ( \ / .-. \ / / \ \ / / ^
+ \ `-` / \ `-' / \ `-` /
+ jgs`-.-` '.____.' `.____.'
+
+
+The beginning
+-------------
+
+ *Felix qui potuit rerum cognoscere causas* -- Virgilius
+
+Everything started with a post by Samuele Pedroni to the Python
+development mailing list [#]_. In his post, Samuele showed that the
+Python 2.2 method resolution order is not monotonic and he proposed to
+replace it with the C3 method resolution order. Guido agreed with his
+arguments and therefore now Python 2.3 uses C3. The C3 method itself
+has nothing to do with Python, since it was invented by people working
+on Dylan and it is described in a paper intended for lispers [#]_. The
+present paper gives a (hopefully) readable discussion of the C3
+algorithm for Pythonistas who want to understand the reasons for the
+change.
+
+First of all, let me point out that what I am going to say only applies
+to the *new style classes* introduced in Python 2.2: *classic classes*
+maintain their old method resolution order, depth first and then left to
+right. Therefore, there is no breaking of old code for classic classes;
+and even if in principle there could be breaking of code for Python 2.2
+new style classes, in practice the cases in which the C3 resolution
+order differs from the Python 2.2 method resolution order are so rare
+that no real breaking of code is expected. Therefore:
+
+ *Don't be scared!*
+
+Moreover, unless you make strong use of multiple inheritance and you
+have non-trivial hierarchies, you don't need to understand the C3
+algorithm, and you can easily skip this paper. On the other hand, if
+you really want to know how multiple inheritance works, then this paper
+is for you. The good news is that things are not as complicated as you
+might expect.
+
+Let me begin with some basic definitions.
+
+1) Given a class C in a complicated multiple inheritance hierarchy, it
+ is a non-trivial task to specify the order in which methods are
+ overridden, i.e. to specify the order of the ancestors of C.
+
+2) The list of the ancestors of a class C, including the class itself,
+ ordered from the nearest ancestor to the furthest, is called the
+ class precedence list or the *linearization* of C.
+
+3) The *Method Resolution Order* (MRO) is the set of rules that
+ construct the linearization. In the Python literature, the idiom
+ "the MRO of C" is also used as a synonymous for the linearization of
+ the class C.
+
+4) For instance, in the case of single inheritance hierarchy, if C is a
+ subclass of C1, and C1 is a subclass of C2, then the linearization of
+ C is simply the list [C, C1 , C2]. However, with multiple
+ inheritance hierarchies, the construction of the linearization is
+ more cumbersome, since it is more difficult to construct a
+ linearization that respects *local precedence ordering* and
+ *monotonicity*.
+
+5) I will discuss the local precedence ordering later, but I can give
+ the definition of monotonicity here. A MRO is monotonic when the
+ following is true: *if C1 precedes C2 in the linearization of C,
+ then C1 precedes C2 in the linearization of any subclass of C*.
+ Otherwise, the innocuous operation of deriving a new class could
+ change the resolution order of methods, potentially introducing very
+ subtle bugs. Examples where this happens will be shown later.
+
+6) Not all classes admit a linearization. There are cases, in
+ complicated hierarchies, where it is not possible to derive a class
+ such that its linearization respects all the desired properties.
+
+Here I give an example of this situation. Consider the hierarchy
+
+ >>> O = object
+ >>> class X(O): pass
+ >>> class Y(O): pass
+ >>> class A(X,Y): pass
+ >>> class B(Y,X): pass
+
+which can be represented with the following inheritance graph, where I
+have denoted with O the ``object`` class, which is the beginning of any
+hierarchy for new style classes:
+
+ ::
+
+ -----------
+ | |
+ | O |
+ | / \ |
+ - X Y /
+ | / | /
+ | / |/
+ A B
+ \ /
+ ?
+
+In this case, it is not possible to derive a new class C from A and B,
+since X precedes Y in A, but Y precedes X in B, therefore the method
+resolution order would be ambiguous in C.
+
+Python 2.3 raises an exception in this situation (TypeError: MRO
+conflict among bases Y, X) forbidding the naive programmer from creating
+ambiguous hierarchies. Python 2.2 instead does not raise an exception,
+but chooses an *ad hoc* ordering (CABXYO in this case).
+
+----
+
+ ::
+
+ _ .-=-. .-==-.
+ { } __ .' O o '. / -<' )
+ { } .' O'. / o .-. O \ / .--v`
+ { } / .-. o\ /O / \ o\ /O /
+ \ `-` / \ O`-'o / \ O`-`o /
+ jgs `-.-` '.____.' `.____.'
+
+
+The C3 Method Resolution Order
+------------------------------
+
+Let me introduce a few simple notations which will be useful for the
+following discussion. I will use the shortcut notation
+
+ C1 C2 ... CN
+
+to indicate the list of classes [C1, C2, ... , CN].
+
+The *head* of the list is its first element:
+
+ head = C1
+
+whereas the *tail* is the rest of the list:
+
+ tail = C2 ... CN.
+
+I shall also use the notation
+
+ C + (C1 C2 ... CN) = C C1 C2 ... CN
+
+to denote the sum of the lists [C] + [C1, C2, ... ,CN].
+
+Now I can explain how the MRO works in Python 2.3.
+
+Consider a class C in a multiple inheritance hierarchy, with C
+inheriting from the base classes B1, B2, ... , BN. We want to
+compute the linearization L[C] of the class C. The rule is the
+following:
+
+ *the linearization of C is the sum of C plus the merge of the
+ linearizations of the parents and the list of the parents.*
+
+In symbolic notation:
+
+ L[C(B1 ... BN)] = C + merge(L[B1] ... L[BN], B1 ... BN)
+
+In particular, if C is the ``object`` class, which has no parents, the
+linearization is trivial:
+
+ L[object] = object.
+
+However, in general one has to compute the merge according to the following
+prescription:
+
+ *take the head of the first list, i.e L[B1][0]; if this head is not in
+ the tail of any of the other lists, then add it to the linearization
+ of C and remove it from the lists in the merge, otherwise look at the
+ head of the next list and take it, if it is a good head. Then repeat
+ the operation until all the class are removed or it is impossible to
+ find good heads. In this case, it is impossible to construct the
+ merge, Python 2.3 will refuse to create the class C and will raise an
+ exception.*
+
+This prescription ensures that the merge operation *preserves* the
+ordering, if the ordering can be preserved. On the other hand, if the
+order cannot be preserved (as in the example of serious order
+disagreement discussed above) then the merge cannot be computed.
+
+The computation of the merge is trivial if C has only one parent
+(single inheritance); in this case
+
+ L[C(B)] = C + merge(L[B],B) = C + L[B]
+
+However, in the case of multiple inheritance things are more cumbersome
+and I don't expect you can understand the rule without a couple of
+examples ;-)
+
+----
+
+ ::
+
+ .-'-.
+ /' `\
+ /' _.-.-._ `\
+ | (|) (|) |
+ | \__"__/ |
+ \ |v.v| /
+ \ | | | /
+ `\ |=^-| /'
+ `|=-=|'
+ | - |
+ |= |
+ |-=-|
+ _.-=-=|= -|=-=-._
+ ( |___| )
+ ( `-=-=-=-=-=-=-=-` )
+ (`-=-=-=-=-=-=-=-=-`)
+ (`-=-=-=-=-=-=-=-=-`)
+ (`-=-=-=-=-=-=-=-`)
+ (`-=-=-=-=-=-=-`)
+ jgs `-=-=-=-=-=-=-`
+
+
+Examples
+--------
+
+First example. Consider the following hierarchy:
+
+ >>> O = object
+ >>> class F(O): pass
+ >>> class E(O): pass
+ >>> class D(O): pass
+ >>> class C(D,F): pass
+ >>> class B(D,E): pass
+ >>> class A(B,C): pass
+
+In this case the inheritance graph can be drawn as
+
+ ::
+
+ 6
+ ---
+ Level 3 | O | (more general)
+ / --- \
+ / | \ |
+ / | \ |
+ / | \ |
+ --- --- --- |
+ Level 2 3 | D | 4| E | | F | 5 |
+ --- --- --- |
+ \ \ _ / | |
+ \ / \ _ | |
+ \ / \ | |
+ --- --- |
+ Level 1 1 | B | | C | 2 |
+ --- --- |
+ \ / |
+ \ / \ /
+ ---
+ Level 0 0 | A | (more specialized)
+ ---
+
+
+The linearizations of O,D,E and F are trivial:
+
+ ::
+
+ L[O] = O
+ L[D] = D O
+ L[E] = E O
+ L[F] = F O
+
+The linearization of B can be computed as
+
+ ::
+
+ L[B] = B + merge(DO, EO, DE)
+
+We see that D is a good head, therefore we take it and we are reduced to
+compute ``merge(O,EO,E)``. Now O is not a good head, since it is in the
+tail of the sequence EO. In this case the rule says that we have to
+skip to the next sequence. Then we see that E is a good head; we take
+it and we are reduced to compute ``merge(O,O)`` which gives O. Therefore
+
+ ::
+
+ L[B] = B D E O
+
+Using the same procedure one finds:
+
+ ::
+
+ L[C] = C + merge(DO,FO,DF)
+ = C + D + merge(O,FO,F)
+ = C + D + F + merge(O,O)
+ = C D F O
+
+Now we can compute:
+
+ ::
+
+ L[A] = A + merge(BDEO,CDFO,BC)
+ = A + B + merge(DEO,CDFO,C)
+ = A + B + C + merge(DEO,DFO)
+ = A + B + C + D + merge(EO,FO)
+ = A + B + C + D + E + merge(O,FO)
+ = A + B + C + D + E + F + merge(O,O)
+ = A B C D E F O
+
+In this example, the linearization is ordered in a pretty nice way
+according to the inheritance level, in the sense that lower levels (i.e.
+more specialized classes) have higher precedence (see the inheritance
+graph). However, this is not the general case.
+
+I leave as an exercise for the reader to compute the linearization for
+my second example:
+
+ >>> O = object
+ >>> class F(O): pass
+ >>> class E(O): pass
+ >>> class D(O): pass
+ >>> class C(D,F): pass
+ >>> class B(E,D): pass
+ >>> class A(B,C): pass
+
+The only difference with the previous example is the change B(D,E) -->
+B(E,D); however even such a little modification completely changes the
+ordering of the hierarchy
+
+ ::
+
+ 6
+ ---
+ Level 3 | O |
+ / --- \
+ / | \
+ / | \
+ / | \
+ --- --- ---
+ Level 2 2 | E | 4 | D | | F | 5
+ --- --- ---
+ \ / \ /
+ \ / \ /
+ \ / \ /
+ --- ---
+ Level 1 1 | B | | C | 3
+ --- ---
+ \ /
+ \ /
+ ---
+ Level 0 0 | A |
+ ---
+
+
+Notice that the class E, which is in the second level of the hierarchy,
+precedes the class C, which is in the first level of the hierarchy, i.e.
+E is more specialized than C, even if it is in a higher level.
+
+A lazy programmer can obtain the MRO directly from Python 2.2, since in
+this case it coincides with the Python 2.3 linearization. It is enough
+to invoke the .mro() method of class A:
+
+ >>> A.mro()
+ (<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>,
+ <class '__main__.C'>, <class '__main__.D'>, <class '__main__.F'>,
+ <type 'object'>)
+
+Finally, let me consider the example discussed in the first section,
+involving a serious order disagreement. In this case, it is
+straightforward to compute the linearizations of O, X, Y, A and B:
+
+ ::
+
+ L[O] = 0
+ L[X] = X O
+ L[Y] = Y O
+ L[A] = A X Y O
+ L[B] = B Y X O
+
+However, it is impossible to compute the linearization for a class C
+that inherits from A and B:
+
+ ::
+
+ L[C] = C + merge(AXYO, BYXO, AB)
+ = C + A + merge(XYO, BYXO, B)
+ = C + A + B + merge(XYO, YXO)
+
+At this point we cannot merge the lists XYO and YXO, since X is in the
+tail of YXO whereas Y is in the tail of XYO: therefore there are no
+good heads and the C3 algorithm stops. Python 2.3 raises an error and
+refuses to create the class C.
+
+----
+
+ ::
+
+ __
+ (\ .-. .-. /_")
+ \\_//^\\_//^\\_//
+ jgs `"` `"` `"`
+
+
+Bad Method Resolution Orders
+----------------------------
+
+A MRO is *bad* when it breaks such fundamental properties as local
+precedence ordering and monotonicity. In this section, I will show
+that both the MRO for classic classes and the MRO for new style classes
+in Python 2.2 are bad.
+
+It is easier to start with the local precedence ordering. Consider the
+following example:
+
+ >>> F=type('Food',(),{'remember2buy':'spam'})
+ >>> E=type('Eggs',(F,),{'remember2buy':'eggs'})
+ >>> G=type('GoodFood',(F,E),{}) # under Python 2.3 this is an error!
+
+with inheritance diagram
+
+ ::
+
+ O
+ |
+ (buy spam) F
+ | \
+ | E (buy eggs)
+ | /
+ G
+
+ (buy eggs or spam ?)
+
+
+We see that class G inherits from F and E, with F *before* E: therefore
+we would expect the attribute *G.remember2buy* to be inherited by
+*F.rembermer2buy* and not by *E.remember2buy*: nevertheless Python 2.2
+gives
+
+ >>> G.remember2buy
+ 'eggs'
+
+This is a breaking of local precedence ordering since the order in the
+local precedence list, i.e. the list of the parents of G, is not
+preserved in the Python 2.2 linearization of G:
+
+ ::
+
+ L[G,P22]= G E F object # F *follows* E
+
+One could argue that the reason why F follows E in the Python 2.2
+linearization is that F is less specialized than E, since F is the
+superclass of E; nevertheless the breaking of local precedence ordering
+is quite non-intuitive and error prone. This is particularly true since
+it is a different from old style classes:
+
+ >>> class F: remember2buy='spam'
+ >>> class E(F): remember2buy='eggs'
+ >>> class G(F,E): pass
+ >>> G.remember2buy
+ 'spam'
+
+In this case the MRO is GFEF and the local precedence ordering is
+preserved.
+
+As a general rule, hierarchies such as the previous one should be
+avoided, since it is unclear if F should override E or viceversa.
+Python 2.3 solves the ambiguity by raising an exception in the creation
+of class G, effectively stopping the programmer from generating
+ambiguous hierarchies. The reason for that is that the C3 algorithm
+fails when the merge
+
+ ::
+
+ merge(FO,EFO,FE)
+
+cannot be computed, because F is in the tail of EFO and E is in the tail
+of FE.
+
+The real solution is to design a non-ambiguous hierarchy, i.e. to derive
+G from E and F (the more specific first) and not from F and E; in this
+case the MRO is GEF without any doubt.
+
+ ::
+
+ O
+ |
+ F (spam)
+ / |
+ (eggs) E |
+ \ |
+ G
+ (eggs, no doubt)
+
+
+Python 2.3 forces the programmer to write good hierarchies (or, at
+least, less error-prone ones).
+
+On a related note, let me point out that the Python 2.3 algorithm is
+smart enough to recognize obvious mistakes, as the duplication of
+classes in the list of parents:
+
+ >>> class A(object): pass
+ >>> class C(A,A): pass # error
+ Traceback (most recent call last):
+ File "<stdin>", line 1, in ?
+ TypeError: duplicate base class A
+
+Python 2.2 (both for classic classes and new style classes) in this
+situation, would not raise any exception.
+
+Finally, I would like to point out two lessons we have learned from this
+example:
+
+1. despite the name, the MRO determines the resolution order of
+ attributes, not only of methods;
+
+2. the default food for Pythonistas is spam ! (but you already knew
+ that ;-)
+
+----
+
+ ::
+
+ __
+ (\ .-. .-. /_")
+ \\_//^\\_//^\\_//
+ jgs `"` `"` `"`
+
+
+Having discussed the issue of local precedence ordering, let me now
+consider the issue of monotonicity. My goal is to show that neither the
+MRO for classic classes nor that for Python 2.2 new style classes is
+monotonic.
+
+To prove that the MRO for classic classes is non-monotonic is rather
+trivial, it is enough to look at the diamond diagram:
+
+ ::
+
+
+ C
+ / \
+ / \
+ A B
+ \ /
+ \ /
+ D
+
+One easily discerns the inconsistency:
+
+ ::
+
+ L[B,P21] = B C # B precedes C : B's methods win
+ L[D,P21] = D A C B C # B follows C : C's methods win!
+
+On the other hand, there are no problems with the Python 2.2 and 2.3
+MROs, they give both
+
+ ::
+
+ L[D] = D A B C
+
+Guido points out in his essay [#]_ that the classic MRO is not so bad in
+practice, since one can typically avoids diamonds for classic classes.
+But all new style classes inherit from ``object``, therefore diamonds are
+unavoidable and inconsistencies shows up in every multiple inheritance
+graph.
+
+The MRO of Python 2.2 makes breaking monotonicity difficult, but not
+impossible. The following example, originally provided by Samuele
+Pedroni, shows that the MRO of Python 2.2 is non-monotonic:
+
+ >>> class A(object): pass
+ >>> class B(object): pass
+ >>> class C(object): pass
+ >>> class D(object): pass
+ >>> class E(object): pass
+ >>> class K1(A,B,C): pass
+ >>> class K2(D,B,E): pass
+ >>> class K3(D,A): pass
+ >>> class Z(K1,K2,K3): pass
+
+Here are the linearizations according to the C3 MRO (the reader should
+verify these linearizations as an exercise and draw the inheritance
+diagram ;-)
+
+ ::
+
+ L[A] = A O
+ L[B] = B O
+ L[C] = C O
+ L[D] = D O
+ L[E] = E O
+ L[K1]= K1 A B C O
+ L[K2]= K2 D B E O
+ L[K3]= K3 D A O
+ L[Z] = Z K1 K2 K3 D A B C E O
+
+Python 2.2 gives exactly the same linearizations for A, B, C, D, E, K1,
+K2 and K3, but a different linearization for Z:
+
+ ::
+
+ L[Z,P22] = Z K1 K3 A K2 D B C E O
+
+It is clear that this linearization is *wrong*, since A comes before D
+whereas in the linearization of K3 A comes *after* D. In other words, in
+K3 methods derived by D override methods derived by A, but in Z, which
+still is a subclass of K3, methods derived by A override methods derived
+by D! This is a violation of monotonicity. Moreover, the Python 2.2
+linearization of Z is also inconsistent with local precedence ordering,
+since the local precedence list of the class Z is [K1, K2, K3] (K2
+precedes K3), whereas in the linearization of Z K2 *follows* K3. These
+problems explain why the 2.2 rule has been dismissed in favor of the C3
+rule.
+
+----
+
+ ::
+
+ __
+ (\ .-. .-. .-. .-. .-. .-. .-. .-. /_")
+ \\_//^\\_//^\\_//^\\_//^\\_//^\\_//^\\_//^\\_//^\\_//
+ jgs `"` `"` `"` `"` `"` `"` `"` `"` `"`
+
+
+
+The end
+-------
+
+This section is for the impatient reader, who skipped all the previous
+sections and jumped immediately to the end. This section is for the
+lazy programmer too, who didn't want to exercise her/his brain.
+Finally, it is for the programmer with some hubris, otherwise s/he would
+not be reading a paper on the C3 method resolution order in multiple
+inheritance hierarchies ;-) These three virtues taken all together (and
+*not* separately) deserve a prize: the prize is a short Python 2.2
+script that allows you to compute the 2.3 MRO without risk to your
+brain. Simply change the last line to play with the various examples I
+have discussed in this paper.
+
+ ::
+
+ #<mro.py>
+
+ """C3 algorithm by Samuele Pedroni (with readability enhanced by me)."""
+
+ class __metaclass__(type):
+ "All classes are metamagically modified to be nicely printed"
+ __repr__ = lambda cls: cls.__name__
+
+ class ex_2:
+ "Serious order disagreement" #From Guido
+ class O: pass
+ class X(O): pass
+ class Y(O): pass
+ class A(X,Y): pass
+ class B(Y,X): pass
+ try:
+ class Z(A,B): pass #creates Z(A,B) in Python 2.2
+ except TypeError:
+ pass # Z(A,B) cannot be created in Python 2.3
+
+ class ex_5:
+ "My first example"
+ class O: pass
+ class F(O): pass
+ class E(O): pass
+ class D(O): pass
+ class C(D,F): pass
+ class B(D,E): pass
+ class A(B,C): pass
+
+ class ex_6:
+ "My second example"
+ class O: pass
+ class F(O): pass
+ class E(O): pass
+ class D(O): pass
+ class C(D,F): pass
+ class B(E,D): pass
+ class A(B,C): pass
+
+ class ex_9:
+ "Difference between Python 2.2 MRO and C3" #From Samuele
+ class O: pass
+ class A(O): pass
+ class B(O): pass
+ class C(O): pass
+ class D(O): pass
+ class E(O): pass
+ class K1(A,B,C): pass
+ class K2(D,B,E): pass
+ class K3(D,A): pass
+ class Z(K1,K2,K3): pass
+
+ def merge(seqs):
+ print '\n\nCPL[%s]=%s' % (seqs[0][0],seqs),
+ res = []; i=0
+ while 1:
+ nonemptyseqs=[seq for seq in seqs if seq]
+ if not nonemptyseqs: return res
+ i+=1; print '\n',i,'round: candidates...',
+ for seq in nonemptyseqs: # find merge candidates among seq heads
+ cand = seq[0]; print ' ',cand,
+ nothead=[s for s in nonemptyseqs if cand in s[1:]]
+ if nothead: cand=None #reject candidate
+ else: break
+ if not cand: raise "Inconsistent hierarchy"
+ res.append(cand)
+ for seq in nonemptyseqs: # remove cand
+ if seq[0] == cand: del seq[0]
+
+ def mro(C):
+ "Compute the class precedence list (mro) according to C3"
+ return merge([[C]]+map(mro,C.__bases__)+[list(C.__bases__)])
+
+ def print_mro(C):
+ print '\nMRO[%s]=%s' % (C,mro(C))
+ print '\nP22 MRO[%s]=%s' % (C,C.mro())
+
+ print_mro(ex_9.Z)
+
+ #</mro.py>
+
+That's all folks,
+
+ enjoy !
+
+
+----
+
+ ::
+
+
+ __
+ ("_\ .-. .-. .-. .-. .-. .-. .-. .-. /)
+ \\_//^\\_//^\\_//^\\_//^\\_//^\\_//^\\_//^\\_//^\\_//
+ jgs `"` `"` `"` `"` `"` `"` `"` `"` `"`
+
+
+Resources
+---------
+
+.. [#] The thread on python-dev started by Samuele Pedroni:
+ http://mail.python.org/pipermail/python-dev/2002-October/029035.html
+
+.. [#] The paper *A Monotonic Superclass Linearization for Dylan*:
+ http://www.webcom.com/haahr/dylan/linearization-oopsla96.html
+
+.. [#] Guido van Rossum's essay, *Unifying types and classes in Python 2.2*:
+ http://www.python.org/2.2.2/descrintro.html
diff --git a/pypers/oxford/multilingual.py b/pypers/oxford/multilingual.py new file mode 100755 index 0000000..bed973e --- /dev/null +++ b/pypers/oxford/multilingual.py @@ -0,0 +1,52 @@ +# multilingual.py
+
+import sys
+from descriptor import AttributeDescriptor
+
+class MultilingualAttribute(AttributeDescriptor):
+ """When a MultilingualAttribute is accessed, you get the translation
+ corresponding to the currently selected language.
+ """
+ def __init__(self, **translations):
+ self.trans = translations
+ def get_from_class(self, cls):
+ return self.trans[getattr(cls, "language", None) or
+ sys.modules[cls.__module__].language]
+ def get_from_obj(self, obj):
+ return self.trans[getattr(obj, "language", None) or
+ sys.modules[obj.__class__.__module__].language]
+
+
+language = "en"
+
+# a dummy User class
+class DefaultUser(object):
+ def has_permission(self):
+ return False
+
+class WebApplication(object):
+ error_msg = MultilingualAttribute(
+ en="You cannot access this page",
+ it="Questa pagina non e' accessibile",
+ fr="Vous ne pouvez pas acceder cette page",)
+ user = DefaultUser()
+ def __init__(self, language=None):
+ self.language = language or getattr(self.__class__, "language", None)
+ def show_page(self):
+ if not self.user.has_permission():
+ return self.error_msg
+
+
+app = WebApplication()
+assert app.show_page() == "You cannot access this page"
+
+app.language = "fr"
+assert app.show_page() == "Vous ne pouvez pas acceder cette page"
+
+app.language = "it"
+assert app.show_page() == "Questa pagina non e' accessibile"
+
+app.language = "en"
+assert app.show_page() == "You cannot access this page"
+
+
diff --git a/pypers/oxford/multilingualprop.py b/pypers/oxford/multilingualprop.py new file mode 100755 index 0000000..9eb2a49 --- /dev/null +++ b/pypers/oxford/multilingualprop.py @@ -0,0 +1,30 @@ +# multilingualprop.py
+
+language = "en"
+
+# a dummy User class
+class DefaultUser(object):
+ def has_permission(self):
+ return False
+
+def multilingualProperty(**trans):
+ def get(self):
+ return trans[self.language]
+ def set(self, value):
+ trans[self.language] = value
+ return property(get, set)
+
+class WebApplication(object):
+ language = language
+ error_msg = multilingualProperty(
+ en="You cannot access this page",
+ it="Questa pagina non e' accessibile",
+ fr="Vous ne pouvez pas acceder cette page",)
+ user = DefaultUser()
+ def __init__(self, language=None):
+ if language: self.language = self.language
+ def show_page(self):
+ if not self.user.has_permission():
+ return self.error_msg
+
+
diff --git a/pypers/oxford/namedtuple.py b/pypers/oxford/namedtuple.py new file mode 100755 index 0000000..aea1049 --- /dev/null +++ b/pypers/oxford/namedtuple.py @@ -0,0 +1,31 @@ +# use operator.itemgetter if we're in 2.4, roll our own if we're in 2.3 +try: + from operator import itemgetter +except ImportError: + def itemgetter(i): + def getter(self): return self[i] + return getter +def superTuple(typename, *attribute_names): + " create and return a subclass of `tuple', with named attributes " + # make the subclass with appropriate __new__ and __repr__ specials + nargs = len(attribute_names) + class supertup(tuple): + __slots__ = () # save memory, we don't need per-instance dict + def __new__(cls, *args): + if len(args) != nargs: + raise TypeError, \ + '%s takes exactly %d arguments (%d given)' % ( + typename, nargs, len(args)) + return tuple.__new__(cls, args) + def __repr__(self): + return '%s(%s)' % (typename, ', '.join(map(repr, self))) + # add a few key touches to our new subclass of `tuple' + for index, attr_name in enumerate(attribute_names): + setattr(supertup, attr_name, property(itemgetter(index))) + supertup.__name__ = typename + return supertup + +Point = superTuple('Point', 'x', 'y') +p = Point(1,2) +print p +print p.x,p.y diff --git a/pypers/oxford/noconflict.py b/pypers/oxford/noconflict.py new file mode 100755 index 0000000..6a42e77 --- /dev/null +++ b/pypers/oxford/noconflict.py @@ -0,0 +1,75 @@ +# noconflict.py
+
+import inspect, types, __builtin__
+from skip_redundant import skip_redundant
+
+memoized_metaclasses_map = {}
+
+# utility function
+def remove_redundant(metaclasses):
+ skipset = set([types.ClassType])
+ for meta in metaclasses: # determines the metaclasses to be skipped
+ skipset.update(inspect.getmro(meta)[1:])
+ return tuple(skip_redundant(metaclasses, skipset))
+
+##################################################################
+## now the core of the module: two mutually recursive functions ##
+##################################################################
+
+def get_noconflict_metaclass(bases, left_metas, right_metas):
+ """Not intended to be used outside of this module, unless you know
+ what you are doing."""
+ # make tuple of needed metaclasses in specified priority order
+ metas = left_metas + tuple(map(type, bases)) + right_metas
+ needed_metas = remove_redundant(metas)
+
+ # return existing confict-solving meta, if any
+ if needed_metas in memoized_metaclasses_map:
+ return memoized_metaclasses_map[needed_metas]
+ # nope: compute, memoize and return needed conflict-solving meta
+ elif not needed_metas: # wee, a trivial case, happy us
+ meta = type
+ elif len(needed_metas) == 1: # another trivial case
+ meta = needed_metas[0]
+ # check for recursion, can happen i.e. for Zope ExtensionClasses
+ elif needed_metas == bases:
+ raise TypeError("Incompatible root metatypes", needed_metas)
+ else: # gotta work ...
+ metaname = '_' + ''.join([m.__name__ for m in needed_metas])
+ meta = classmaker()(metaname, needed_metas, {})
+ memoized_metaclasses_map[needed_metas] = meta
+ return meta
+
+def classmaker(left_metas=(), right_metas=()):
+ def make_class(name, bases, adict):
+ metaclass = get_noconflict_metaclass(bases, left_metas, right_metas)
+ return metaclass(name, bases, adict)
+ return make_class
+
+#################################################################
+## and now a conflict-safe replacement for 'type' ##
+#################################################################
+
+__type__=__builtin__.type # the aboriginal 'type'
+# left available in case you decide to rebind __builtin__.type
+
+class safetype(__type__):
+ # this is REALLY DEEP MAGIC
+ """Overrides the ``__new__`` method of the ``type`` metaclass, making the
+ generation of classes conflict-proof."""
+ def __new__(mcl, *args):
+ nargs = len(args)
+ if nargs == 1: # works as __builtin__.type
+ return __type__(args[0])
+ elif nargs == 3: # creates the class using the appropriate metaclass
+ n, b, d = args # name, bases and dictionary
+ meta = get_noconflict_metaclass(b, (mcl,), ())
+ if meta is mcl: # meta is trivial, dispatch to the default __new__
+ return super(safetype, mcl).__new__(mcl, n, b, d)
+ else: # non-trivial metaclass, dispatch to the right __new__
+ # (it will take a second round) # print mcl, meta
+ return super(mcl, meta).__new__(meta, n, b, d)
+ else:
+ raise TypeError('%s() takes 1 or 3 arguments' % mcl.__name__)
+
+
diff --git a/pypers/oxford/non_cooperative.py b/pypers/oxford/non_cooperative.py new file mode 100755 index 0000000..6c7b293 --- /dev/null +++ b/pypers/oxford/non_cooperative.py @@ -0,0 +1,13 @@ +# non_cooperative.py + +class B1(object): + def __init__(self, **kw): + print "B1.__init__" + super(B1, self).__init__(**kw) + +class B2(object): + def __init__(self, **kw): + print "B2.__init__" + super(B2, self).__init__(**kw) + + diff --git a/pypers/oxford/not_cooperative.txt b/pypers/oxford/not_cooperative.txt new file mode 100755 index 0000000..9017713 --- /dev/null +++ b/pypers/oxford/not_cooperative.txt @@ -0,0 +1,120 @@ + +Background: I am preparing my lecture for the Oxford ACCU conference +and I wanted to show how __getattribute__ can be used to trace +attribute access. Then I discovered that composing the TracedAttribute +mixin with a built-in type does not work reliabily, where it works +perfectly fine with custom classes. + +Methods of builtin types are usually cooperative. +Consider for instance this example: + +#<cooperative.py> + +class B1(object): + def __init__(self, **kw): + print "B1.__init__" + super(B1, self).__init__(**kw) + +class B2(object): + def __init__(self, **kw): + print "B2.__init__" + super(B2, self).__init__(**kw) + +#</cooperative.py> + +>>> from cooperative import B1, B2 +>>> class C(B1, B2, str): +... pass +>>> c = C() +B1.__init__ +B2.__init__ + +str.__init__, B1.__init__ and B2.__init__ are cooperative, as expected. +Moreover, + +>>> class C(B1, str, B2): +... pass +>>> c = C() +B1.__init__ +B2.__init__ + +and + +>>> class C(str, B1, B2): +... pass +>>> c = C() +B1.__init__ +B2.__init__ + +also work as expected. + +However str.__getattribute__ (the same for int.__getattribute__) +is only apparently cooperative with B1.__getattribute__ and +B2.__getattribute__. Consider this other example: + +#<trace_builtin.py> + +class TracedAccess1(object): + def __getattribute__(self, name): + print "1: accessing %s" % name + return super(TracedAccess1, self).__getattribute__(name) + +class TracedAccess2(object): + def __getattribute__(self, name): + print "2: accessing %s" % name + return super(TracedAccess2, self).__getattribute__(name) + +class B(object): + def __init__(self, *args): + super(B, self).__init__(*args) + + +#</trace_builtin.py> + +This *seems* to work: + +>>> from trace_builtin import TracedAccess1, TracedAccess2 +>>> class C(TracedAccess1, TracedAccess2, str): +... pass +>>> cinit = C().__init__ +1: accessing __init__ +2: accessing __init__ + +However, if I change the order of the bases, I get + +>>> class C(TracedAccess1, str, TracedAccess2): +... pass +>>> cinit = C().__init__ +1: accessing __init__ + +and + +>>> class C(str, TracedAccess1, TracedAccess2): +... pass +>>> cinit = C().__init__ + +so str.__getattribute__ (or int.__getattribute__) is not +cooperative. + +There is no such a problem for a custom type, such as B: + +>>> from trace_builtin import B +>>> class C(TracedAccess1, TracedAccess2, B): +... pass +>>> cinit = C().__init__ +1: accessing __init__ +2: accessing __init__ + +>>> class C(TracedAccess1, B, TracedAccess2): +... pass +>>> cinit = C().__init__ +1: accessing __init__ +2: accessing __init__ + +>>> class C(B, TracedAccess1, TracedAccess2): +... pass +>>> cinit = C().__init__ +1: accessing __init__ +2: accessing __init__ + +Here, everything works fine. Can somebody share some light on this? diff --git a/pypers/oxford/objects.html b/pypers/oxford/objects.html new file mode 100755 index 0000000..644fb0a --- /dev/null +++ b/pypers/oxford/objects.html @@ -0,0 +1,743 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> +<meta name="generator" content="Docutils 0.3.7: http://docutils.sourceforge.net/" /> +<title>Lecture 2: Objects (delegation & inheritance)</title> +<link rel="stylesheet" href="default.css" type="text/css" /> +</head> +<body> +<div class="document" id="lecture-2-objects-delegation-inheritance"> +<h1 class="title">Lecture 2: Objects (delegation & inheritance)</h1> +<div class="section" id="part-i-delegation"> +<h1><a name="part-i-delegation">Part I: delegation</a></h1> +<p>Understanding how attribute access works: internal delegation via <em>descriptors</em></p> +<div class="section" id="accessing-simple-attributes"> +<h2><a name="accessing-simple-attributes">Accessing simple attributes</a></h2> +<pre class="doctest-block"> +>>> class C(object): +... a = 2 +... def __init__(self, x): +... self.x = x +... +</pre> +<pre class="doctest-block"> +>>> c = C(1) +>>> c.x +1 +>>> c.a +2 +</pre> +<p>We are retrieving</p> +<pre class="doctest-block"> +>>> c.__dict__["x"] +1 +</pre> +<p>If there is nothing in c.__dict__, Python looks at C.__dict__:</p> +<pre class="doctest-block"> +>>> print c.__dict__.get("a") +None +</pre> +<pre class="doctest-block"> +>>> C.__dict__["a"] +2 +</pre> +<p>If there is nothing in C.__dict__, Python looks at the superclasses according +to the MRO (see part II).</p> +</div> +<div class="section" id="accessing-methods"> +<h2><a name="accessing-methods">Accessing methods</a></h2> +<pre class="doctest-block"> +>>> c.__init__ #doctest: +ELLIPSIS +<bound method C.__init__ of <__main__.C object at 0x...>> +</pre> +<p>since __init__ is not in c.__dict__ Python looks in the class dictionary +and finds</p> +<pre class="doctest-block"> +>>> C.__dict__["__init__"] #doctest: +ELLIPSIS +<function __init__ at 0x...> +</pre> +<p>Then it magically converts the function into a method bound to the instance +"c".</p> +<p>NOTE: this mechanism works for new-style classes only.</p> +<p>The old-style mechanism is less consistent and the attribute lookup of special +methods is special: (*)</p> +<pre class="doctest-block"> +>>> class C(object): pass +>>> c = C() +>>> c.__str__ = lambda : "hello!" +>>> print c #doctest: +ELLIPSIS +<__main__.C object at ...> +</pre> +<p>whereas for old-style classes</p> +<pre class="doctest-block"> +>>> class C: pass +>>> c = C() +>>> c.__str__ = lambda : "hello!" +>>> print c +hello! +</pre> +<p>the special method is looked for in the instance dictionary too.</p> +<p>(*) modulo a very subtle difference for __getattr__-delegated special methods, +see later.</p> +</div> +<div class="section" id="converting-functions-into-methods"> +<h2><a name="converting-functions-into-methods">Converting functions into methods</a></h2> +<p>It is possible to convert a function into a bound or unbound method +by invoking the <tt class="docutils literal"><span class="pre">__get__</span></tt> special method:</p> +<pre class="doctest-block"> +>>> def f(x): pass +>>> f.__get__ #doctest: +ELLIPSIS +<method-wrapper object at 0x...> +</pre> +<pre class="doctest-block"> +>>> class C(object): pass +... +</pre> +<pre class="doctest-block"> +>>> def f(self): pass +... +>>> f.__get__(C(), C) #doctest: +ELLIPSIS +<bound method C.f of <__main__.C object at 0x...>> +</pre> +<pre class="doctest-block"> +>>> f.__get__(None, C) +<unbound method C.f> +</pre> +<p>Functions are the simplest example of <em>descriptors</em>.</p> +<p>Access to methods works since internally Python transforms</p> +<blockquote> +<tt class="docutils literal"><span class="pre">c.__init__</span> <span class="pre">-></span> <span class="pre">type(c).__dict__['__init__'].__get__(c,</span> <span class="pre">type(c))</span></tt></blockquote> +<p>Note: not <em>all</em> functions are descriptors:</p> +<pre class="doctest-block"> +>>> from operator import add +>>> add.__get__ +Traceback (most recent call last): + ... +AttributeError: 'builtin_function_or_method' object has no attribute '__get__' +</pre> +</div> +<div class="section" id="hack-a-very-slick-adder"> +<h2><a name="hack-a-very-slick-adder">Hack: a very slick adder</a></h2> +<p>The descriptor protocol can be (ab)used as a way to avoid the late binding +issue in for loops:</p> +<pre class="doctest-block"> +>>> def add(x,y): +... return x + y +>>> closures = [add.__get__(i) for i in range(10)] +>>> closures[5](1000) +1005 +</pre> +<p>Notice: operator.add will not work.</p> +</div> +<div class="section" id="descriptor-protocol"> +<h2><a name="descriptor-protocol">Descriptor Protocol</a></h2> +<p>Everything at <a class="reference" href="http://users.rcn.com/python/download/Descriptor.htm">http://users.rcn.com/python/download/Descriptor.htm</a></p> +<p>Formally:</p> +<pre class="literal-block"> +descr.__get__(self, obj, type=None) --> value +descr.__set__(self, obj, value) --> None +descr.__delete__(self, obj) --> None +</pre> +<p>Examples of custom descriptors:</p> +<pre class="literal-block"> +#<descriptor.py> + + +class AttributeDescriptor(object): + def __get__(self, obj, cls=None): + if obj is None and cls is None: + raise TypeError("__get__(None, None) is invalid") + elif obj is None: + return self.get_from_class(cls) + else: + return self.get_from_obj(obj) + def get_from_class(self, cls): + print "Getting %s from %s" % (self, cls) + def get_from_obj(self, obj): + print "Getting %s from %s" % (self, obj) + + +class Staticmethod(AttributeDescriptor): + def __init__(self, func): + self.func = func + def get_from_class(self, cls): + return self.func + get_from_obj = get_from_class + + +class Classmethod(AttributeDescriptor): + def __init__(self, func): + self.func = func + def get_from_class(self, cls): + return self.func.__get__(cls, type(cls)) + def get_from_obj(self, obj): + return self.get_from_class(obj.__class__) + +class C(object): + s = Staticmethod(lambda : 1) + c = Classmethod(lambda cls : cls.__name__) + +c = C() + +assert C.s() == c.s() == 1 +assert C.c() == c.c() == "C" + +#</descriptor.py> +</pre> +</div> +<div class="section" id="multilingual-attribute"> +<h2><a name="multilingual-attribute">Multilingual attribute</a></h2> +<p>Inspirated by a question in the Italian Newsgroup:</p> +<pre class="literal-block"> +#<multilingual.py> + +import sys +from descriptor import AttributeDescriptor + +class MultilingualAttribute(AttributeDescriptor): + """When a MultilingualAttribute is accessed, you get the translation + corresponding to the currently selected language. + """ + def __init__(self, **translations): + self.trans = translations + def get_from_class(self, cls): + return self.trans[getattr(cls, "language", None) or + sys.modules[cls.__module__].language] + def get_from_obj(self, obj): + return self.trans[getattr(obj, "language", None) or + sys.modules[obj.__class__.__module__].language] + + +language = "en" + +# a dummy User class +class DefaultUser(object): + def has_permission(self): + return False + +class WebApplication(object): + error_msg = MultilingualAttribute( + en="You cannot access this page", + it="Questa pagina non e' accessibile", + fr="Vous ne pouvez pas acceder cette page",) + user = DefaultUser() + def __init__(self, language=None): + self.language = language or getattr(self.__class__, "language", None) + def show_page(self): + if not self.user.has_permission(): + return self.error_msg + + +app = WebApplication() +assert app.show_page() == "You cannot access this page" + +app.language = "fr" +assert app.show_page() == "Vous ne pouvez pas acceder cette page" + +app.language = "it" +assert app.show_page() == "Questa pagina non e' accessibile" + +app.language = "en" +assert app.show_page() == "You cannot access this page" + +#</multilingual.py> +</pre> +<p>The same can be done with properties:</p> +<pre class="literal-block"> +#<multilingualprop.py> + +language = "en" + +# a dummy User class +class DefaultUser(object): + def has_permission(self): + return False + +def multilingualProperty(**trans): + def get(self): + return trans[self.language] + def set(self, value): + trans[self.language] = value + return property(get, set) + +class WebApplication(object): + language = language + error_msg = multilingualProperty( + en="You cannot access this page", + it="Questa pagina non e' accessibile", + fr="Vous ne pouvez pas acceder cette page",) + user = DefaultUser() + def __init__(self, language=None): + if language: self.language = self.language + def show_page(self): + if not self.user.has_permission(): + return self.error_msg + +#</multilingualprop.py> +</pre> +<p>This also gives the possibility to set the error messages.</p> +<p>The difference with the descriptor approach</p> +<pre class="doctest-block"> +>>> from multilingual import WebApplication +>>> app = WebApplication() +>>> print app.error_msg +You cannot access this page +>>> print WebApplication.error_msg +You cannot access this page +</pre> +<p>is that with properties there is no nice access from the class:</p> +<pre class="doctest-block"> +>>> from multilingualprop import WebApplication +>>> WebApplication.error_msg #doctest: +ELLIPSIS +<property object at ...> +</pre> +</div> +<div class="section" id="another-use-case-for-properties-storing-users"> +<h2><a name="another-use-case-for-properties-storing-users">Another use case for properties: storing users</a></h2> +<p>Consider a library providing a simple User class:</p> +<pre class="literal-block"> +#<crypt_user.py> + +class User(object): + def __init__(self, username, password): + self.username, self.password = username, password + +#</crypt_user.py> +</pre> +<p>The User objects are stored in a database as they are. +For security purpose, in a second version of the library it is +decided to crypt the password, so that only crypted passwords +are stored in the database. With properties, it is possible to +implement this functionality without changing the source code for +the User class:</p> +<pre class="literal-block"> +#<crypt_user.py> + +from crypt import crypt + +def cryptedAttribute(seed="x"): + def get(self): + return getattr(self, "_pw", None) + def set(self, value): + self._pw = crypt(value, seed) + return property(get, set) + +User.password = cryptedAttribute() +</pre> +<p>#</crypt_user.py></p> +<pre class="doctest-block"> +>>> from crypt_user import User +>>> u = User("michele", "secret") +>>> print u.password +xxZREZpkHZpkI +</pre> +<p>Notice the property factory approach used here.</p> +</div> +<div class="section" id="low-level-delegation-via-getattribute"> +<h2><a name="low-level-delegation-via-getattribute">Low-level delegation via __getattribute__</a></h2> +<p>Attribute access is managed by the__getattribute__ special method:</p> +<pre class="literal-block"> +#<tracedaccess.py> + +class TracedAccess(object): + def __getattribute__(self, name): + print "Accessing %s" % name + return object.__getattribute__(self, name) + + +class C(TracedAccess): + s = staticmethod(lambda : 'staticmethod') + c = classmethod(lambda cls: 'classmethod') + m = lambda self: 'method' + a = "hello" + +#</tracedaccess.py> +</pre> +<pre class="doctest-block"> +>>> from tracedaccess import C +>>> c = C() +>>> print c.s() +Accessing s +staticmethod +>>> print c.c() +Accessing c +classmethod +>>> print c.m() +Accessing m +method +>>> print c.a +Accessing a +hello +>>> print c.__init__ #doctest: +ELLIPSIS +Accessing __init__ +<method-wrapper object at 0x...> +>>> try: c.x +... except AttributeError, e: print e +... +Accessing x +'C' object has no attribute 'x' +</pre> +<pre class="doctest-block"> +>>> c.y = 'y' +>>> c.y +Accessing y +'y' +</pre> +<p>You are probably familiar with <tt class="docutils literal"><span class="pre">__getattr__</span></tt> which is similar +to <tt class="docutils literal"><span class="pre">__getattribute__</span></tt>, but it is called <em>only for missing attributes</em>.</p> +</div> +<div class="section" id="traditional-delegation-via-getattr"> +<h2><a name="traditional-delegation-via-getattr">Traditional delegation via __getattr__</a></h2> +<p>Realistic use case in "object publishing":</p> +<pre class="literal-block"> +#<webapp.py> + +class WebApplication(object): + def __getattr__(self, name): + return name.capitalize() + + +app = WebApplication() + +assert app.page1 == 'Page1' +assert app.page2 == 'Page2' + +#</webapp.py> +</pre> +<p>Here is another use case in HTML generation:</p> +<pre class="literal-block"> +#<XMLtag.py> + +def makeattr(dict_or_list_of_pairs): + dic = dict(dict_or_list_of_pairs) + return " ".join('%s="%s"' % (k, dic[k]) for k in dic) # simplistic + +class XMLTag(object): + def __getattr__(self, name): + def tag(value, **attr): + """value can be a string or a sequence of strings.""" + if hasattr(value, "__iter__"): # is iterable + value = " ".join(value) + return "<%s %s>%s</%s>" % (name, makeattr(attr), value, name) + return tag + +class XMLShortTag(object): + def __getattr__(self, name): + def tag(**attr): + return "<%s %s />" % (name, makeattr(attr)) + return tag + +tag = XMLTag() +tg = XMLShortTag() + +#</XMLtag.py> +</pre> +<pre class="doctest-block"> +>>> from XMLtag import tag, tg +>>> print tag.a("example.com", href="http://www.example.com") +<a href="http://www.example.com">example.com</a> +>>> print tg.br(**{'class':"br_style"}) +<br class="br_style" /> +</pre> +</div> +<div class="section" id="keyword-dictionaries-with-getattr-setattr"> +<h2><a name="keyword-dictionaries-with-getattr-setattr">Keyword dictionaries with __getattr__/__setattr__</a></h2> +<pre class="literal-block"> +#<kwdict.py> + +class kwdict(dict): # UserDict not dict, to make it to work with Zope + """A typing shortcut used in place of a keyword dictionary. + It also has two useful 'fromfile' and 'fromstring' constructors + """ + def __getattr__(self, name): + return self[name] + def __setattr__(self, name, value): + self[name] = value + +#</kwdict.py> +</pre> +<p>An now for a completely different solution:</p> +<pre class="literal-block"> +#<dictwrapper.py> + +class DictWrapper(object): + def __init__(self, **kw): + self.__dict__.update(kw) + +#</dictwrapper.py> +</pre> +</div> +<div class="section" id="delegation-to-special-methods-caveat"> +<h2><a name="delegation-to-special-methods-caveat">Delegation to special methods caveat</a></h2> +<pre class="doctest-block"> +>>> class ListWrapper(object): +... def __init__(self, ls): +... self._list = ls +... def __getattr__(self, name): +... if name == "__getitem__": # special method +... return self._list.__getitem__ +... elif name == "reverse": # regular method +... return self._list.reverse +... else: +... raise AttributeError("%r is not defined" % name) +... +>>> lw = ListWrapper([0,1,2]) +>>> print lw.x +Traceback (most recent call last): + ... +AttributeError: 'x' is not defined +</pre> +<pre class="doctest-block"> +>>> lw.reverse() +>>> print lw.__getitem__(0) +2 +>>> print lw.__getitem__(1) +1 +>>> print lw.__getitem__(2) +0 +>>> print lw[0] +Traceback (most recent call last): + ... +TypeError: unindexable object +</pre> +</div> +</div> +<div class="section" id="part-ii-inheritance"> +<h1><a name="part-ii-inheritance">Part II: Inheritance</a></h1> +<p>The major changes in inheritance from Python 2.1 to 2.2+ are:</p> +<ol class="arabic simple"> +<li>you can subclass built-in types (as a consequence the constructor__new__ +has been exposed to the user, to help subclassing immutable types);</li> +<li>the Method Resolution Order (MRO) has changed;</li> +<li>now Python allows <em>cooperative method calls</em>, i.e. we have <em>super</em>.</li> +</ol> +<p>In principle, the last two changes are relevant only if you use multiple +inheritance. If you use single inheritance only, you don't need <tt class="docutils literal"><span class="pre">super</span></tt>; +you can just name the superclass or use tricks such as:</p> +<pre class="literal-block"> +self.__class__.__base__.__init__(self, *args, **kw) +</pre> +<p>instead of:</p> +<pre class="literal-block"> +super(CurrentClass, self).__init__(*args, **kw) +</pre> +<p>However, somebody else may want to use your class in a MI hierarchy, +and you would make her life difficult if you don't use <tt class="docutils literal"><span class="pre">super</span></tt>:</p> +<pre class="literal-block"> +#<why_super.py> + +class Base(object): + def __init__(self): + print "B.__init__" + +class MyClass(Base): + "I do not cooperate with others" + def __init__(self): + print "MyClass.__init__" + Base.__init__(self) #instead of super(MyClass, self).__init__() + + +class Mixin(Base): + "I am cooperative with others" + def __init__(self): + print "Mixin.__init__" + super(Mixin, self).__init__() + +class HerClass(MyClass, Mixin): + "I am cooperative too" + def __init__(self): + print "HerClass.__init__" + super(HerClass, self).__init__() + +#</why_super.py> +</pre> +<pre class="doctest-block"> +>>> from why_super import HerClass +>>> h = HerClass() # Mixin.__init__ is not called! +HerClass.__init__ +MyClass.__init__ +B.__init__ +</pre> +<p>So to be polite versus your future users you should use <tt class="docutils literal"><span class="pre">super</span></tt> always. +This adds a cognitive burden even for people not using MI :-(</p> +<p>Notice that there is no good comprehensive reference on <tt class="docutils literal"><span class="pre">super</span></tt> (yet) +Your best bet is still <a class="reference" href="http://www.python.org/2.2.3/descrintro.html#cooperation">http://www.python.org/2.2.3/descrintro.html#cooperation</a></p> +<p>The MRO instead is explained here: <a class="reference" href="http://www.python.org/2.3/mro.html">http://www.python.org/2.3/mro.html</a></p> +<p>Notice that I DO NOT recommand Multiple Inheritance.</p> +<p>More often than not you are better off using composition/delegation/wrapping, +etc.</p> +<p>See Zope 2 -> Zope 3 experience.</p> +<div class="section" id="elementary-introduction-to-the-most-sophisticated-descriptor-ever-super"> +<h2><a name="elementary-introduction-to-the-most-sophisticated-descriptor-ever-super">Elementary introduction to the most sophisticated descriptor ever: <em>super</em></a></h2> +<pre class="doctest-block"> +>>> class B(object): +... def __init__(self): print "B.__init__" +... +>>> class C(B): +... def __init__(self): print "C.__init__" +... +>>> c = C() +C.__init__ +</pre> +<p><tt class="docutils literal"><span class="pre">super(cls,</span> <span class="pre">instance)</span></tt>, where <tt class="docutils literal"><span class="pre">instance</span></tt> is an instance of <tt class="docutils literal"><span class="pre">cls</span></tt> or of +a subclass of <tt class="docutils literal"><span class="pre">cls</span></tt>, retrieves the right method in the MRO:</p> +<pre class="doctest-block"> +>>> super(C, c).__init__ #doctest: +ELLIPSIS +<bound method C.__init__ of <__main__.C object at 0x...>> +</pre> +<pre class="doctest-block"> +>>> super(C, c).__init__.im_func is B.__init__.im_func +True +</pre> +<pre class="doctest-block"> +>>> super(C, c).__init__() +B.__init__ +</pre> +<p><tt class="docutils literal"><span class="pre">super(cls,</span> <span class="pre">subclass)</span></tt> works for unbound methods:</p> +<pre class="doctest-block"> +>>> super(C, C).__init__ +<unbound method C.__init__> +</pre> +<pre class="doctest-block"> +>>> super(C, C).__init__.im_func is B.__init__.im_func +True +>>> super(C, C).__init__(c) +B.__init__ +</pre> +<p><tt class="docutils literal"><span class="pre">super(cls,</span> <span class="pre">subclass)</span></tt> is also necessary for classmethods and staticmethods. +Properties and custom descriptorsw works too:</p> +<pre class="literal-block"> +#<super_ex.py> + +from descriptor import AttributeDescriptor + +class B(object): + @staticmethod + def sm(): return "staticmethod" + + @classmethod + def cm(cls): return cls.__name__ + + p = property() + a = AttributeDescriptor() + +class C(B): pass + +#</super_ex.py> +</pre> +<pre class="doctest-block"> +>>> from super_ex import C +</pre> +<p>Staticmethod usage:</p> +<pre class="doctest-block"> +>>> super(C, C).sm #doctest: +ELLIPSIS +<function sm at 0x...> +>>> super(C, C).sm() +'staticmethod' +</pre> +<p>Classmethod usage:</p> +<pre class="doctest-block"> +>>> super(C, C()).cm +<bound method type.cm of <class 'super_ex.C'>> +>>> super(C, C).cm() # C is automatically passed +'C' +</pre> +<p>Property usage:</p> +<pre class="doctest-block"> +>>> print super(C, C).p #doctest: +ELLIPSIS +<property object at 0x...> +>>> super(C, C).a #doctest: +ELLIPSIS +Getting <descriptor.AttributeDescriptor object at 0x...> from <class 'super_ex.C'> +</pre> +<p><tt class="docutils literal"><span class="pre">super</span></tt> does not work with old-style classes, however you can use the +following trick:</p> +<pre class="literal-block"> +#<super_old_new.py> +class O: + def __init__(self): + print "O.__init__" + +class N(O, object): + def __init__(self): + print "N.__init__" + super(N, self).__init__() +</pre> +<p>#</super_old_new.py></p> +<pre class="doctest-block"> +>>> from super_old_new import N +>>> new = N() +N.__init__ +O.__init__ +</pre> +<p>There are dozens of tricky points concerning <tt class="docutils literal"><span class="pre">super</span></tt>, be warned!</p> +</div> +<div class="section" id="subclassing-built-in-types-new-vs-init"> +<h2><a name="subclassing-built-in-types-new-vs-init">Subclassing built-in types; __new__ vs. __init__</a></h2> +<pre class="literal-block"> +#<point.py> + +class NotWorkingPoint(tuple): + def __init__(self, x, y): + super(NotWorkingPoint, self).__init__((x,y)) + self.x, self.y = x, y + +#</point.py> +</pre> +<pre class="doctest-block"> +>>> from point import NotWorkingPoint +>>> p = NotWorkingPoint(2,3) +Traceback (most recent call last): + ... +TypeError: tuple() takes at most 1 argument (2 given) +</pre> +<blockquote> +<p>#<point.py></p> +<dl class="docutils"> +<dt>class Point(tuple):</dt> +<dd><dl class="first last docutils"> +<dt>def __new__(cls, x, y):</dt> +<dd>return super(Point, cls).__new__(cls, (x,y))</dd> +<dt>def __init__(self, x, y):</dt> +<dd>super(Point, self).__init__((x, y)) +self.x, self.y = x, y</dd> +</dl> +</dd> +</dl> +<p>#</point.py></p> +</blockquote> +<p>Notice that__new__ is a staticmethod, not a classmethod, so one needs +to pass the class explicitely.</p> +<pre class="doctest-block"> +>>> from point import Point +>>> p = Point(2,3) +>>> print p, p.x, p.y +(2, 3) 2 3 +</pre> +</div> +<div class="section" id="be-careful-when-using-new-with-mutable-types"> +<h2><a name="be-careful-when-using-new-with-mutable-types">Be careful when using __new__ with mutable types</a></h2> +<pre class="doctest-block"> +>>> class ListWithDefault(list): +... def __new__(cls): +... return super(ListWithDefault, cls).__new__(cls, ["hello"]) +... +>>> print ListWithDefault() # beware! NOT ["hello"]! +[] +</pre> +<p>Reason: lists are re-initialized to empty lists in list.__init__!</p> +<p>Instead</p> +<pre class="doctest-block"> +>>> class ListWithDefault(list): +... def __init__(self): +... super(ListWithDefault, self).__init__(["hello"]) +... +>>> print ListWithDefault() # works! +['hello'] +</pre> +</div> +</div> +</div> +</body> +</html> diff --git a/pypers/oxford/objects.txt b/pypers/oxford/objects.txt new file mode 100755 index 0000000..d78e3a0 --- /dev/null +++ b/pypers/oxford/objects.txt @@ -0,0 +1,747 @@ +Lecture 2: Objects (delegation & inheritance) +============================================== + +Part I: delegation ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Understanding how attribute access works: internal delegation via *descriptors* + +Accessing simple attributes +-------------------------------- + +>>> class C(object): +... a = 2 +... def __init__(self, x): +... self.x = x +... + +>>> c = C(1) +>>> c.x +1 +>>> c.a +2 + +We are retrieving + +>>> c.__dict__["x"] +1 + +If there is nothing in c.__dict__, Python looks at C.__dict__: + +>>> print c.__dict__.get("a") +None + +>>> C.__dict__["a"] +2 + +If there is nothing in C.__dict__, Python looks at the superclasses according +to the MRO (see part II). + +Accessing methods +-------------------------------------------------------- + +>>> c.__init__ #doctest: +ELLIPSIS +<bound method C.__init__ of <__main__.C object at 0x...>> + +since __init__ is not in c.__dict__ Python looks in the class dictionary +and finds + +>>> C.__dict__["__init__"] #doctest: +ELLIPSIS +<function __init__ at 0x...> + +Then it magically converts the function into a method bound to the instance +"c". + +NOTE: this mechanism works for new-style classes only. + +The old-style mechanism is less consistent and the attribute lookup of special +methods is special: (*) + +>>> class C(object): pass +>>> c = C() +>>> c.__str__ = lambda : "hello!" +>>> print c #doctest: +ELLIPSIS +<__main__.C object at ...> + +whereas for old-style classes + +>>> class C: pass +>>> c = C() +>>> c.__str__ = lambda : "hello!" +>>> print c +hello! + +the special method is looked for in the instance dictionary too. + +(*) modulo a very subtle difference for __getattr__-delegated special methods, +see later. + +Converting functions into methods +------------------------------------- + +It is possible to convert a function into a bound or unbound method +by invoking the ``__get__`` special method: + +>>> def f(x): pass +>>> f.__get__ #doctest: +ELLIPSIS +<method-wrapper object at 0x...> + +>>> class C(object): pass +... + +>>> def f(self): pass +... +>>> f.__get__(C(), C) #doctest: +ELLIPSIS +<bound method C.f of <__main__.C object at 0x...>> + +>>> f.__get__(None, C) +<unbound method C.f> + +Functions are the simplest example of *descriptors*. + +Access to methods works since internally Python transforms + + ``c.__init__ -> type(c).__dict__['__init__'].__get__(c, type(c))`` + + +Note: not *all* functions are descriptors: + +>>> from operator import add +>>> add.__get__ +Traceback (most recent call last): + ... +AttributeError: 'builtin_function_or_method' object has no attribute '__get__' + +Hack: a very slick adder +----------------------------- + +The descriptor protocol can be (ab)used as a way to avoid the late binding +issue in for loops: + +>>> def add(x,y): +... return x + y +>>> closures = [add.__get__(i) for i in range(10)] +>>> closures[5](1000) +1005 + +Notice: operator.add will not work. + +Descriptor Protocol +---------------------- + +Everything at http://users.rcn.com/python/download/Descriptor.htm + +Formally:: + + descr.__get__(self, obj, type=None) --> value + descr.__set__(self, obj, value) --> None + descr.__delete__(self, obj) --> None + +Examples of custom descriptors:: + + #<descriptor.py> + + + class AttributeDescriptor(object): + def __get__(self, obj, cls=None): + if obj is None and cls is None: + raise TypeError("__get__(None, None) is invalid") + elif obj is None: + return self.get_from_class(cls) + else: + return self.get_from_obj(obj) + def get_from_class(self, cls): + print "Getting %s from %s" % (self, cls) + def get_from_obj(self, obj): + print "Getting %s from %s" % (self, obj) + + + class Staticmethod(AttributeDescriptor): + def __init__(self, func): + self.func = func + def get_from_class(self, cls): + return self.func + get_from_obj = get_from_class + + + class Classmethod(AttributeDescriptor): + def __init__(self, func): + self.func = func + def get_from_class(self, cls): + return self.func.__get__(cls, type(cls)) + def get_from_obj(self, obj): + return self.get_from_class(obj.__class__) + + class C(object): + s = Staticmethod(lambda : 1) + c = Classmethod(lambda cls : cls.__name__) + + c = C() + + assert C.s() == c.s() == 1 + assert C.c() == c.c() == "C" + + #</descriptor.py> + +Multilingual attribute +---------------------- + +Inspirated by a question in the Italian Newsgroup:: + + #<multilingual.py> + + import sys + from descriptor import AttributeDescriptor + + class MultilingualAttribute(AttributeDescriptor): + """When a MultilingualAttribute is accessed, you get the translation + corresponding to the currently selected language. + """ + def __init__(self, **translations): + self.trans = translations + def get_from_class(self, cls): + return self.trans[getattr(cls, "language", None) or + sys.modules[cls.__module__].language] + def get_from_obj(self, obj): + return self.trans[getattr(obj, "language", None) or + sys.modules[obj.__class__.__module__].language] + + + language = "en" + + # a dummy User class + class DefaultUser(object): + def has_permission(self): + return False + + class WebApplication(object): + error_msg = MultilingualAttribute( + en="You cannot access this page", + it="Questa pagina non e' accessibile", + fr="Vous ne pouvez pas acceder cette page",) + user = DefaultUser() + def __init__(self, language=None): + self.language = language or getattr(self.__class__, "language", None) + def show_page(self): + if not self.user.has_permission(): + return self.error_msg + + + app = WebApplication() + assert app.show_page() == "You cannot access this page" + + app.language = "fr" + assert app.show_page() == "Vous ne pouvez pas acceder cette page" + + app.language = "it" + assert app.show_page() == "Questa pagina non e' accessibile" + + app.language = "en" + assert app.show_page() == "You cannot access this page" + + #</multilingual.py> + +The same can be done with properties:: + + #<multilingualprop.py> + + language = "en" + + # a dummy User class + class DefaultUser(object): + def has_permission(self): + return False + + def multilingualProperty(**trans): + def get(self): + return trans[self.language] + def set(self, value): + trans[self.language] = value + return property(get, set) + + class WebApplication(object): + language = language + error_msg = multilingualProperty( + en="You cannot access this page", + it="Questa pagina non e' accessibile", + fr="Vous ne pouvez pas acceder cette page",) + user = DefaultUser() + def __init__(self, language=None): + if language: self.language = self.language + def show_page(self): + if not self.user.has_permission(): + return self.error_msg + + #</multilingualprop.py> + +This also gives the possibility to set the error messages. + +The difference with the descriptor approach + +>>> from multilingual import WebApplication +>>> app = WebApplication() +>>> print app.error_msg +You cannot access this page +>>> print WebApplication.error_msg +You cannot access this page + +is that with properties there is no nice access from the class: + +>>> from multilingualprop import WebApplication +>>> WebApplication.error_msg #doctest: +ELLIPSIS +<property object at ...> + +Another use case for properties: storing users +------------------------------------------------------------ + +Consider a library providing a simple User class:: + + #<crypt_user.py> + + class User(object): + def __init__(self, username, password): + self.username, self.password = username, password + + #</crypt_user.py> + +The User objects are stored in a database as they are. +For security purpose, in a second version of the library it is +decided to crypt the password, so that only crypted passwords +are stored in the database. With properties, it is possible to +implement this functionality without changing the source code for +the User class:: + + #<crypt_user.py> + + from crypt import crypt + + def cryptedAttribute(seed="x"): + def get(self): + return getattr(self, "_pw", None) + def set(self, value): + self._pw = crypt(value, seed) + return property(get, set) + + User.password = cryptedAttribute() + +#</crypt_user.py> + +>>> from crypt_user import User +>>> u = User("michele", "secret") +>>> print u.password +xxZREZpkHZpkI + +Notice the property factory approach used here. + +Low-level delegation via __getattribute__ +------------------------------------------------------------------ + +Attribute access is managed by the__getattribute__ special method:: + + #<tracedaccess.py> + + class TracedAccess(object): + def __getattribute__(self, name): + print "Accessing %s" % name + return object.__getattribute__(self, name) + + + class C(TracedAccess): + s = staticmethod(lambda : 'staticmethod') + c = classmethod(lambda cls: 'classmethod') + m = lambda self: 'method' + a = "hello" + + #</tracedaccess.py> + +>>> from tracedaccess import C +>>> c = C() +>>> print c.s() +Accessing s +staticmethod +>>> print c.c() +Accessing c +classmethod +>>> print c.m() +Accessing m +method +>>> print c.a +Accessing a +hello +>>> print c.__init__ #doctest: +ELLIPSIS +Accessing __init__ +<method-wrapper object at 0x...> +>>> try: c.x +... except AttributeError, e: print e +... +Accessing x +'C' object has no attribute 'x' + +>>> c.y = 'y' +>>> c.y +Accessing y +'y' + +You are probably familiar with ``__getattr__`` which is similar +to ``__getattribute__``, but it is called *only for missing attributes*. + +Traditional delegation via __getattr__ +-------------------------------------------------------- + +Realistic use case in "object publishing":: + + #<webapp.py> + + class WebApplication(object): + def __getattr__(self, name): + return name.capitalize() + + + app = WebApplication() + + assert app.page1 == 'Page1' + assert app.page2 == 'Page2' + + #</webapp.py> + +Here is another use case in HTML generation:: + + #<XMLtag.py> + + def makeattr(dict_or_list_of_pairs): + dic = dict(dict_or_list_of_pairs) + return " ".join('%s="%s"' % (k, dic[k]) for k in dic) # simplistic + + class XMLTag(object): + def __getattr__(self, name): + def tag(value, **attr): + """value can be a string or a sequence of strings.""" + if hasattr(value, "__iter__"): # is iterable + value = " ".join(value) + return "<%s %s>%s</%s>" % (name, makeattr(attr), value, name) + return tag + + class XMLShortTag(object): + def __getattr__(self, name): + def tag(**attr): + return "<%s %s />" % (name, makeattr(attr)) + return tag + + tag = XMLTag() + tg = XMLShortTag() + + #</XMLtag.py> + +>>> from XMLtag import tag, tg +>>> print tag.a("example.com", href="http://www.example.com") +<a href="http://www.example.com">example.com</a> +>>> print tg.br(**{'class':"br_style"}) +<br class="br_style" /> + +Keyword dictionaries with __getattr__/__setattr__ +--------------------------------------------------- +:: + + #<kwdict.py> + + class kwdict(dict): # or UserDict, to make it to work with Zope + """A typing shortcut used in place of a keyword dictionary.""" + def __getattr__(self, name): + return self[name] + def __setattr__(self, name, value): + self[name] = value + + #</kwdict.py> + +And now for a completely different solution:: + + #<dictwrapper.py> + + class DictWrapper(object): + def __init__(self, **kw): + self.__dict__.update(kw) + + #</dictwrapper.py> + + +Delegation to special methods caveat +-------------------------------------- + +>>> class ListWrapper(object): +... def __init__(self, ls): +... self._list = ls +... def __getattr__(self, name): +... if name == "__getitem__": # special method +... return self._list.__getitem__ +... elif name == "reverse": # regular method +... return self._list.reverse +... else: +... raise AttributeError("%r is not defined" % name) +... +>>> lw = ListWrapper([0,1,2]) +>>> print lw.x +Traceback (most recent call last): + ... +AttributeError: 'x' is not defined + +>>> lw.reverse() +>>> print lw.__getitem__(0) +2 +>>> print lw.__getitem__(1) +1 +>>> print lw.__getitem__(2) +0 +>>> print lw[0] +Traceback (most recent call last): + ... +TypeError: unindexable object + + +Part II: Inheritance +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +The major changes in inheritance from Python 2.1 to 2.2+ are: + +1. you can subclass built-in types (as a consequence the constructor__new__ + has been exposed to the user, to help subclassing immutable types); +2. the Method Resolution Order (MRO) has changed; +3. now Python allows *cooperative method calls*, i.e. we have *super*. + +Why you need to know about MI even if you do not use it +----------------------------------------------------------- + +In principle, the last two changes are relevant only if you use multiple +inheritance. If you use single inheritance only, you don't need ``super``: +you can just name the superclass. +However, somebody else may want to use your class in a MI hierarchy, +and you would make her life difficult if you don't use ``super``. + +My SI hierarchy:: + + #<why_super.py> + + class Base(object): + def __init__(self): + print "B.__init__" + + class MyClass(Base): + "I do not cooperate with others" + def __init__(self): + print "MyClass.__init__" + Base.__init__(self) #instead of super(MyClass, self).__init__() + + #</why_super.py> + +Her MI hierarchy:: + + #<why_super.py> + + class Mixin(Base): + "I am cooperative with others" + def __init__(self): + print "Mixin.__init__" + super(Mixin, self).__init__() + + class HerClass(MyClass, Mixin): + "I am supposed to be cooperative too" + def __init__(self): + print "HerClass.__init__" + super(HerClass, self).__init__() + + #</why_super.py> + +>>> from why_super import HerClass +>>> h = HerClass() # Mixin.__init__ is not called! +HerClass.__init__ +MyClass.__init__ +B.__init__ + + :: + + 4 object + | + 3 Base + / \ + 1 MyClass 2 Mixin + \ / + 0 HerClass + +>>> [ancestor.__name__ for ancestor in HerClass.mro()] +['HerClass', 'MyClass', 'Mixin', 'Base', 'object'] + +In order to be polite versus your future users, you should use ``super`` +always. This adds a cognitive burden even for people not using MI :-( + +Notice that there is no good comprehensive reference on ``super`` (yet) +Your best bet is still http://www.python.org/2.2.3/descrintro.html#cooperation + +The MRO instead is explained here: http://www.python.org/2.3/mro.html + +Notice that I DO NOT recommand Multiple Inheritance. + +More often than not you are better off using composition/delegation/wrapping, +etc. + +See Zope 2 -> Zope 3 experience. + +A few details about ``super`` (not the whole truth) +------------------------------------------------------------------------------ + +>>> class B(object): +... def __init__(self): print "B.__init__" +... +>>> class C(B): +... def __init__(self): print "C.__init__" +... +>>> c = C() +C.__init__ + +``super(cls, instance)``, where ``instance`` is an instance of ``cls`` or of +a subclass of ``cls``, retrieves the right method in the MRO: + +>>> super(C, c).__init__ #doctest: +ELLIPSIS +<bound method C.__init__ of <__main__.C object at 0x...>> + +>>> super(C, c).__init__.im_func is B.__init__.im_func +True + +>>> super(C, c).__init__() +B.__init__ + +``super(cls, subclass)`` works for unbound methods: + +>>> super(C, C).__init__ +<unbound method C.__init__> + +>>> super(C, C).__init__.im_func is B.__init__.im_func +True +>>> super(C, C).__init__(c) +B.__init__ + +``super(cls, subclass)`` is also necessary for classmethods and staticmethods. +Properties and custom descriptorsw works too:: + + #<super_ex.py> + + from descriptor import AttributeDescriptor + + class B(object): + @staticmethod + def sm(): return "staticmethod" + + @classmethod + def cm(cls): return cls.__name__ + + p = property() + a = AttributeDescriptor() + + class C(B): pass + + #</super_ex.py> + +>>> from super_ex import C + +Staticmethod usage: + +>>> super(C, C).sm #doctest: +ELLIPSIS +<function sm at 0x...> +>>> super(C, C).sm() +'staticmethod' + +Classmethod usage: + +>>> super(C, C()).cm +<bound method type.cm of <class 'super_ex.C'>> +>>> super(C, C).cm() # C is automatically passed +'C' + +Property usage: + +>>> print super(C, C).p #doctest: +ELLIPSIS +<property object at 0x...> +>>> super(C, C).a #doctest: +ELLIPSIS +Getting <descriptor.AttributeDescriptor object at 0x...> from <class 'super_ex.C'> + +``super`` does not work with old-style classes, however you can use the +following trick:: + + #<super_old_new.py> + class O: + def __init__(self): + print "O.__init__" + + class N(O, object): + def __init__(self): + print "N.__init__" + super(N, self).__init__() + + #</super_old_new.py> + +>>> from super_old_new import N +>>> new = N() +N.__init__ +O.__init__ + +There are dozens of tricky points concerning ``super``, be warned! + +Subclassing built-in types; __new__ vs. __init__ +----------------------------------------------------- + +:: + + #<point.py> + + class NotWorkingPoint(tuple): + def __init__(self, x, y): + super(NotWorkingPoint, self).__init__((x,y)) + self.x, self.y = x, y + + #</point.py> + +>>> from point import NotWorkingPoint +>>> p = NotWorkingPoint(2,3) +Traceback (most recent call last): + ... +TypeError: tuple() takes at most 1 argument (2 given) + +:: + + #<point.py> + + class Point(tuple): + def __new__(cls, x, y): + return super(Point, cls).__new__(cls, (x,y)) + def __init__(self, x, y): + super(Point, self).__init__((x, y)) + self.x, self.y = x, y + + #</point.py> + +Notice that__new__ is a staticmethod, not a classmethod, so one needs +to pass the class explicitely. + +>>> from point import Point +>>> p = Point(2,3) +>>> print p, p.x, p.y +(2, 3) 2 3 + +Be careful when using __new__ with mutable types +------------------------------------------------ + +>>> class ListWithDefault(list): +... def __new__(cls): +... return super(ListWithDefault, cls).__new__(cls, ["hello"]) +... +>>> print ListWithDefault() # beware! NOT ["hello"]! +[] + +Reason: lists are re-initialized to empty lists in list.__init__! + +Instead + +>>> class ListWithDefault(list): +... def __init__(self): +... super(ListWithDefault, self).__init__(["hello"]) +... +>>> print ListWithDefault() # works! +['hello'] diff --git a/pypers/oxford/other.txt b/pypers/oxford/other.txt new file mode 100755 index 0000000..2b6d62e --- /dev/null +++ b/pypers/oxford/other.txt @@ -0,0 +1,193 @@ +Mixin programming +----------------------------------- + +:: + + #<interp.py> + + import UserDict + + class Chainmap(UserDict.DictMixin): + """Combine multiple mappings for sequential lookup. Raymond Hettinger, + http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/305268 """ + + def __init__(self, *maps): + self._maps = maps + + def __getitem__(self, key): + for mapping in self._maps: + try: + return mapping[key] + except KeyError: + pass + raise KeyError(key) + + #</interp.py> + +An application of chainmap +--------------------------------- +:: + + #<interp.py> + + import sys + from string import Template + + def interp(text, repldic=None, safe_substitute=True): + caller = sys._getframe(1) + if repldic: + mapping = Chainmap(repldic, caller.f_locals, caller.f_globals) + else: + mapping = Chainmap(caller.f_locals, caller.f_globals) + t = Template(text) + if safe_substitute: + return t.safe_substitute(mapping) + else: + return t.substitute(mapping) + + ## Example: + + language="Python" + + def printmsg(): + opinion = "favorite" + print interp("My $opinion language is $language.") + +#</interp.py> + +>>> from interp import printmsg +>>> printmsg() +My favorite language is Python. + + +Operator overloading + http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/384122 + +Thread "Puzzling OO design": + +http://groups-beta.google.com/group/comp.lang.python/browse_frm/thread/63fcfade7aae5d84/c43d73fabc73bd73#c43d73fabc73bd73 + + +Deferred +------------------------------------------- + + + + + + + + + + + +Darker than you think: ``MagicSuper`` +------------------------------------------------ + +#<magicsuper.py> + +"""MagicSuper: an example of metaclass recompiling the source code. +This provides Python with a ``callsupermethod`` macro simplifying +the cooperative call syntax. +Examples: + +from magicsuper import object + +class B(object): + def __new__(cls, *args, **kw): + print "B.__new__" + return callsupermethod(cls) + def __init__(self, *args, **kw): + print "B.__init__" + callsupermethod(*args, **kw) + @staticmethod + def sm(): + print "B.sm" + @classmethod + def cm(cls): + print cls.__name__ + + +class C(B): + def __new__(cls, *args, **kw): + print args, kw + return callsupermethod(cls, *args, **kw) + @staticmethod + def sm(): + callsupermethod() + @classmethod + def cm(cls): + callsupermethod() + +c = C() +c.cm() +c.sm() +""" + +import inspect, textwrap + +class MagicSuper(type): + def __init__(cls, clsname, bases, dic): + clsmodule = __import__(cls.__module__)# assume cls is defined in source + for name, value in dic.iteritems(): + # __new__ is seen as a function in the dic, so it has + # to be converted explicitly into a staticmethod; + # ordinary staticmethods don't type-dispatch on their + # first argument, so use 'super(cls, cls)' for them. + was_staticmethod = False + if isinstance(value, staticmethod): + value = value.__get__("dummy") # convert to function + was_staticmethod = True + elif isinstance(value, classmethod): + value = value.__get__("dummy").im_func # convert to function + if inspect.isfunction(value): + if was_staticmethod: + first_arg = clsname # super first argument + else: + first_arg = inspect.getargspec(value)[0][0] + source = textwrap.dedent(inspect.getsource(value)) + if not 'callsupermethod' in source: continue + source = source.replace( + 'callsupermethod', 'super(%s, %s).%s' + % (clsname, first_arg, name)) + # print source # debug + exec source in clsmodule.__dict__, dic # modifies dic + if name == "__new__": + dic[name] = staticmethod(dic[name]) + setattr(cls, name, dic[name]) + +object = MagicSuper("object", (), {}) + +# example: + +class B(object): + def __new__(cls, *args, **kw): + return callsupermethod(cls) + def __init__(self, *args, **kw): + print "B.__init__" + callsupermethod(*args, **kw) + @staticmethod + def sm(): + print "B.sm" + @classmethod + def cm(cls): + print cls.__name__ + + +class C(B): + def __new__(cls, *args, **kw): + print args, kw + return callsupermethod(cls, *args, **kw) + @staticmethod + def sm(): + callsupermethod() + @classmethod + def cm(cls): + callsupermethod() + + +c = C(1, x=2) +c.sm() +c.cm() + +#</magicsuper.py> diff --git a/pypers/oxford/paleo.py b/pypers/oxford/paleo.py new file mode 100755 index 0000000..1df4559 --- /dev/null +++ b/pypers/oxford/paleo.py @@ -0,0 +1,20 @@ +# paleo.py + +class Homo(object): + def can(self): + print "<%s> can:" % self.__class__.__name__ + for attr in dir(self): + if attr.endswith('__can'): print getattr(self, attr) + +class HomoHabilis(Homo): + __can = " - make tools" + +class HomoSapiens(HomoHabilis): + __can = " - make abstractions" + +class HomoSapiensSapiens(HomoSapiens): + __can = " - make art" + +modernman = HomoSapiensSapiens() + + diff --git a/pypers/oxford/parens2indent.py b/pypers/oxford/parens2indent.py new file mode 100755 index 0000000..2823836 --- /dev/null +++ b/pypers/oxford/parens2indent.py @@ -0,0 +1,30 @@ +# parens2indent.py +"""A simple s-expression parser.""" + +import re + +def parse(sexpr): + position = 0 + nesting_level = 0 + paren = re.compile(r"(?P<paren_beg>\()|(?P<paren_end>\))") + while True: + match = paren.search(sexpr, position) + if match: + yield nesting_level, sexpr[position: match.start()] + if match.lastgroup == "paren_beg": + nesting_level += 1 + elif match.lastgroup == "paren_end": + nesting_level -= 1 + position = match.end() + else: + break + +def parens2indent(sexpr): + for nesting, text in parse(sexpr.replace("\n", "")): + if text.strip(): print " "*nesting, text + +parens2indent( """\ +(html (head (title Example)) (body (h1 s-expr formatter example) +(a (href http://www.example.com) A link)))""") + + diff --git a/pypers/oxford/passwd.py b/pypers/oxford/passwd.py new file mode 100755 index 0000000..c25a129 --- /dev/null +++ b/pypers/oxford/passwd.py @@ -0,0 +1,31 @@ +class User(object): + def __init__(self, un, pw): + self.un, self.pw = un, pw + +from crypt import crypt + +def cryptedAttribute(seed): + def get(self): + return getattr(self, "_pw", None) + def set(self, value): + self._pw = crypt(value, seed) + return property(get, set) + +class User(object): + pw = cryptedAttribute("a") + def __init__(self, un, pw): + self.un, self.pw = un, pw + + +class SpecificUser(User): + pw = cryptedAttribute("b") + + + +u = User("michele", "secret") +print u.un, u.pw + +su = SpecificUser("michele", "secret") +print su.un, su.pw + +print su._pw diff --git a/pypers/oxford/point.py b/pypers/oxford/point.py new file mode 100755 index 0000000..4145b0d --- /dev/null +++ b/pypers/oxford/point.py @@ -0,0 +1,17 @@ +# point.py
+
+class NotWorkingPoint(tuple):
+ def __init__(self, x, y):
+ super(NotWorkingPoint, self).__init__((x,y))
+ self.x, self.y = x, y
+
+
+
+class Point(tuple):
+ def __new__(cls, x, y):
+ return super(Point, cls).__new__(cls, (x,y))
+ def __init__(self, x, y):
+ super(Point, self).__init__((x, y))
+ self.x, self.y = x, y
+
+
diff --git a/pypers/oxford/pro.py b/pypers/oxford/pro.py new file mode 100755 index 0000000..b8ee7fa --- /dev/null +++ b/pypers/oxford/pro.py @@ -0,0 +1,5 @@ +class O(object): + def __init__(self): + pass + +O() diff --git a/pypers/oxford/program.txt b/pypers/oxford/program.txt new file mode 100755 index 0000000..1f5b36f --- /dev/null +++ b/pypers/oxford/program.txt @@ -0,0 +1,45 @@ +*Wonders of Today's Python* -- presented by Michele Simionato + +*Everything you always wanted to know about Python but were afraid to ask!* + +In the last few years, with the advent of the new-style object model and +of iterators, generators, decorators and more, Python has undergone a silent +revolution. Many of the idioms, patterns and techniques of the past have +been replaced by better solutions. You may feel that the language +is evolving at too rapid a pace for you, or that your Python skills are +becoming obsolete. In this case don't worry: this seminar is for you! + +For this lectures, I have picked some of the most interesting new +paradigmas in modern Python, and I discuss them through examples big +and small, together with a thorough grounding in the relevant language +mechanisms. + +Look at the variety of design choices that today's Python makes +available to you, and learn when you should use the advanced +techniques presented here and when you should not. + +Iterators and Generators underlie Python's new approach to looping -- +it's not your grandparents' loop any more! Learn how to encapsulate +the underlying logic of your control structures and make it +reusable. See how itertools can turn the "abstraction penalty" typical +of other languages into an abstraction _bonus_, making your code +faster at the same time as more abstract and general. + +Learn about Design Patterns and other Object-Oriented idioms and +mechanisms. Python is a multi-paradigm language, but OOP is its core +paradigm. Understand the pros and cons of your alternatives: When +should you use closures, rather than callable instances? When is +inheritance OK, and when is it better to hold-and-delegate? What +classical Design Patterns are built-in to Python, and which others are +appropriate to consider, when? + +Descriptors and Metaclasses are the underpinnings of today's Python's +OOP -- Python exposes them and lets you customize them for your own +purposes. Add Decorators, the new syntax just introduced in Python 2.4 +(a systematic application of a crucial use case for higher-order +functions), and you'll see why the working title of that chapter was +"Black Magic"... Learn important use cases for each of these advanced +mechanisms. + +Prerequisites: you need a solid grasp of Python fundamentals to start +with. Course objectives: you'll walk out of this a Python wizard! diff --git a/pypers/oxford/quixote_ex.py b/pypers/oxford/quixote_ex.py new file mode 100755 index 0000000..56421f3 --- /dev/null +++ b/pypers/oxford/quixote_ex.py @@ -0,0 +1,15 @@ +from ms.quixote_utils_exp import Website, htmlpage + +@htmlpage() +def _q_index(): + yield "This is the main page." + yield "You can access <a href='apage'>apage</a> too." + +@htmlpage() +def apage(): + return "hello!" + +publisher = Website(_q_index, apage).publisher() + +if __name__ == "__main__": + publisher.run_show() diff --git a/pypers/oxford/reiterable.py b/pypers/oxford/reiterable.py new file mode 100755 index 0000000..74b1bb8 --- /dev/null +++ b/pypers/oxford/reiterable.py @@ -0,0 +1,10 @@ +# reiterable.py
+
+class ReIter(object):
+ "A re-iterable object."
+ def __iter__(self):
+ yield 1
+ yield 2
+ yield 3
+
+
diff --git a/pypers/oxford/s_parser.py b/pypers/oxford/s_parser.py new file mode 100755 index 0000000..e83517f --- /dev/null +++ b/pypers/oxford/s_parser.py @@ -0,0 +1,42 @@ +# s-expr parser + +sexpr = """\ +(html +(head + (title HTML as s-expr example)) +(body + (h1 HTML as s-expr example) + (a (href http://www.example.com) A link)) +html) +""" + +import re, inspect + +def second_arg(func): + args = inspect.getargspec(func)[0] + if len(args) >= 2: return args[1] + +class MetaParser(type): + def __init__(cls, name, bases, dic): + groups = [] + for n, f in dic.iteritems(): + if inspect.isfunction(f) and second_arg(f) == "pattern": + groups.append("(?P<%s>%s)" % (n, f.func_defaults[0])) + rx = re.compile("|".join(groups)) + +class BaseParser(object): + __metaclass__ = MetaParser + def __init__(self, sexpr): + self.sexpr = sexpr + +class SexprParser(BaseParser): + def paren_beg(self, pattern=r"\("): + return + def paren_end(self, pattern=r"\)"): + return + def __iter__(self): + seek = 0 + searcher = lambda : paren.search(self.text, seek) + while True: + pass + diff --git a/pypers/oxford/sect.py b/pypers/oxford/sect.py new file mode 100755 index 0000000..7bc154f --- /dev/null +++ b/pypers/oxford/sect.py @@ -0,0 +1,12 @@ +from walk import walk, pprint +from operator import itemgetter +from itertools import groupby + +nested_ls = [1,[2,[3,[[[4,5],6]]]],7] +pprint(nested_ls) + +d = dict.fromkeys(range(6), 0) +levels = (lvl for obj, lvl in walk(nested_ls)) + +for lvl, grp in groupby(walk(nested_ls), key=itemgetter(1)): + print lvl, list(grp) diff --git a/pypers/oxford/setter.py b/pypers/oxford/setter.py new file mode 100755 index 0000000..10aea13 --- /dev/null +++ b/pypers/oxford/setter.py @@ -0,0 +1,17 @@ +import re + +class Regexp(object): + def __init__(self, pattern): + self._rx = re.compile(pattern) + +class Setter(object): + def __setattr__(self, name, value): + val = "(?P<%s>%s)" % (name, value) + super(Setter, self).__setattr__(name, val) + +s = Setter() + +s.paren_beg = r"\(" +s.paren_end = r"\)" + +print s.__dict__ diff --git a/pypers/oxford/sexpr2indent.py b/pypers/oxford/sexpr2indent.py new file mode 100755 index 0000000..63cd2cf --- /dev/null +++ b/pypers/oxford/sexpr2indent.py @@ -0,0 +1,26 @@ +# sexpr2indent.py
+"""A simple s-expression formatter."""
+
+import re
+
+def parse(sexpr):
+ position = 0
+ nesting_level = 0
+ paren = re.compile(r"(?P<paren_beg>\()|(?P<paren_end>\))")
+ while True:
+ match = paren.search(sexpr, position)
+ if match:
+ yield nesting_level, sexpr[position: match.start()]
+ if match.lastgroup == "paren_beg":
+ nesting_level += 1
+ elif match.lastgroup == "paren_end":
+ nesting_level -= 1
+ position = match.end()
+ else:
+ break
+
+def sexpr_indent(sexpr):
+ for nesting, text in parse(sexpr.replace("\n", "")):
+ if text.strip(): print " "*nesting, text
+
+
diff --git a/pypers/oxford/skip_redundant.py b/pypers/oxford/skip_redundant.py new file mode 100755 index 0000000..22e966b --- /dev/null +++ b/pypers/oxford/skip_redundant.py @@ -0,0 +1,11 @@ +# skip_redundant.py
+
+def skip_redundant(iterable, skipset=None):
+ "Redundant items are repeated items or items in the original skipset."
+ if skipset is None: skipset = set()
+ for item in iterable:
+ if item not in skipset:
+ skipset.add(item)
+ yield item
+
+
diff --git a/pypers/oxford/skip_rendundant.py b/pypers/oxford/skip_rendundant.py new file mode 100755 index 0000000..9c143d9 --- /dev/null +++ b/pypers/oxford/skip_rendundant.py @@ -0,0 +1,11 @@ +# skip_rendundant.py + +def skip_redundant(iterable, skipset=None): + "Redundant items are repeated items or items in the original skipset." + if skipset is None: skipset = set() + for item in iterable: + if item not in skipset: + skipset.add(item) + yield item + + diff --git a/pypers/oxford/super.py b/pypers/oxford/super.py new file mode 100755 index 0000000..5021a67 --- /dev/null +++ b/pypers/oxford/super.py @@ -0,0 +1,26 @@ +class B(object): + def __init__(self): + print "B.__init__" + +class C(B): + def __init__(self): + #super(C, self).__init__() + B.__init__(self) + print "C.__init__" + +###################################### + +class herB(object): + pass + +def __init__(self): + super(self.__class__, self).__init__() + print "herB.__init__" + +herB.__init__ = __init__ + +class herC(herB, C): + pass + + +c = herC() diff --git a/pypers/oxford/super2.py b/pypers/oxford/super2.py new file mode 100755 index 0000000..65ca9d0 --- /dev/null +++ b/pypers/oxford/super2.py @@ -0,0 +1,18 @@ +from magicsuper import object + +class B(object): + def __init__(self): + print "B.__init__" + super(B, self).__init__() + + +class C(B): + def __init__(self): + print "C.__init__" + callsupermethod() + + +c = C() + + + diff --git a/pypers/oxford/super_ex.py b/pypers/oxford/super_ex.py new file mode 100755 index 0000000..8a84fee --- /dev/null +++ b/pypers/oxford/super_ex.py @@ -0,0 +1,17 @@ +# super_ex.py
+
+from descriptor import AttributeDescriptor
+
+class B(object):
+ @staticmethod
+ def sm(): return "staticmethod"
+
+ @classmethod
+ def cm(cls): return cls.__name__
+
+ p = property()
+ a = AttributeDescriptor()
+
+class C(B): pass
+
+
diff --git a/pypers/oxford/super_old_new.py b/pypers/oxford/super_old_new.py new file mode 100755 index 0000000..f078ead --- /dev/null +++ b/pypers/oxford/super_old_new.py @@ -0,0 +1,11 @@ +# super_old_new.py
+class O:
+ def __init__(self):
+ print "O.__init__"
+
+class N(O, object):
+ def __init__(self):
+ print "N.__init__"
+ super(N, self).__init__()
+
+
diff --git a/pypers/oxford/supermeth.py b/pypers/oxford/supermeth.py new file mode 100755 index 0000000..a135a81 --- /dev/null +++ b/pypers/oxford/supermeth.py @@ -0,0 +1,40 @@ +def supermeth(meth): # not working with classmethods + name = meth.__name__ + cls = meth.im_class + inst = meth.im_self + print "****", type(meth), cls, inst + return getattr(super(cls, inst or cls), name) + +from super_ex import B + + +class C1(B): + pass + +class C2(B): + def __init__(self): + print "C2.__init__" + +class D(C1, C2): + def __init__(self): + print "D.__init__" + + + +class E(D): + def __init__(self): + print "E.__init__" + supermeth(E.__init__)(self) + @classmethod + def cm(cls): + print super(E, cls).cm() + return supermeth(E.cm)(cls) + @staticmethod + def sm(): + print super(E, E).sm() + return supermeth(E.sm)() + +e = E() +print +print E.sm() + diff --git a/pypers/oxford/threaded.py b/pypers/oxford/threaded.py new file mode 100755 index 0000000..4acf435 --- /dev/null +++ b/pypers/oxford/threaded.py @@ -0,0 +1,15 @@ +import threading +from decorators import decorator + +def deferred(nsec): + def inner_deferred(func, *args, **kw): + return threading.Timer(nsec, func, args, kw).start() + return decorator(inner_deferred) + + +@deferred(2) +def hello(): + print "hello" + +print "Calling hello ..." +hello() diff --git a/pypers/oxford/timed.py b/pypers/oxford/timed.py new file mode 100755 index 0000000..8d435f8 --- /dev/null +++ b/pypers/oxford/timed.py @@ -0,0 +1,43 @@ +# timed.py
+
+import sys, time
+
+class Timed(object):
+ """Decorator factory: each decorator object wraps a function and
+ executes it many times (default 100 times).
+ The average time spent in one iteration, expressed in milliseconds,
+ is stored in the attributes wrappedfunc.time and wrappedfunc.clocktime,
+ and displayed into a log file which defaults to stdout.
+ """
+ def __init__(self, repeat=100, logfile=sys.stdout):
+ self.repeat = repeat
+ self.logfile = logfile
+ def __call__(self, func):
+ def wrappedfunc(*args, **kw):
+ fullname = "%s.%s ..." % (func.__module__, func.func_name)
+ print >> self.logfile, 'Executing %s' % fullname.ljust(30),
+ time1 = time.time()
+ clocktime1 = time.clock()
+ for i in xrange(self.repeat):
+ res = func(*args,**kw) # executes func self.repeat times
+ time2 = time.time()
+ clocktime2 = time.clock()
+ wrappedfunc.time = 1000*(time2-time1)/self.repeat
+ wrappedfunc.clocktime = 1000*(clocktime2 - clocktime1)/self.repeat
+ print >> self.logfile, \
+ 'Real time: %s ms;' % self.rounding(wrappedfunc.time),
+ print >> self.logfile, \
+ 'Clock time: %s ms' % self.rounding(wrappedfunc.clocktime)
+ return res
+ wrappedfunc.func_name = func.func_name
+ wrappedfunc.__module__ = func.__module__
+ return wrappedfunc
+ @staticmethod
+ def rounding(float_):
+ "Three digits rounding for small numbers, 1 digit rounding otherwise."
+ if float_ < 10.:
+ return "%5.3f" % float_
+ else:
+ return "%5.1f" % float_
+
+
diff --git a/pypers/oxford/trace_builtin.py b/pypers/oxford/trace_builtin.py new file mode 100755 index 0000000..d5ebdd3 --- /dev/null +++ b/pypers/oxford/trace_builtin.py @@ -0,0 +1,18 @@ +# trace_builtin.py + +class TracedAccess1(object): + def __getattribute__(self, name): + print "1: accessing %s" % name + return super(TracedAccess1, self).__getattribute__(name) + +class TracedAccess2(object): + def __getattribute__(self, name): + print "2: accessing %s" % name + return super(TracedAccess2, self).__getattribute__(name) + +class B(object): + def __init__(self, *args): + super(B, self).__init__(*args) + + + diff --git a/pypers/oxford/traced.py b/pypers/oxford/traced.py new file mode 100755 index 0000000..4a6367e --- /dev/null +++ b/pypers/oxford/traced.py @@ -0,0 +1,13 @@ +# traced.py
+
+def traced(func):
+ def tracedfunc(*args, **kw):
+ print "calling %s.%s" % (func.__module__, func.__name__)
+ return func(*args, **kw)
+ tracedfunc.__name__ = func.__name__
+ return tracedfunc
+
+@traced
+def f(): pass
+
+
diff --git a/pypers/oxford/traced_function2.py b/pypers/oxford/traced_function2.py new file mode 100755 index 0000000..5942934 --- /dev/null +++ b/pypers/oxford/traced_function2.py @@ -0,0 +1,19 @@ +# traced_function2.py
+
+from decorators import decorator
+
+def trace(f, *args, **kw):
+ print "calling %s with args %s, %s" % (f.func_name, args, kw)
+ return f(*args, **kw)
+
+traced_function = decorator(trace)
+
+@traced_function
+def f1(x):
+ pass
+
+@traced_function
+def f2(x, y):
+ pass
+
+
diff --git a/pypers/oxford/tracedaccess.py b/pypers/oxford/tracedaccess.py new file mode 100755 index 0000000..51b4af1 --- /dev/null +++ b/pypers/oxford/tracedaccess.py @@ -0,0 +1,15 @@ +# tracedaccess.py
+
+class TracedAccess(object):
+ def __getattribute__(self, name):
+ print "Accessing %s" % name
+ return object.__getattribute__(self, name)
+
+
+class C(TracedAccess):
+ s = staticmethod(lambda : 'staticmethod')
+ c = classmethod(lambda cls: 'classmethod')
+ m = lambda self: 'method'
+ a = "hello"
+
+
diff --git a/pypers/oxford/transl.py b/pypers/oxford/transl.py new file mode 100755 index 0000000..9f764ea --- /dev/null +++ b/pypers/oxford/transl.py @@ -0,0 +1,23 @@ +def attributoTraducibile(**dic): + def get(self): + return dic[self.lingua] + def set(self, traduzione): + dic[self.lingua]= traduzione + return property(get, set) + +class Oggetto(object): + definizione = attributoTraducibile(it="vaso", en="potter") + tipologia = attributoTraducibile(it="antico", en="ancient") + +o = Oggetto() +o.lingua = "it" +print o.definizione +o.lingua = "en" +print o.definizione + +o.lingua = "it" +o.definizione = "Vaso" +print o.definizione +o.lingua = "en" +o.definizione = "Potter" +print o.definizione diff --git a/pypers/oxford/walk.py b/pypers/oxford/walk.py new file mode 100755 index 0000000..253e574 --- /dev/null +++ b/pypers/oxford/walk.py @@ -0,0 +1,18 @@ +# walk.py
+
+def walk(iterable, level=0):
+ for obj in iterable:
+ if not hasattr(obj, "__iter__"): # atomic object
+ yield obj, level
+ else: # composed object: iterate again
+ for subobj, lvl in walk(obj, level + 1):
+ yield subobj, lvl
+
+def flatten(iterable):
+ return (obj for obj, level in walk(iterable))
+
+def pprint(iterable):
+ for obj, level in walk(iterable):
+ print " "*level, obj
+
+
diff --git a/pypers/oxford/webapp.py b/pypers/oxford/webapp.py new file mode 100755 index 0000000..d93293e --- /dev/null +++ b/pypers/oxford/webapp.py @@ -0,0 +1,13 @@ +# webapp.py
+
+class WebApplication(object):
+ def __getattr__(self, name):
+ return name.capitalize()
+
+
+app = WebApplication()
+
+assert app.page1 == 'Page1'
+assert app.page2 == 'Page2'
+
+
diff --git a/pypers/oxford/why_super.py b/pypers/oxford/why_super.py new file mode 100755 index 0000000..2bbbeba --- /dev/null +++ b/pypers/oxford/why_super.py @@ -0,0 +1,27 @@ +# why_super.py
+
+class Base(object):
+ def __init__(self):
+ print "B.__init__"
+
+class MyClass(Base):
+ "I do not cooperate with others"
+ def __init__(self):
+ print "MyClass.__init__"
+ Base.__init__(self) #instead of super(MyClass, self).__init__()
+
+
+
+class Mixin(Base):
+ "I am cooperative with others"
+ def __init__(self):
+ print "Mixin.__init__"
+ super(Mixin, self).__init__()
+
+class HerClass(MyClass, Mixin):
+ "I am supposed to be cooperative too"
+ def __init__(self):
+ print "HerClass.__init__"
+ super(HerClass, self).__init__()
+
+
diff --git a/pypers/oxford/wraplist.py b/pypers/oxford/wraplist.py new file mode 100755 index 0000000..dc52136 --- /dev/null +++ b/pypers/oxford/wraplist.py @@ -0,0 +1,20 @@ +class ListWrapper(object): + def __init__(self, ls): + self._list = ls + def __getattr__(self, name): + if name == "__getitem__": + return self._list.__getitem__ + elif name == "reverse": + return self._list.reverse + else: + return name + +lw = ListWrapper([0,1,2]) + +print lw.x + +lw.reverse() +print lw.__getitem__(0) +print lw.__getitem__(1) +print lw.__getitem__(2) +print lw[0] diff --git a/pypers/oxford/x.py b/pypers/oxford/x.py new file mode 100755 index 0000000..7c6e342 --- /dev/null +++ b/pypers/oxford/x.py @@ -0,0 +1,24 @@ +class C(object): + x = 1 + #def __getattr__(self, name): + # print "Trying to access a non-existing attribute" + # return name + + def __getattribute__(self, name): + print "Trying to accessing %s" % name + return name + + @staticmethod + def f(): pass + + def m(self): pass + + + + + +c = C() + +print str(c) + +print getattr(c, "x") diff --git a/pypers/oxford/y.py b/pypers/oxford/y.py new file mode 100755 index 0000000..d0b153b --- /dev/null +++ b/pypers/oxford/y.py @@ -0,0 +1,11 @@ +class Point(tuple): + @staticmethod + def __new__(cls, x, y): + return tuple.__new__(cls, [x, y]) + def __init__(self, x, y): + self.x = x + self.y = y + +p = Point(1,2) + +print p.x, p.y diff --git a/pypers/oxford/z.py b/pypers/oxford/z.py new file mode 100755 index 0000000..41cde45 --- /dev/null +++ b/pypers/oxford/z.py @@ -0,0 +1,8 @@ +class MyStr(str): + def __new__(cls, arg): + return str.__new__(cls, 1) + + +print repr(MyStr("world")) + +print type(MyStr("world")) |