Python 3 Patterns Idioms
Python 3 Patterns Idioms
Release 1.0
Bruce Eckel
CONTENTS
1 2 3 4 5
Contributors 1.1 Thanks To . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ToDo List The remainder are from context, from the book. A Note To Readers Introduction 5.1 A Team Effort . . . . . . . 5.2 Not an Introductory Book 5.3 The License . . . . . . . . 5.4 The Printed Book . . . . . 5.5 Translations . . . . . . . . 5.6 My Motives . . . . . . . . Teaching Support Book Development Rules 7.1 Contribute What You Can 7.2 Dont Get Attached . . . . 7.3 Credit . . . . . . . . . . . 7.4 Mechanics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1 1 3 5 7 9 9 10 10 11 11 12 13 15 15 16 16 16
6 7
7.5 8
Diagrams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 21 22 22 23 24 24 25 25 27 27 31 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 34 35 36 37 38 43 45 46 47 48 51 53 55 55 57 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 59 60 60 61
Developer Guide 8.1 Getting Started: The Easiest Approach . . . . 8.2 For Windows Users . . . . . . . . . . . . . . . 8.3 Installing Sphinx . . . . . . . . . . . . . . . . . 8.4 Getting the Development Branch of the Book . 8.5 Building the Book . . . . . . . . . . . . . . . . 8.6 Building the PDF . . . . . . . . . . . . . . . . . 8.7 Setting up Mercurial . . . . . . . . . . . . . . . 8.8 Working with BitBucket and Mercurial . . . . 8.9 A Simple Overview Of Editing and Merging . 8.10 Emacs for Editing Restructured Text . . . . . . Part I: Foundations
10 Quick Python for Programmers 10.1 Scripting vs. Programming 10.2 Built-In Containers . . . . . 10.3 Functions . . . . . . . . . . 10.4 Strings . . . . . . . . . . . . 10.5 Classes . . . . . . . . . . . .
11 Unit Testing & Test-Driven Development 11.1 Write Tests First . . . . . . . . . . . . . 11.2 Simple Python Testing . . . . . . . . . 11.3 A Very Simple Framework . . . . . . 11.4 Writing Tests . . . . . . . . . . . . . . 11.5 White-Box & Black-Box Tests . . . . . 11.6 Running tests . . . . . . . . . . . . . . 11.7 Automatically Executing Tests . . . . 11.8 Exercises . . . . . . . . . . . . . . . . . 12 Python 3 Language Changes 13 Decorators 13.1 Decorators vs. the Decorator Pattern . 13.2 History of Macros . . . . . . . . . . . 13.3 The Goal of Macros . . . . . . . . . . . 13.4 What Can You Do With Decorators? . ii
Function Decorators . . . . . . . . . . . . . . . . Slightly More Useful . . . . . . . . . . . . . . . . Using Functions as Decorators . . . . . . . . . . Review: Decorators without Arguments . . . . Decorators with Arguments . . . . . . . . . . . . Decorator Functions with Decorator Arguments Further Reading . . . . . . . . . . . . . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
61 63 64 66 67 69 71 73
15 Comprehensions 75 15.1 A More Complex Example . . . . . . . . . . . . . . . . . . . . . . . 75 16 Coroutines & Concurrency 81 16.1 Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 17 Jython 17.1 Installation . . . . . . . . . . . . . 17.2 Scripting . . . . . . . . . . . . . . . 17.3 Interpreter Motivation . . . . . . . 17.4 Using Java libraries . . . . . . . . . 17.5 Controlling Java from Jython . . . 17.6 Controlling the Interpreter . . . . 17.7 Creating Java classes with Jython 17.8 Summary . . . . . . . . . . . . . . 17.9 Exercises . . . . . . . . . . . . . . . 18 Part II: Idioms 19 Discovering the Details About Your Platform 20 A Canonical Form for Command-Line Programs 21 Messenger/Data Transfer Object 22 Part III: Patterns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 84 85 87 90 93 97 107 113 114 117 119 121 123 125
23 The Pattern Concept 127 23.1 What is a Pattern? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127 23.2 Classifying Patterns . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
iii
23.3 Pattern Taxonomy . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130 23.4 Design Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131 23.5 Design Principles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132 24 The Singleton 135 24.1 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141 25 Building Application Frameworks 143 25.1 Template Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143 25.2 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144 26 Fronting for an Implementation 145 26.1 Proxy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146 26.2 State . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147 27 StateMachine 151 27.1 Table-Driven State Machine . . . . . . . . . . . . . . . . . . . . . . . 158 27.2 Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168 27.3 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168 28 Decorator: Dynamic Type Selection 28.1 Basic Decorator Structure . . . 28.2 A Coffee Example . . . . . . . 28.3 Class for Each Combination . . 28.4 The Decorator Approach . . . 28.5 Compromise . . . . . . . . . . 28.6 Other Considerations . . . . . 28.7 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171 172 172 173 175 178 181 181
29 Iterators: Decoupling Algorithms from Containers 183 29.1 Type-Safe Iterators . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184 30 Factory: Encapsulating Object Creation 30.1 Simple Factory Method . . . . . . 30.2 Polymorphic Factories . . . . . . . 30.3 Abstract Factories . . . . . . . . . 30.4 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187 188 191 193 196
31 Function Objects 199 31.1 Command: Choosing the Operation at Runtime . . . . . . . . . . . 199
iv
31.2 Strategy: Choosing the Algorithm at Runtime . . . . . . . . . . . . 201 31.3 Chain of Responsibility . . . . . . . . . . . . . . . . . . . . . . . . . 202 31.4 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206 32 Changing the Interface 207 32.1 Adapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207 32.2 Faade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209 32.3 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210 33 Table-Driven Code: Conguration Flexibility 211 33.1 Table-Driven Code Using Anonymous Inner Classes . . . . . . . . 211 34 Observer 213 34.1 Observing Flowers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215 35 Multiple Dispatching 231
36 Visitor 237 36.1 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239 37 Pattern Refactoring 37.1 Simulating the Trash Recycler . . . 37.2 Improving the Design . . . . . . . . 37.3 A Pattern for Prototyping Creation . 37.4 Abstracting Usage . . . . . . . . . . 37.5 Multiple Dispatching . . . . . . . . 37.6 The Visitor Pattern . . . . . . . . . . 37.7 RTTI Considered Harmful? . . . . . 37.8 Summary . . . . . . . . . . . . . . . 37.9 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241 241 245 249 258 262 270 279 282 284
38 Projects 285 38.1 Rats & Mazes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285 39 Indices and tables Index 291 293
vi
CHAPTER
ONE
CONTRIBUTORS
List of contributors. Note: This needs some thought. I want to include everyone who makes a contribution, but Id also like to indicate people who have made larger contributions theres no automatic way to do this now that we have moved to BitBucket and are using Wikis to allow people to make contributions more simply in the beginning.
1.1 Thanks To
BitBucket.org and the creators of Mercurial Creator(s) of Sphinx And of course, Guido and the team for their incessant improvement of Python, especially for taking the risk in breaking backward compatibility in Python 3.0 to refactor the language.
Todo Yarko (example label of ToDo): update CSS styles for todos & todo lists; 1
look at http://sphinx.pocoo.org/ext/coverage.html for example. Autogenerated ToDoLists do not appear in LaTeX output - debug, x; DONE: - ToDo does not appear to be created by make dependencies (its autogenerated); - update Makele to always re-generate todo lists;
Chapter 1. Contributors
CHAPTER
TWO
TODO LIST
Currently, this doesnt seem to link into the index, as Id hoped. Rene Printed Book and Translations Code extractor for rst les (maybe part of intro chapter?) Code updater to put code in/refresh code into book. Move frontmatter into its own directory <!> Seems to be a warning sign (but initial tests didnt work) Idea: decorator on a dictionary object, to turn it into an ordered dictionary. Other resources at the end of each chapter For print version, convert hyperlinks into footnotes. build tool for this, or check int rst handling of this - see if it works with Sphinx;
CHAPTER
THREE
hg branch lp:python3patterns hg commit -m initial checkout (hack, hack, hack....) hg merge (pull new updates) hg commit -m checkin after merge... ... and so on... (The original entry is located in DeveloperGuide.rst, line 204 and can be found here.) Todo Someone who knows more about emacs for Linux please add more specic information about the windowed version(s). (The original entry is located in DeveloperGuide.rst, line 299 and can be found here.) Todo rewrite to distinguish python generator from above description, or choose different name. (The original entry is located in Jython.rst, line 496 and can be found here.)
CHAPTER
FOUR
A NOTE TO READERS
What you see here is an early version of the book. We have yet to get everything working right and rewritten for Python. Or even to get the book testing systems in place. If youre here because youre curious, thats great. But please dont expect too much from the book just yet. When we get it to a point where everything compiles and all the Java references, etc. have been rewritten out, then this note will disappear. Until then, caveat emptor.
CHAPTER
FIVE
INTRODUCTION
5.1 A Team Effort
This book is an experiment to see if we can combine everyones best efforts to create something great. You can nd the contributors right before this introduction. They are listed in order of Karma Points, a system Launchpad.net uses to keep track of contributions from everyone working on an open-source project. In my case, I will write new material, as well as rewriting other contributions to clarify and give voice, setting up the architecture and being the Benevolent Dictator for the book. But I denitely wont be doing everything; my goal is that this is a team project and that everyone who wants to will have something useful to contribute. Well be using Launchpad.nets Blueprints facility to add features to the book, so thats where youll nd the main repository of things to do. What can you contribute? Anything as small as spelling and grammatical correctons, and as large as a whole chapter. Research into new topics and creating examples is what takes me the most time, so if you know something already or are willing to gure it out, dont worry if your writing or programming isnt perfect contribute it and I and the rest of the group will improve it. You may also have talents in guring things out. Sphinx formatting, for example, or how to produce camera-ready formatting. These are all very useful things which will not only benet this book but also any future book built on this tem9
plate (every bit of the build system for the book will be out in the open, so if you want to take what weve done here and start your own book, you can). Of course, not everything can make it into the nal print book, but things that dont t into the main book can be moved into an appendix book or a volume 2 book or something like that.
10
Chapter 5. Introduction
specify that anything you think of or create at any time of day or night belongs to the company).
5.5 Translations
Launchpad.net, where this project is hosted, has support for doing translations and this gave me an idea. I had just come back from speaking at the Python conference in Brazil, and was thinking about the user group there and how I might support them. (We had done a seminar while I was there in order to help pay for my trip and support the organization). If the book can be kept in a Sphinx restructured text format that can be turned directly into camera-ready PDF (the basic form is there but it will take somebody messing about with it to get it into camera-ready layout), then the job of translation can be kept to something that could be done by user groups during sprints. The user group could then use a print-on-demand service to print the book, and get the group members to take them to local bookstores and do other kinds of promotions. The prots from the book could go to the user group (who knows, just like the Brazillian group, your group may end up using some of those prots to bring me to speak at your conference!). If possible, I would like a royalty from these translations. To me, 5% of the cover price sounds reasonable. If the user group would like to use my cover, then they 5.4. The Printed Book 11
could pay this royalty. If they wanted to go their own way, its creative commons so as long as the book is attributed thats their choice.
5.6 My Motives
Just so its clear, I have the following motives for creating this book: 1. Learn more about Python and contribute to the Python community, to help create more and better Python programmers. 2. Develop more Python consulting and training clients through the publicity generated by the book (see here). 3. Experiment with group creation of teaching materials for the book, which will benet me in my own training (see the previous point) but will also benet anyone choosing to use the book as a text in a course or training seminar. (See Teaching Support). 4. Generate prots by selling printed books. (But see above about the ability to print the book yourself). 5. Help raise money for non-U.S. Python user groups via translations, from which I might gain a small percentage.
12
Chapter 5. Introduction
CHAPTER
SIX
TEACHING SUPPORT
Teachers and lecturers often need support material to help them use a book for teaching. I have put some exercises in, and I hope we can add more. Id also like to create teaching materials like slides and exercises as a group effort with a creative commons license, especially by allying with those people who are in the teaching professions. Id like to make it something that everyone would like to teach from including myself. Here are some places to get ideas for exercises and projects: For coding dojos Rosetta Code
13
14
CHAPTER
SEVEN
15
You probably have ideas about things youd like to understand, that havent been covered elsewhere. And sometimes people are particularly good at spotting typos.
7.3 Credit
As much as possible, I want to give credit to contributions. Much of this will be taken care of automatically by the Launchpad.net Karma system. However, if you contribute something signicant, for example the bulk of a new chapter, then you should put contributed by at the beginning of that chapter, and if you make signicant improvements and changes to a chapter you should say further contributions by or further changes by, accordingly.
7.4 Mechanics
Automate everything. Everything should be in the build script; nothing should be done by hand. 16 Chapter 7. Book Development Rules
All documents will be in Sphinx restructured text format. Heres the link to the Sphinx documentation. Everything goes through Launchpad.net and uses Launchpads Bazzar distributed version control system. Follow PEP8 for style. That way we dont have to argue about it. Camelcasing for naming. PEP8 suggests underscores as a preference rather than a hard-and fast rule, and camelcasing feels more like OO to me, as if we are emphasizing the design here (which I want to do) and putting less focus on the C-ish nature that can be expressed in Python.
The above point is still being debated.
Four space indents. Were not using chapter numbers because well be moving chapters around. If you need to cross-reference a chapter, use the chapter name and a link. Index as you go. Indexing will happen throughout the project. Although nalizing the index is a task in itself, it will be very helpful if everyone adds index entries anytime they occur to you. You can nd example index entries by going to the index, clicking on one of the entries, then selecting view source in the left-side bar (Sphinx cleverly shows you the Sphinx source so you can use it as an example). Dont worry about chapter length. Some chapters may be very small, others may be quite signicant. Its just the nature of this book. Trying to make the chapters the same length will end up ufng some up which will not benet the reader. Make the chapters however long they need to be, but no longer.
7.5 Diagrams
Create diagrams using whatever tool is convenient for you, as long as it produces formats that Sphinx can use.
7.5. Diagrams
17
It doesnt matter if your diagram is imperfect. Even if you just sketch something by hand and scan it in, it will help readers visualize whats going on. At some point, diagrams will be redone for consistency using a single tool, with print publication in mind. This tool may be a commercial product. However, if you need to change the diagram you can replace it with your new version using your tool of choice. The important thing is to get the diagram right; at some point it will be redone to look good. Note that all image tags should use a * at the end, not the le extension name. For example ..image:: _images/foo.*. This way the tag will work for both the HTML output and the Latex output. Also, all images should be placed in the _images directory. Heres an example which was done with the free online service Gliffy.com, then modied using the free Windows program Paint.NET (note, however, that we should not use color because it wont translate well to the print book):
18
7.5. Diagrams
19
20
CHAPTER
EIGHT
DEVELOPER GUIDE
Details for people participating in the book development process.
21
When youre ready, you can learn more about Sphinx and Mercurial and begin making contributions that way. The following sections are for those who are ready to build the book on their own machines.
22
$ hg clone http://www.bitbucket.org/birkenfeld/sphinx/ and to update, use: $ hg pull Once you update, run $ python setup.py install (You can repeat this step whenever you need to update). We may talk about minimum version numbers to process the book. Check your version with: $ hg identify -n The full anouncement from Georg (Sphinx creator) is here: http://groups.google.com/group/sphinxdev/browse_thread/thread/6dd415847e5cbf7c Mercurial Cheat sheets & quick starts should be enough to answer your questions: http://edong.net/2008v1/docs/dongwoo-Hg-120dpi.png http://www.ivy.fr/mercurial/ref/v1.0/
Linux: Your Linux install may already have support, but if not, install following these instructions: http://www.tug.org/texlive/quickinstall.html Once TeX is installed, move to this books src directory and run make latex. When that command runs successfully, it will give you instructions as to how to nish. 24 Chapter 8. Developer Guide
In addition, you can change the editor that Mercurial uses via an environment variable. For example, on OSX and Linux (and Windows with cygwin) you add this to your .bash_prole to set emacs as the default editor:
export set EDITOR=/usr/bin/emacs
25
cloned the trunk to your local machine: - hg clone https://my_login@bitbucket.org/BruceEckel/python-3-patterns-idioms/ cloned your local copy of trunk to create a working directory: - hg clone python-3-patterns-idioms devel Todo This section still work in progress: hg branch lp:python3patterns hg commit -m initial checkout (hack, hack, hack....) hg merge (pull new updates) hg commit -m checkin after merge... ... and so on... When you have a new function idea, or think youve found a bug, ask Bruce on the group. If you have a new feature, create a wiki page on BitBucket and describe what youre going to do. If you have found a bug, make a bug report on BitBucket (later assign it to yourself, and link your branch to it); If you want to work on a project, look for an unassigned bug and try to work it out - then proceed as below... When you are ready to share your work have others review, register a branch. Note: You can re-use one branch for multiple bug xes. 1. Sign up for an account on BitBucket.org 2. Go to the project and select register branch (https://code.BitBucket.org/python3patterns/+addbranch). Suggest you create a hosted branch, then you can work locally, and pull/push as you make progress (see http://doc.Mercurialvcs.org/latest/en/user-guide/index.html#organizing). 26 Chapter 8. Developer Guide
3. Once you have registered your branch, BitBucket will provide you with instructions on how to pull and push to your personal development copy. 4. Link your bug report or blueprint to your branch. 5. Merge from your parent (the trunk, or others you are working with) as needed. 6. Push your working copy to BitBucket as your work is ready for others to review or test. 7. Once you are done making your changes, have completed testing, and are ready for the project team to inspect & test, please select propose for merging 8. Somebody on the core team will make a test merge (it may include merging with other patches). Once tests pass, and your branch is accepted, it will be merged into the trunk.
has a long and venerable history, and is an extremely powerful editor. Emacs also has versions that are customized for operating systems to make it much more familiar. Heres a simple introduction to emacs and a useful introductory help guide. For Windows, theres a special FAQ. Mac OSX: Comes with built-in emacs which you can invoke from the command line. For a nicer version, install Aquamacs, which looks and feels like a native Mac application. Windows: You can download the latest windows installer here (choose the highest numbered zip le with bin in the name). This blog gives useful tips to make emacs on Windows even friendlier (in particular, it puts emacs on the right-click menu and improves the startup settings). Linux: Its virtually guaranteed that you already have emacs preinstalled on your Linux distribution, which you can start from a command prompt. However, there may also be more windowy versions that you can install separately. Todo Someone who knows more about emacs for Linux please add more specic information about the windowed version(s). Finally, heres the documentation for installing and using the emacs restructuredtext mode. The elisp code it refers to is in the le rst.el. To customize your emacs, you need to open the .emacs le. The above Windows FAQ tells you how to put your .emacs le somewhere else, but the easiest thing to do is just open emacs and inside it type C-x C-f ~/.emacs, which will open your default .emacs le if you have one, or create a new one if you dont. Youll need to install rst.el someplace emacs will nd it. Heres an example .emacs le which adds a local directory called ~/emacs/ to the search path, (so you can put .el les there) and also automatically starts rst mode for les with extensions of rst and .rest:
(require cl) (defvar emacs-directory "~/emacs/" "The directory containing the emacs configuration files.") (pushnew (expand-file-name emacs-directory) load-path) (require rst)
28
29
30
CHAPTER
NINE
PART I: FOUNDATIONS
31
32
CHAPTER
TEN
33
The # denotes a comment that goes until the end of the line, just like C++ and Java // comments. First notice that the basic syntax of Python is C-ish as you can see in the if statement. But in a C if, you would be required to use parentheses around the conditional, whereas they are not necessary in Python (it wont complain if you use them anyway). The conditional clause ends with a colon, and this indicates that what follows will be a group of indented statements, which are the then part of the if statement. In this case, there is a print statement which sends the result to standard output, followed by an assignment to a variable named val. The subsequent
34
statement is not indented so it is no longer part of the if. Indenting can nest to any level, just like curly braces in C++ or Java, but unlike those languages there is no option (and no argument) about where the braces are placed - the compiler forces everyones code to be formatted the same way, which is one of the main reasons for Pythons consistent readability. Python normally has only one statement per line (you can put more by separating them with semicolons), thus no terminating semicolon is necessary. Even from the brief example above you can see that the language is designed to be as simple as possible, and yet still very readable.
The rst line creates a list. You can print the list and it will look exactly as you put it in (in contrast, remember that I had to create a special Arrays2 class in Thinking in Java in order to print arrays in Java). Lists are like Java containers - you can add new elements to them (here, append( ) is used) and they will automatically resize themselves. The for statement creates an iterator x which takes on each value in the list.
35
You can create a list of numbers with the range( ) function, so if you really need to imitate Cs for, you can. Notice that there arent any type declarations - the object names simply appear, and Python infers their type by the way that you use them. Its as if Python is designed so that you only need to press the keys that absolutely must. Youll nd after youve worked with Python for a short while that youve been using up a lot of brain cycles parsing semicolons, curly braces, and all sorts of other extra verbiage that was demanded by your non-Python programming language but didnt actually describe what your program was supposed to do.
10.3 Functions
To create a function in Python, you use the def keyword, followed by the function name and argument list, and a colon to begin the function body. Here is the rst example turned into a function:
# QuickPython/myFunction.py def myFunction(response): val = 0 if response == "yes": print("affirmative") val = 1 print("continuing...") return val print(myFunction("no")) print(myFunction("yes"))
Notice there is no type information in the function signature - all it species is the name of the function and the argument identiers, but no argument types or return types. Python is a structurally-typed language, which means it puts the minimum possible requirements on typing. For example, you could pass and return different types from the same function:
# QuickPython/differentReturns.py def differentReturns(arg): if arg == 1:
36
The only constraints on an object that is passed into the function are that the function can apply its operations to that object, but other than that, it doesnt care. Here, the same function applies the + operator to integers and strings:
# QuickPython/sum.py def sum(arg1, arg2): return arg1 + arg2 print(sum(42, 47)) print(sum(spam , "eggs"))
When the operator + is used with strings, it means concatenation (yes, Python supports operator overloading, and it does a nice job of it).
10.4 Strings
The above example also shows a little bit about Python string handling, which is the best of any language Ive seen. You can use single or double quotes to represent strings, which is very nice because if you surround a string with double quotes, you can embed single quotes and vice versa:
# QuickPython/strings.py print("That isnt a horse") print(You are not a "Viking") print("""Youre just pounding two coconut halves together.""") print("Oh no!" He exclaimed. "Its the blemange!") print(rc:\python\lib\utils)
10.4. Strings
37
Note that Python was not named after the snake, but rather the Monty Python comedy troupe, and so examples are virtually required to include Python-esque references. The triple-quote syntax quotes everything, including newlines. This makes it particularly useful for doing things like generating web pages (Python is an especially good CGI language), since you can just triple-quote the entire page that you want without any other editing. The r right before a string means raw, which takes the backslashes literally so you dont have to put in an extra backslash in order to insert a literal backslash. Substitution in strings is exceptionally easy, since Python uses Cs printf() substitution syntax, but for any string at all. You simply follow the string with a % and the values to substitute:
# QuickPython/stringFormatting.py val = 47 print("The number is %d" % val) val2 = 63.4 s = "val: %d, val2: %f" % (val, val2) print(s)
As you can see in the second case, if you have more than one argument you surround them in parentheses (this forms a tuple, which is a list that cannot be modied - you can also use regular lists for multiple arguments, but tuples are typical). All the formatting from printf() is available, including control over the number of decimal places and alignment. Python also has very sophisticated regular expressions.
10.5 Classes
Like everything else in Python, the denition of a class uses a minimum of additional syntax. You use the class keyword, and inside the body you use def to create methods. Heres a simple class:
38
# QuickPython/SimpleClass.py class Simple: def __init__(self, str): print("Inside the Simple constructor") self.s = str # Two methods: def show(self): print(self.s) def showMsg(self, msg): print(msg + :, self.show()) # Calling another method if __name__ == "__main__": # Create an object: x = Simple("constructor argument") x.show() x.showMsg("A message")
Both methods have self as their rst argument. C++ and Java both have a hidden rst argument in their class methods, which points to the object that the method was called for and can be accessed using the keyword this. Python methods also use a reference to the current object, but when you are dening a method you must explicitly specify the reference as the rst argument. Traditionally, the reference is called self but you could use any identier you want (if you do not use self you will probably confuse a lot of people, however). If you need to refer to elds in the object or other methods in the object, you must use self in the expression. However, when you call a method for an object as in x.show( ), you do not hand it the reference to the object - that is done for you. Here, the rst method is special, as is any identier that begins and ends with double underscores. In this case, it denes the constructor, which is automatically called when the object is created, just like in C++ and Java. However, at the bottom of the example you can see that the creation of an object looks just like a function call using the class name. Pythons spare syntax makes you realize that the new keyword isnt really necessary in C++ or Java, either. All the code at the bottom is set off by an if clause, which checks to see if something called __name__ is equivalent to __main__. Again, the double underscores indicate special names. The reason for the if is that any le can also be used as a library module within another program (modules are described shortly). In that
10.5. Classes
39
case, you just want the classes dened, but you dont want the code at the bottom of the le to be executed. This particular if statement is only true when you are running this le directly; that is, if you say on the command line:
Python SimpleClass.py
However, if this le is imported as a module into another program, the __main__ code is not executed. Something thats a little surprising at rst is that while in C++ or Java you declare object level elds outside of the methods, you do not declare them in Python. To create an object eld, you just name it - using self - inside of one of the methods (usually in the constructor, but not always), and space is created when that method is run. This seems a little strange coming from C++ or Java where you must decide ahead of time how much space your object is going to occupy, but it turns out to be a very exible way to program. If you declare elds using the C++/Java style, they implicitly become class level elds (similar to the static elds in C++/Java)
10.5.1 Inheritance
Because Python is dynamically typed, it doesnt really care about interfaces - all it cares about is applying operations to objects (in fact, Javas interface keyword would be wasted in Python). This means that inheritance in Python is different from inheritance in C++ or Java, where you often inherit simply to establish a common interface. In Python, the only reason you inherit is to inherit an implementation - to re-use the code in the base class. If youre going to inherit from a class, you must tell Python to bring that class into your new le. Python controls its name spaces as aggressively as Java does, and in a similar fashion (albeit with Pythons penchant for simplicity). Every time you create a le, you implicitly create a module (which is like a package in Java) with the same name as that le. Thus, no package keyword is needed in Python. When you want to use a module, you just say import and give the name of the module. Python searches the PYTHONPATH in the same way that Java searches the CLASSPATH (but for some reason, Python doesnt have the same kinds of pitfalls as Java does) and reads in the le. To refer to any of the functions or classes within a module, you give the module name, a period, and the function or class name. If you dont want the trouble of qualifying the name, you can say 40 Chapter 10. Quick Python for Programmers
from module import name(s) Where name(s) can be a list of names separated by commas. You inherit a class (or classes - Python supports multiple inheritance) by listing the name(s) of the class inside parentheses after the name of the inheriting class. Note that the Simple class, which resides in the le (and thus, module) named SimpleClass is brought into this new name space using an import statement:
# QuickPython/Simple2.py from SimpleClass import Simple class Simple2(Simple): def __init__(self, str): print("Inside Simple2 constructor") # You must explicitly call # the base-class constructor: Simple.__init__(self, str) def display(self): self.showMsg("Called from display()") # Overriding a base-class method def show(self): print("Overridden show() method") # Calling a base-class method from inside # the overridden method: Simple.show(self) class Different: def show(self): print("Not derived from Simple") if __name__ == "__main__": x = Simple2("Simple2 constructor argument") x.display() x.show() x.showMsg("Inside main") def f(obj): obj.show() # One-line definition f(x) f(Different())
Note: you dont have to explicitly call the base-class constructor if the argument list is the same. Show example.
10.5. Classes
41
Note: (Reader) The note above is confusing. Did not understand. IMHO one still needs to invoke the base-class constructor if the argument is the same. Probably one needs to state that in case the base class constructor functionality continues to be adequate for the derived class, then a new constructor need not be declared for the derived class at all. Simple2 is inherited from Simple, and in the constructor, the base-class constructor is called. In display( ), showMsg( ) can be called as a method of self, but when calling the base-class version of the method you are overriding, you must fully qualify the name and pass self in as the rst argument, as shown in the base-class constructor call. This can also be seen in the overridden version of show( ). In __main__, you will see (when you run the program) that the base-class constructor is called. You can also see that the showMsg( ) method is available in the derived class, just as you would expect with inheritance. The class Different also has a method named show( ), but this class is not derived from Simple. The f( ) method dened in __main__ demonstrates weak typing: all it cares about is that show( ) can be applied to obj, and it doesnt have any other type requirements. You can see that f( ) can be applied equally to an object of a class derived from Simple and one that isnt, without discrimination. If youre a C++ programmer, you should see that the objective of the C++ template feature is exactly this: to provide weak typing in a strongly-typed language. Thus, in Python you automatically get the equivalent of templates without having to learn that particularly difcult syntax and semantics. Note: (Reader) I am not sure if I agree with the remark about templates. One of the big objective of templates has always been type safety along with genericity. What python gives us is the genericity. IMHO the analogy with template mechanism is not appropriate. Note: Suggest Further Topics for inclusion in the introductory chapter
42
CHAPTER
ELEVEN
43
considered a development pattern, but perhaps there are enough pattern phrases in the world already. Its effect on development is so signicant that it will be used throughout this book, and thus will be introduced here. My own experience with unit testing began when I realized that every program in a book must be automatically extracted and organized into a source tree, along with appropriate makeles (or some equivalent technology) so that you could just type make to build the whole tree. The effect of this process on the code quality of the book was so immediate and dramatic that it soon became (in my mind) a requisite for any programming book-how can you trust code that you didnt compile? I also discovered that if I wanted to make sweeping changes, I could do so using search-and-replace throughout the book, and also bashing the code around at will. I knew that if I introduced a aw, the code extractor and the makeles would ush it out. As programs became more complex, however, I also found that there was a serious hole in my system. Being able to successfully compile programs is clearly an important rst step, and for a published book it seemed a fairly revolutionary one-usually due to the pressures of publishing, its quite typical to randomly open a programming book and discover a coding aw. However, I kept getting messages from readers reporting semantic problems in my code (in Thinking in Java). These problems could only be discovered by running the code. Naturally, I understood this and had taken some early faltering steps towards implementing a system that would perform automatic execution tests, but I had succumbed to the pressures of publishing, all the while knowing that there was denitely something wrong with my process and that it would come back to bite me in the form of embarrassing bug reports (in the open source world, embarrassment is one of the prime motivating factors towards increasing the quality of ones code!). The other problem was that I was lacking a structure for the testing system. Eventually, I started hearing about unit testing and Junit 1 , which provided a basis for a testing structure. However, even though JUnit is intended to make the creation of test code easy, I wanted to see if I could make it even easier, applying the Extreme Programming principle of do the simplest thing that could possibly work as a starting point, and then evolving the system as usage demands (In addition, I wanted to try to reduce the amount of test code, in an attempt to t more functionality in less code for screen presentations). This chapter is the result.
1
http://www.junit.org
44
45
Just run this from the root directory of the code listings for the book; it will descend into each subdirectory and run the program there. An easy way to check things is to redirect standard output to a le, then if there are any errors they will be the only thing that appears at the console during program execution.
46
The only testing method [[ So far ]] is afrm( ) , which is protected so that it can be used from the inheriting class. All this method does is verify that something is true. If not, it adds an error to the list, reporting that the current test (established
47
by the static testID, which is set by the test-running program that you shall see shortly) has failed. Although this is not a lot of information-you might also wish to have the line number, which could be extracted from an exception-it may be enough for most situations. Unlike JUnit (which uses setUp( ) and tearDown( ) methods), test objects will be built using ordinary Python construction. You dene the test objects by creating them as ordinary class members of the test class, and a new test class object will be created for each test method (thus preventing any problems that might occur from side effects between tests). Occasionally, the creation of a test object will allocate non-memory resources, in which case you must override cleanup( ) to release those resources.
48
print("TestDemo.testA") affirm(test1.someCondition()) def testB(self): print("TestDemo.testB") affirm(test2.someCondition()) affirm(TestDemo.objCounter != 0) # Causes the build to halt: #! def test3(): affirm(0)
The test3( ) method is commented out because, as youll see, it causes the automatic build of this books source-code tree to stop. You can name your inner class anything youd like; the only important factor is that it extends UnitTest. You can also include any necessary support code in other methods. Only public methods that take no arguments and return void will be treated as tests (the names of these methods are also not constrained). The above test class creates two instances of TestDemo. The TestDemo constructor prints something, so that we can see it being called. You could also dene a default constructor (the only kind that is used by the test framework), although none is necessary here. The TestDemo class has a close( ) method which suggests it is used as part of object cleanup, so this is called in the overridden cleanup( ) method in Test. The testing methods use the afrm( ) method to validate expressions, and if there is a failure the information is stored and printed after all the tests are run. Of course, the afrm( ) arguments are usually more complicated than this; youll see more examples throughout the rest of this book. Notice that in testB( ), the private eld objCounter is accessible to the testing code-this is because Test has the permissions of an inner class. You can see that writing test code requires very little extra effort, and no knowledge other than that used for writing ordinary classes. To run the tests, you use RunUnitTests.py (which will be introduced shortly). The command for the above code looks like this: java com.bruceeckel.test.RunUnitTests TestDemo It produces the following output:
49
test1: count = test2: count = TestDemo.testA Cleaning up: 2 Cleaning up: 1 test1: count = test2: count = TestDemo.testB Cleaning up: 4 Cleaning up: 3
1 2
3 4
All the output is noise as far as the success or failure of the unit testing is concerned. Only if one or more of the unit tests fail does the program returns a non-zero value to terminate the make process after the error messages are produced. Thus, you can choose to produce output or not, as it suits your needs, and the test class becomes a good place to put any printing code you might need- if you do this, you tend to keep such code around rather than putting it in and stripping it out as is typically done with tracing code. If you need to add a test to a class derived from one that already has a test class, its no problem, as you can see here:
# UnitTesting/TestDemo2.py # Inheriting from a class that # already has a test is no problem. class TestDemo2(TestDemo): def __init__(self, s): TestDemo.__init__(s) # You can even use the same name # as the test class in the base class: class Test(UnitTest): def testA(self): print("TestDemo2.testA") affirm(1 + 1 == 2) def testB(self): print("TestDemo2.testB") affirm(2 * 2 == 4)
50
Even the name of the inner class can be the same. In the above code, all the assertions are always true so the tests will never fail.
51
Normally, the only method that should be directly accessible to the client programmer is f4( ). However, if you put your black-box test in the same directory, it automatically becomes part of the same package (in this case, the default package since none is specied) and then has inappropriate access:
# UnitTesting/TooMuchAccess.py class TooMuchAccess(UnitTest): Testable tst = Testable() def test1(self): tst.f2() # Oops! tst.f3() # Oops! tst.f4() # OK
You can solve the problem by moving TooMuchAccess.py into its own subdirectory, thereby putting it in its own default package (thus a different package from Testable.py). Of course, when you do this, then Testable must be in its own package, so that it can be imported (note that it is also possible to import a package-less class by giving the class name in the import statement and ensuring that the class is in your CLASSPATH):
# UnitTesting/testable/Testable.py package c02.testable class Testable: def f1(): pass def f2(self): # "Friendly": package access def f3(self): # Also package access def f4(self):
Heres the black-box test in its own package, showing how only public methods may be called:
# UnitTesting/BlackBoxTest.py class BlackBoxTest(UnitTest): Testable tst = Testable() def test1(self): #! tst.f2() # Nope!
52
Note that the above program is indeed very similar to the one that the client programmer would write to use your class, including the imports and available methods. So it does make a good programming example. Of course, its easier from a coding standpoint to just make an inner class, and unless youre ardent about the need for specic black-box testing you may just want to go ahead and use the inner classes (with the knowledge that if you need to you can later extract the inner classes into separate black-box test classes, without too much effort).
53
if(!UnitTest.class. isAssignableFrom(classes[j])) continue ut = classes[j] break # Finds the first test class only # If it found an inner class, # that class must be static: if(ut != null) require( Modifier.isStatic(ut.getModifiers()), "inner UnitTest class must be static") # If it couldnt find the inner class, # maybe its a regular class (for black# box testing: if(ut == null) if(UnitTest.class.isAssignableFrom(c)) ut = c require(ut != null, "No UnitTest class found") require( Modifier.isPublic(ut.getModifiers()), "UnitTest class must be public") Method[] methods = ut.getDeclaredMethods() for(int k = 0 k < methods.length k++): Method m = methods[k] # Ignore overridden UnitTest methods: if(m.getName().equals("cleanup")) continue # Only public methods with no # arguments and void return # types will be used as test code: if(m.getParameterTypes().length == 0 && m.getReturnType() == void.class && Modifier.isPublic(m.getModifiers())): # The name of the test is # used in error messages: UnitTest.testID = m.getName() # A instance of the # test object is created and # cleaned up for each test:
54
Object test = ut.newInstance() m.invoke(test, Object[0]) ((UnitTest)test).cleanup() except e: e.printStackTrace(System.err) # Any exception will return a nonzero # value to the console, so that # make will abort: System.err.println("Aborting make") System.exit(1) # After all tests in this class are run, # display any results. If there were errors, # abort make by returning a nonzero value. if(UnitTest.errors.size() != 0): it = UnitTest.errors.iterator() while(it.hasNext()): print(it.next()) sys.exit(1)
55
I had originally called this assert(), but that word became reserved in JDK 1.4 when assertions were added to the language.
56
CHAPTER
TWELVE
57
58
CHAPTER
THIRTEEN
DECORATORS
Note: This chapter is a work in progress; its probably better if you dont begin making changes until Ive nished the original version, which is being posted as a series on my weblog. This amazing feature appeared in the language almost apologetically and with concern that it might not be that useful. I predict that in time it will be seen as one of the more powerful features in the language. The problem is that all the introductions to decorators that I have seen have been rather confusing, so I will try to rectify that here.
59
60
When the compiler passes over this code, aFunction() is compiled and the resulting function object is passed to the myDecorator code, which does something to produce a function-like object that is then substituted for the original aFunction(). What does the myDecorator code look like? Well, most introductory examples show this as a function, but Ive found that its easier to start understanding decorators by using classes as decoration mechanisms instead of functions. In addition, its more powerful.
61
The only constraint upon the object returned by the decorator is that it can be used as a function which basically means it must be callable. Thus, any classes we use as decorators must implement __call__. What should the decorator do? Well, it can do anything but usually you expect the original function code to be used at some point. This is not required, however:
# PythonDecorators/my_decorator.py class my_decorator(object): def __init__(self, f): print("inside my_decorator.__init__()") f() # Prove that function definition has completed def __call__(self): print("inside my_decorator.__call__()") @my_decorator def aFunction(): print("inside aFunction()") print("Finished decorating aFunction()") aFunction()
Notice that the constructor for my_decorator is executed at the point of decoration of the function. Since we can call f() inside __init__(), it shows that the creation of f() is complete before the decorator is called. Note also that the decorator constructor receives the function object being decorated. Typically, youll capture the function object in the constructor and later use it in the __call__() method (the fact that decoration and calling are two clear phases when using classes is why I argue that its easier and more powerful this way).
62
When aFunction() is called after it has been decorated, we get completely different behavior; the my_decorator.__call__() method is called instead of the original code. Thats because the act of decoration replaces the original function object with the result of the decoration in our case, the my_decorator object replaces aFunction. Indeed, before decorators were added you had to do something much less elegant to achieve the same thing:
def foo(): pass foo = staticmethod(foo)
With the addition of the @ decoration operator, you now get the same result by saying:
@staticmethod def foo(): pass
This is the reason why people argued against decorators, because the @ is just a little syntax sugar meaning pass a function object through another function and assign the result to the original function. The reason I think decorators will have such a big impact is because this little bit of syntax sugar changes the way you think about programming. Indeed, it brings the idea of applying code to other code (i.e.: macros) into mainstream thinking by formalizing it as a language construct.
63
self.f() print("Exited", self.f.__name__) @entry_exit def func1(): print("inside func1()") @entry_exit def func2(): print("inside func2()") func1() func2()
You can see that the decorated functions now have the Entering and Exited trace statements around the call. The constructor stores the argument, which is the function object. In the call, we use the __name__ attribute of the function to display that functions name, then call the function itself.
64
# PythonDecorators/entry_exit_function.py def entry_exit(f): def new_f(): print("Entering", f.__name__) f() print("Exited", f.__name__) return new_f @entry_exit def func1(): print("inside func1()") @entry_exit def func2(): print("inside func2()") func1() func2() print(func1.__name__)
new_f() is dened within the body of entry_exit(), so it is created and returned when entry_exit() is called. Note that new_f() is a closure, because it captures the actual value of f. Once new_f() has been dened, it is returned from entry_exit() so that the decorator mechanism can assign the result as the decorated function. The output of the line print(func1.__name__) is new_f, because the new_f function has been substituted for the original function during decoration. If this is a problem you can change the name of the decorator function before you return it:
def entry_exit(f): def new_f(): print("Entering", f.__name__) f() print("Exited", f.__name__) new_f.__name__ = f.__name__ return new_f
65
The information you can dynamically get about functions, and the modications you can make to those functions, are quite powerful in Python.
66
Any arguments for the decorated function are just passed to __call__(). The output is:
Inside __init__() After decoration Preparing to call sayHello() Inside __call__() sayHello arguments: say hello argument list After self.f(*args) After first sayHello() call Inside __call__() sayHello arguments: a different set of arguments After self.f(*args) After second sayHello() call
Notice that __init__() is the only method called to perform decoration, and __call__() is called every time you call the decorated sayHello().
67
self.arg3 = arg3 def __call__(self, f): """ If there are decorator arguments, __call__() is only called once, as part of the decoration process! You can only give it a single argument, which is the function object. """ print("Inside __call__()") def wrapped_f(*args): print("Inside wrapped_f()") print("Decorator arguments:", self.arg1, self.arg2, self.arg3) f(*args) print("After f(*args)") return wrapped_f @decorator_with_arguments("hello", "world", 42) def sayHello(a1, a2, a3, a4): print(sayHello arguments:, a1, a2, a3, a4) print("After decoration") print("Preparing to call sayHello()") sayHello("say", "hello", "argument", "list") print("after first sayHello() call") sayHello("a", "different", "set of", "arguments") print("after second sayHello() call")
From the output, we can see that the behavior changes quite signicantly:
Inside __init__() Inside __call__() After decoration Preparing to call sayHello() Inside wrapped_f() Decorator arguments: hello world 42 sayHello arguments: say hello argument list After f(*args) after first sayHello() call Inside wrapped_f() Decorator arguments: hello world 42
68
sayHello arguments: a different set of arguments After f(*args) after second sayHello() call
Now the process of decoration calls the constructor and then immediately invokes __call__(), which can only take a single argument (the function object) and must return the decorated function object that replaces the original. Notice that __call__() is now only invoked once, during decoration, and after that the decorated function that you return from __call__() is used for the actual calls. Although this behavior makes sense the constructor is now used to capture the decorator arguments, but the object __call__() can no longer be used as the decorated function call, so you must instead use __call__() to perform the decoration it is nonetheless surprising the rst time you see it because its acting so much differently than the no-argument case, and you must code the decorator very differently from the no-argument case.
69
print(sayHello arguments:, a1, a2, a3, a4) print("After decoration") print("Preparing to call sayHello()") sayHello("say", "hello", "argument", "list") print("after first sayHello() call") sayHello("a", "different", "set of", "arguments") print("after second sayHello() call")
The return value of the decorator function must be a function used to wrap the function to be decorated. That is, Python will take the returned function and call it at decoration time, passing the function to be decorated. Thats why we have three levels of functions; the inner one is the actual replacement function. Because of closures, wrapped_f() has access to the decorator arguments arg1, arg2 and arg3, without having to explicitly store them as in the class version. However, this is a case where I nd explicit is better than implicit, so even though the function version is more succinct I nd the class version easier to understand and thus to modify and maintain.
70
71
72
CHAPTER
FOURTEEN
73
74
CHAPTER
FIFTEEN
COMPREHENSIONS
History: where did they come from? They require a mind shift. What makes them so compelling (once you get it)? A two-level list comprehension using os.walk():
# Comprehensions/os_walk_comprehension.py import os restFiles = [os.path.join(d[0], f) for d in os.walk(".") for f in d[2] if f.endswith(".rst")] for r in restFiles: print(r)
75
files. You can just put in the codeMarker and the (indented) first line (containing file path) into your restructured text file, then run the update program to automatically insert the rest of the file. """ import os, re, sys, shutil, inspect, difflib
restFiles = [os.path.join(d[0], f) for d in os.walk(".") if not "_test" in d[ for f in d[2] if f.endswith(".rst")] class Languages: "Strategy design pattern" class Python: codeMarker = "::\n\n" commentTag = "#" listings = re.compile("::\n\n( {4}#.*(?:\n+ {4}.*)*)") class Java: codeMarker = ".. code-block:: java\n\n" commentTag = "//" listings = \ re.compile(".. *code-block:: *java\n\n( {4}//.*(?:\n+ {4}.*)*)")
def shift(listing): "Shift the listing left by 4 spaces" return [x[4:] if x.startswith(" ") else x for x in listing.splitlines(
# TEST - makes duplicates of the rst files in a test directory to test update dirs = set([os.path.join("_test", os.path.dirname(f)) for f in restFiles]) if [os.makedirs(d) for d in dirs if not os.path.exists(d)]: [shutil.copy(f, os.path.join("_test", f)) for f in restFiles] testFiles = [os.path.join(d[0], f) for d in os.walk("_test") for f in d[2] if f.endswith(".rst")] class Commands: """ Each static method can be called from the command line. Add a new static method here to add a new command to the program. """
76
@staticmethod def display(language): """ Print all the code listings in the .rst files. """ for f in restFiles: listings = language.listings.findall(open(f).read()) if not listings: continue print(= * 60 + "\n" + f + "\n" + = * 60) for n, l in enumerate(listings): print("\n".join(shift(l))) if n < len(listings) - 1: print(- * 60) @staticmethod def extract(language): """ Pull the code listings from the .rst files and write each listing into its own file. Will not overwrite if code files and .rst files disagree unless you say "extract -force". """ force = len(sys.argv) == 3 and sys.argv[2] == -force paths = set() for listing in [shift(listing) for f in restFiles for listing in language.listings.findall(open(f).read())]: path = listing[0][len(language.commentTag):].strip() if path in paths: print("ERROR: Duplicate file name: %s" % path) sys.exit(1) else: paths.add(path) path = os.path.join("..", "code", path) dirname = os.path.dirname(path) if dirname and not os.path.exists(dirname): os.makedirs(dirname) if os.path.exists(path) and not force: for i in difflib.ndiff(open(path).read().splitlines(), listing): if i.startswith("+ ") or i.startswith("- "): print("ERROR: Existing file different from .rst") print("Use extract -force to force overwrite") Commands.check(language)
77
@staticmethod def check(language): """ Ensure that external code files exist and check which external files have changed from whats in the .rst files. Generate files in the _deltas subdirectory showing what has changed. """ class Result: # Messenger def __init__(self, **kwargs): self.__dict__ = kwargs result = Result(missing = [], deltas = []) listings = [Result(code = shift(code), file = f) for f in restFiles for code in language.listings.findall(open(f).read())] paths = [os.path.normpath(os.path.join("..", "code", path)) for path [listing.code[0].strip()[len(language.commentTag):].strip for listing in listings]] if os.path.exists("_deltas"): shutil.rmtree("_deltas") for path, listing in zip(paths, listings): if not os.path.exists(path): result.missing.append(path) else: code = open(path).read().splitlines() for i in difflib.ndiff(listing.code, code): if i.startswith("+ ") or i.startswith("- "): d = difflib.HtmlDiff() if not os.path.exists("_deltas"): os.makedirs("_deltas") html = os.path.join("_deltas", os.path.basename(path).split(.)[0] + ".html") open(html, w).write( "<html><h1>Left: %s<br>Right: %s</h1>" % (listing.file, path) + d.make_file(listing.code, code)) result.deltas.append(Result(file = listing.file, path = path, html = html, code = code)) break
78
if result.missing: print("Missing %s files:\n%s" % (language.__name__, "\n".join(result.missing))) for delta in result.deltas: print("%s changed in %s; see %s" % (delta.file, delta.path, delta.html)) return result @staticmethod def update(language): # Test until it is trustworthy """ Refresh external code files into .rst files. """ check_result = Commands.check(language) if check_result.missing: print(language.__name__, "update aborted") return changed = False def _update(matchobj): listing = shift(matchobj.group(1)) path = listing[0].strip()[len(language.commentTag):].strip() filename = os.path.basename(path).split(.)[0] path = os.path.join("..", "code", path) code = open(path).read().splitlines() return language.codeMarker + \ "\n".join([(" " + line).rstrip() for line in listing]) for f in testFiles: updated = language.listings.sub(_update, open(f).read()) open(f, w).write(updated) if __name__ == "__main__": commands = dict(inspect.getmembers(Commands, inspect.isfunction)) if len(sys.argv) < 2 or sys.argv[1] not in commands: print("Command line options:\n") for name in commands: print(name + ": " + commands[name].__doc__) else: for language in inspect.getmembers(Languages, inspect.isclass): commands[sys.argv[1]](language[1])
79
80
CHAPTER
SIXTEEN
81
82
CHAPTER
SEVENTEEN
JYTHON
Note: This chapter is being brought up to date with Jython 2.5, and will need changes when Jython 3 comes out. Note: Some of the descriptions in this chapter are introductory, so that the material can be used to introduce Java programmers to Jython. Sometimes its easier and faster to temporarily step into another language to solve a particular aspect of your problem. This chapter looks at the value of crossing language boundaries. It is often advantageous to solve a problem using more than one programming language; as youll see, a problem that is very difcult or tedious to solve in one language can often be solved quickly and easily in another. By combining languages, you can create your product much more quickly and cheaply. One use of this idea is the Interpreter design pattern, which adds an interpreted language to your program to allow the end user to easily customize a solution. If the application user needs greater run time exibility, for example to create scripts describing the desired behavior of the system, you can use Interpreter by creating and embedding a language interpreter into your program. In Java, the easiest and most powerful way to do this is with Jython 1 , an implementation of Python in pure Java byte codes. As you will see, this brings together the benets of both worlds. Jython is generated entirely in Java byte codes, so incorporating it into your apThe original version of this was called JPython, but the project changed and the name was changed to emphasize the distinctness of the new version.
1
83
plication is quite simple, and its as portable as Java is. It has an extremely clean interface with Java: Java can call Python classes, and Python can call Java classes. Because Jython is just Java classes, it can often be stealthed into companies that have rigid processes for using new languges and tools. If Java has been accepted, such companies often accept anything that runs on the JVM without question. The Python/Jython language can be freely embedded into your for-prot application without signing any license agreement, paying royalties, or dealing with strings of any kind. There are basically no restrictions when youre using Python/Jython. Python is designed with classes from the ground up and provides pure support for object-oriented programming (both C++ and Java violate purity in various ways). Python scales up so that you can create large programs without losing control of the code. Java projects have been quickly created using Jython, then later optimized by rewriting into Java sections of the Jython code that have proled as bottlenecks.
17.1 Installation
To install Jython, go to http://jython.sourceforge.net. Note: Select test the beta. The download is a .class le, which will run an installer when you execute it using java -jar. You also need the Java Development Kit (JDK), and to add jython-complete.jar to your Java CLASSPATH. As an example, here is the appropriate section in my .bashrc for *nix; for Windows you need to do the equivalent:
export set JYTHON_HOME="/Users/bruceeckel/jython2.5b0" export set CLASSPATH=.:..:$JYTHON_HOME/jython-complete.jar
When you run Jython, you might get the warning: cant create package cache dir, /cachedir/packages. Jython will still work, but startup will be slower because caching isnt happening. Jython caching requires /cachedir/packages/ in the python.home directory. It is often the case on *nix that users lack sufcient priveleges to create or write to this directory. Be-
84
cause the problem is merely permissions, something like mkdir cachedir; chmod a+rw cachedir within the Jython directory should eliminate this warning message.
Then just invoke ant against the build.xml le. dist/bin/jython is a shell script that starts up jython in console mode. Lastly, modify the registry (in dist/registry) so that:
python.console=org.python.util.ReadlineConsole python.console.readlinelib=GnuReadline
(readline is GPL, so it makes it a bit harder to automate this part of the distro). See: http://wiki.python.org/jython/ReadlineSetup
17.2 Scripting
One compelling benet of using a dynamic language on the JVM is scripting. You can rapidly create and test code, and solve problems more quickly. Heres an example that shows a little of what you can do in a Jython script, and also gives you a sense of performance:
# Jython/Simple.py import platform, glob, time from subprocess import Popen, PIPE print platform.uname() # What are we running on? print glob.glob("*.py") # Find files with .py extensions # Send a command to the OS and capture the results:
17.2. Scripting
85
print Popen(["ping", "-c", "1", "www.mindview.net"], stdout=PIPE).communicate()[0] # Time an operation: start = time.time() for n in xrange(1000000): for i in xrange(10): oct(i) print time.time() - start
Note: The timeit module in the alpha distribution could not be used as it tries to turn off the Java garbage collector. If you run this program under both cpython and Jython, youll see that the timed loop produces very similar results; Jython 2.5 is in beta so this is quite impressive and should get faster theres even talk that Jython could run faster than cpython, because of the optimization benets of the JVM. The total runtime of the cpython version is faster because of its rapid startup time; the JVM always has a delay for startup. Note that things that are very quick to write in Jython require much more code (and often research) in Java. Heres an example that uses a Python list comprehension with the os.walk() function to visit all the directories in a directory tree, and nd all the les with names that end in .java and contain the word PythonInterpreter:
# Jython/Walk_comprehension.py import os restFiles = [os.path.join(d[0], f) for d in os.walk(".") for f in d[2] if f.endswith(".java") and "PythonInterpreter" in open(os.path.join(d[0], f)).read()] for r in restFiles: print(r)
You can certainly achieve this in Java. It will just take a lot longer. Often more sophisticated programs begin as scripts, and then evolve. The fact that you can quickly try things out allows you to test concepts, and then create more rened code as needed.
86
87
self.time = time Event.events.append(self) def __cmp__ (self, """ So sort() will """ if self.time < if self.time > return 0 other): compare only on time. other.time: return -1 other.time: return 1
def run(self): print("%.2f: %s" % (self.time, self.action)) class LightOn(Event): def __init__(self, time): Event.__init__(self, "Light on", time) class LightOff(Event): def __init__(self, time): Event.__init__(self, "Light off", time) class WaterOn(Event): def __init__(self, time): Event.__init__(self, "Water on", time) class WaterOff(Event): def __init__(self, time): Event.__init__(self, "Water off", time) class ThermostatNight(Event): def __init__(self, time): Event.__init__(self,"Thermostat night", time) class ThermostatDay(Event): def __init__(self, time): Event.__init__(self, "Thermostat day", time) class Bell(Event): def __init__(self, time): Event.__init__(self, "Ring bell", time)
88
def run(): Event.events.sort(); for e in Event.events: e.run() if __name__ == "__main__": ThermostatNight(5.00) LightOff(2.00) WaterOn(3.30) WaterOff(4.45) LightOn(1.00) ThermostatDay(6.00) Bell(7.00) run()
Note: To run this program say python GreenHouseLanguage.py or jython GreenHouseLanguage.py. The constructor of each derived class calls the base-class constructor, which adds the new object to the list. The run() function sorts the list, which automatically uses the __cmp__() method dened in Event to base comparisons on time only. In this example, it only prints out the list, but in the real system it would wait for the time of each event to come up and then run the event. The __main__ section performs a simple test on the classes. The above le which is an ordinary Python program is now a module that can be included in another Python program. But instead of using it in an ordinary Python program, lets use Jython, inside of Java. This turns out to be remarkably simple: you import some Jython classes, create a PythonInterpreter object, and cause the Python les to be loaded:
// Jython/GreenHouseController.java import org.python.core.*; import org.python.util.PythonInterpreter; public class GreenHouseController { public static void main(String[] args) throws PyException PythonInterpreter interp = new PythonInterpreter(); System.out.println("Loading GreenHouse Language"); interp.execfile("GreenHouseLanguage.py");
89
The PythonInterpreter object is a complete Python interpreter that accepts commands from the Java program. One of these commands is execle(), which tells it to execute all the statements it nds in a particular le. By executing GreenHouseLanguage.py, all the classes from that le are loaded into our PythonInterpreter object, and so it now holds the greenhouse controller language. The Schedule.ghs le is the one created by the end user to control the greenhouse. Heres an example:
# Jython/Schedule.ghs Bell(7.00) ThermostatDay(6.00) WaterOn(3.30) LightOn(1.00) ThermostatNight(5.00) LightOff(2.00) WaterOff(4.45)
This is the goal of the interpreter design pattern: to make the conguration of your program as simple as possible for the end user. With Jython you can achieve this with almost no effort at all. One of the other methods available to the PythonInterpreter is exec(), which allows you to send a command to the interpreter. In the above program, the run() function is called using exec().
90
If you compare the Java version of the program to the above Jython implementation, youll see that Jython is shorter and generally easier to understand. For example, to set up the frame in the Java version you had to make several calls: the constructor for JFrame(), the setVisible() method and the setDefaultCloseOperation() method, whereas in the above code all three of these operations are performed with a single constructor call. Also notice that the JButton is congured with an actionListener() method inside the constructor, with the assignment to kapow. In addition, Jythons JavaBean awareness means that a call to any method with a name that begins with set can be replaced with an assignment, as you see above. The only method that did not come over from Java is the pack() method, which seems to be essential in order to force the layout to happen properly. Its also important that the call to pack() appear before the size setting.
91
MyDialog is inherited from JDialog, and you can see named arguments being used in the call to the base-class constructor. In the creation of the OK JButton, note that the actionPerformed method is set right inside the constructor, and that the function is created using the Python lambda keyword. This creates a nameless function with the arguments appearing before the colon and the expression that generates the returned value after the colon. As you should know, the Java prototype for the actionPerformed() method only contains a single argument, but the lambda expression indicates two. However, the second argument is provided with a default value, so the function can be called with only one argument. The reason for the second argument is seen in the default value, because this is a way to pass self into the
92
lambda expression, so that it can be used to dispose of the dialog. Compare this code with the version thats published in Thinking in Java. Youll nd that Python language features allow a much more succinct and direct implementation.
93
jset.add(y) print(jmap) print(jset) # Iterating through a set: for z in jset: print(z, z.__class__) print(jmap[3]) # Uses Python dictionary indexing for x in jmap.keySet(): # keySet() is a Map method print(x, jmap[x]) # Using a Java class that you create yourself is # just as easy: jc = JavaClass() jc2 = JavaClass("Created within Jython") print(jc2.getVal()) jc.setVal("Using a Java class is trivial") print(jc.getVal()) print(jc.getChars()) jc.val = "Using bean properties" print(jc.val)
Todo rewrite to distinguish python generator from above description, or choose different name. Note that the import statements map to the Java package structure exactly as you would expect. In the rst example, a Date() object is created as if it were a native Python class, and printing this object just calls toString(). ValGen implements the concept of a generator which is used a great deal in the C++ STL (Standard Template Library, part of the Standard C++ Library). A generator is an object that produces a new object every time its generation method is called, and it is quite convenient for lling containers. Here, I wanted to use it in a for iteration, and so I needed the generation method to be the one that is called by the iteration process. This is a special method called __getitem__(), which is actually the overloaded operator for indexing, [ ]. A for loop calls this method every time it wants to move the iteration forward, and when the elements run
94
out, __getitem__() throws an out-of-bounds exception and that signals the end of the for loop (in other languages, you would never use an exception for ordinary control ow, but in Python it seems to work quite well). This exception happens automatically when self.val[i] runs out of elements, so the __getitem__() code turns out to be simple. The only complexity is that __getitem__() appears to return two objects instead of just one. What Python does is automatically package multiple return values into a tuple, so you still only end up returning a single object (in C++ or Java you would have to create your own data structure to accomplish this). In addition, in the for loop where ValGen is used, Python automatically unpacks the tuple so that you can have multiple iterators in the for. These are the kinds of syntax simplications that make Python so endearing. The jmap and jset objects are instances of Javas HashMap and HashSet, again created as if those classes were just native Python components. In the for loop, the put() and add() methods work just like they do in Java. Also, indexing into a Java Map uses the same notation as for dictionaries, but note that to iterate through the keys in a Map you must use the Map method keySet() rather than the Python dictionary method keys(). The nal part of the example shows the use of a Java class that I created from scratch, to demonstrate how trivial it is. Notice also that Jython intuitively understands JavaBeans properties, since you can either use the getVal() and setVal() methods, or assign to and read from the equivalent val property. Also, getChars() returns a Character[] in Java, and this automatically becomes an array in Python. The easiest way to use Java classes that you create for use inside a Python program is to put them inside a package. Although Jython can also import unpackaged java classes (import JavaClass), all such unpackaged java classes will be treated as if they were dened in different packages so they can only see each others public methods. Java packages translate into Jython modules, and Jython must import a module in order to be able to use the Java class. Here is the Java code for JavaClass:
// Jython/javaclass/JavaClass.java package Jython.javaclass; import java.util.*; public class JavaClass { private String s = ""; public JavaClass() {
95
System.out.println("JavaClass()"); } public JavaClass(String a) { s = a; System.out.println("JavaClass(String)"); } public String getVal() { System.out.println("getVal()"); return s; } public void setVal(String a) { System.out.println("setVal()"); s = a; } public Character[] getChars() { System.out.println("getChars()"); Character[] r = new Character[s.length()]; for(int i = 0; i < s.length(); i++) r[i] = new Character(s.charAt(i)); return r; } public static void main(String[] args) { JavaClass x1 = new JavaClass(), x2 = new JavaClass("UnitTest"); System.out.println(x2.getVal()); x1.setVal("SpamEggsSausageAndSpam"); System.out.println(Arrays.toString(x1.getChars())); } }
You can see that this is just an ordinary Java class, without any awareness that it will be used in a Jython program. For this reason, one of the important uses of Jython is in testing Java code . Because Python is such a powerful, exible, dynamic language it is an ideal tool for automated test frameworks, without making any changes to the Java code thats being tested.
96
Non-static inner classes must have an outer class instance supplied explicitly as the rst argument:
com.foo.JavaClass.InnerClass(com.foo.JavaClass())
97
PythonInterpreter interp = new PythonInterpreter(); // It automatically converts Strings // into native Python strings: interp.set("a", "This is a test"); interp.exec("print(a)"); interp.exec("print(a[5:])"); // A slice // It also knows what to do with arrays: String[] s = { "How", "Do", "You", "Do?" }; interp.set("b", s); interp.exec("for x in b: print(x[0], x)"); // set() only takes Objects, so it cant // figure out primitives. Instead, // you have to use wrappers: interp.set("c", new PyInteger(1)); interp.set("d", new PyFloat(2.2)); interp.exec("print(c + d)"); // You can also use Javas object wrappers: interp.set("c", new Integer(9)); interp.set("d", new Float(3.14)); interp.exec("print(c + d)"); // Define a Python function to print arrays: interp.exec( "def prt(x): \n" + " print(x)\n" + " for i in x: \n" + " print(i,)\n" + " print(x.__class__)\n"); // Arrays are Objects, so it has no trouble // figuring out the types contained in arrays: Object[] types = { new boolean[]{ true, false, false, true }, new char[]{ a, b, c, d }, new byte[]{ 1, 2, 3, 4 }, new int[]{ 10, 20, 30, 40 }, new long[]{ 100, 200, 300, 400 }, new float[]{ 1.1f, 2.2f, 3.3f, 4.4f }, new double[]{ 1.1, 2.2, 3.3, 4.4 }, }; for(int i = 0; i < types.length; i++) { interp.set("e", types[i]); interp.exec("prt(e)");
98
} // It uses toString() to print Java objects: interp.set("f", new Date()); interp.exec("print(f)"); // You can pass it a List // and index into it... List x = new ArrayList(); for(int i = 0; i < 10; i++) x.add(new Integer(i * 10)); interp.set("g", x); interp.exec("print(g)"); interp.exec("print(g[1])"); // ... But its not quite smart enough // to treat it as a Python array: interp.exec("print(g.__class__)"); // interp.exec("print(g[5:])"); // Fails // must extract the Java array: System.out.println("ArrayList to array:"); interp.set("h", x.toArray()); interp.exec("print(h.__class__)"); interp.exec("print(h[5:])"); // Passing in a Map: Map m = new HashMap(); m.put(new Integer(1), new Character(a)); m.put(new Integer(3), new Character(b)); m.put(new Integer(5), new Character(c)); m.put(new Integer(7), new Character(d)); m.put(new Integer(11), new Character(e)); System.out.println("m: " + m); interp.set("m", m); interp.exec("print(m, m.__class__," + "m[1], m[1].__class__)"); // Not a Python dictionary, so this fails: //! interp.exec("for x in m.keys():" + //! "print(x, m[x])"); // To convert a Map to a Python dictionary, use PyUtil: interp.set("m", PyUtil.toPyDictionary(m)); interp.exec("print(m, m.__class__, " + "m[1], m[1].__class__)"); interp.exec("for x in m.keys():print(x,m[x])"); }
99
As usual with Java, the distinction between real objects and primitive types causes trouble. In general, if you pass a regular object to set(), it knows what to do with it, but if you want to pass in a primitive you must perform a conversion. One way to do this is to create a Py type, such as PyInteger or PyFloat. but it turns out you can also use Javas own object wrappers like Integer and Float, which is probably going to be a lot easier to remember. Early in the program youll see an exec() containing the Python statement:
print(a[5:])
The colon inside the indexing statement indicates a Python slice, which produces a range of elements from the original array. In this case, it produces an array containing the elements from number 5 until the end of the array. You could also say a[3:5] to produce elements 3 through 5, or a[:5] to produce the elements zero through 5. The reason a slice is used in this statement is to make sure that the Java String has really been converted to a Python string, which can also be treated as an array of characters. You can see that its possible, using exec(), to create a Python function (although its a bit awkward). The prt() function prints the whole array, and then (to make sure its a real Python array), iterates through each element of the array and prints it. Finally, it prints the class of the array, so we can see what conversion has taken place (Python not only has run-time type information, it also has the equivalent of Java reection). The prt() function is used to print arrays that come from each of the Java primitive types. Although a Java ArrayList does pass into the interpreter using set(), and you can index into it as if it were an array, trying to create a slice fails. To completely convert it into an array, one approach is to simply extract a Java array using toArray(), and pass that in. The set() method converts it to a PyArray one of the classes provided with Jython which can be treated as a Python array (you can also explicitly create a PyArray, but this seems unnecessary). Finally, a Map is created and passed directly into the interpreter. While it is possible to do simple things like index into the resulting object, its not a real Python dictionary so you cant (for example) call the keys() method. There is no straightforward way to convert a Java Map into a Python dictionary, and
100
so I wrote a utility called toPyDictionary() and made it a static method of net.mindview.python.PyUtil. This also includes utilities to extract a Python array into a Java List, and a Python dictionary into a Java Map:
// Jython/PyUtil.java // PythonInterpreter utilities import org.python.util.PythonInterpreter; import org.python.core.*; import java.util.*; public class PyUtil { /** Extract a Python tuple or array into a Java List (which can be converted into other kinds of lists and sets inside Java). @param interp The Python interpreter object @param pyName The id of the python list object */ public static List toList(PythonInterpreter interp, String pyName){ return new ArrayList(Arrays.asList( (Object[])interp.get( pyName, Object[].class))); } /** Extract a Python dictionary into a Java Map @param interp The Python interpreter object @param pyName The id of the python dictionary */ public static Map toMap(PythonInterpreter interp, String pyName){ PyList pa = ((PyDictionary)interp.get( pyName)).items(); Map map = new HashMap(); while(pa.__len__() != 0) { PyTuple po = (PyTuple)pa.pop(); Object first = po.__finditem__(0) .__tojava__(Object.class); Object second = po.__finditem__(1) .__tojava__(Object.class); map.put(first, second); } return map; }
101
/** Turn a Java Map into a PyDictionary, suitable for placing into a PythonInterpreter @param map The Java Map object */ public static PyDictionary toPyDictionary(Map map) { Map m = new HashMap(); Iterator it = map.entrySet().iterator(); while(it.hasNext()) { Map.Entry e = (Map.Entry)it.next(); m.put(Py.java2py(e.getKey()), Py.java2py(e.getValue())); } return new PyDictionary(m); } }
102
public void test4() { Map m = new HashMap(); m.put("twas", new Integer(11)); m.put("brillig", new Integer(27)); m.put("and", new Integer(47)); m.put("the", new Integer(42)); m.put("slithy", new Integer(33)); m.put("toves", new Integer(55)); System.out.println(m); pi.set("m", PyUtil.toPyDictionary(m)); pi.exec("print(m)"); pi.exec("print(m[slithy])"); } public static void main(String args[]) { TestPyUtil test = new TestPyUtil(); test.test1(); test.test2(); test.test3(); test.test4(); } }
Well see the use of the extraction tools in the next section.
lating strings and les, and so you will commonly want to extract the results as an array of strings. For example, you can do a wildcard expansion of le names using Pythons glob(), as shown further down in the following code:
// Jython/PythonInterpreterGetting.java // Getting data from the PythonInterpreter object. import org.python.util.PythonInterpreter; import org.python.core.*; import java.util.*; public class PythonInterpreterGetting { public static void main(String[] args) throws PyException { PythonInterpreter interp = new PythonInterpreter(); interp.exec("a = 100"); // If you just use the ordinary get(), // it returns a PyObject: PyObject a = interp.get("a"); // Theres not much you can do with a generic // PyObject, but you can print it out: System.out.println("a = " + a); // If you know the type its supposed to be, // you can "cast" it using __tojava__() to // that Java type and manipulate it in Java. // To use a as an int, you must use // the Integer wrapper class: int ai= ((Integer)a.__tojava__(Integer.class)) .intValue(); // There are also convenience functions: ai = Py.py2int(a); System.out.println("ai + 47 = " + (ai + 47)); // You can convert it to different types: float af = Py.py2float(a); System.out.println("af + 47 = " + (af + 47)); // If you try to cast it to an inappropriate // type youll get a runtime exception: //! String as = (String)a.__tojava__( //! String.class); // If you know the type, a more useful method // is the overloaded get() that takes the // desired class as the 2nd argument:
104
interp.exec("x = 1 + 2"); int x = ((Integer)interp .get("x", Integer.class)).intValue(); System.out.println("x = " + x); // Since Python is so good at manipulating // strings and files, you will often need to // extract an array of Strings. Here, a file // is read as a Python array: interp.exec("lines = " + "open(PythonInterpreterGetting.java)" + ".readlines()"); // Pull it in as a Java array of String: String[] lines = (String[]) interp.get("lines", String[].class); for(int i = 0; i < 10; i++) System.out.print(lines[i]); // As an example of useful string tools, // global expansion of ambiguous file names // using glob is very useful, but its not // part of the standard Jython package, so // youll have to make sure that your // Python path is set to include these, or // that you deliver the necessary Python // files with your application. interp.exec("from glob import glob"); interp.exec("files = glob(*.java)"); String[] files = (String[]) interp.get("files", String[].class); for(int i = 0; i < files.length; i++) System.out.println(files[i]); // You can extract tuples and arrays into // Java Lists with net.mindview.PyUtil: interp.exec("tup = (fee, fi, fo, fum, fi)"); List tup = PyUtil.toList(interp, "tup"); System.out.println(tup); // It really is a list of String objects: System.out.println(tup.get(0).getClass()); // You can easily convert it to a Set:
105
Set tups = new HashSet(tup); System.out.println(tups); interp.exec("ints=[1,3,5,7,9,11,13,17,19]"); List ints = PyUtil.toList(interp, "ints"); System.out.println(ints); // It really is a List of Integer objects: System.out.println((ints.get(1)).getClass()); // If you have a Python dictionary, it can // be extracted into a Java Map, again with // net.mindview.PyUtil: interp.exec("dict = { 1 : a, 3 : b," + "5 : c, 9 : d, 11 : e }"); Map map = PyUtil.toMap(interp, "dict"); System.out.println("map: " + map); // It really is Java objects, not PyObjects: Iterator it = map.entrySet().iterator(); Map.Entry e = (Map.Entry)it.next(); System.out.println(e.getKey().getClass()); System.out.println(e.getValue().getClass()); } }
The last two examples show the extraction of Python tuples and lists into Java Lists, and Python dictionaries into Java Maps. Both of these cases require more processing than is provided in the standard Jython library, so I have again created utilities in net.mindview.pyton.PyUtil: toList() to produce a List from a Python sequence, and toMap() to produce a Map from a Python dictionary. The PyUtil methods make it easier to take important data structures back and forth between Java and Python.
106
import org.python.util.PythonInterpreter; import org.python.core.*; public class MultipleJythons { public static void main(String[] args) throws PyException { PythonInterpreter interp1 = new PythonInterpreter(), interp2 = new PythonInterpreter(); interp1.set("a", new PyInteger(42)); interp2.set("a", new PyInteger(47)); interp1.exec("print(a)"); interp2.exec("print(a)"); PyObject x1 = interp1.get("a"); PyObject x2 = interp2.get("a"); System.out.println("a from interp1: " + x1); System.out.println("a from interp2: " + x2); } }
When you run the program youll see that the value of a is distinct within each PythonInterpreter.
107
Java classes are statically typed, while Python functions and methods are dynamically typed. Thus, you must somehow tell jythonc that a Python method is intended to have a particular set of argument types and that its return value is a particular type. You accomplish this with the @sig string, which is placed right after the beginning of the Python method denition (this is the standard location for the Python documentation string). For example:
def returnArray(self): "@sig public java.lang.String[] returnArray()"
The Python denition doesnt specify any return type, but the @sig string gives the full type information about what is being passed and returned. The jythonc compiler uses this information to generate the correct Java code. Theres one other set of rules you must follow in order to get a successful compilation: you must inherit from a Java class or interface in your Python class (you do not need to specify the @sig signature for methods dened in the superclass/interface). If you do not do this, you wont get your desired methods unfortunately, jythonc gives you no warnings or errors in this case, but you wont get what you want. If you dont see whats missing, it can be very frustrating. In addition, you must import the appropriate java class and give the correct package specication. In the example below, java is imported so you must inherit from java.lang.Object, but you could also say from java.lang import Object and then youd just inherit from Object without the package specication. Unfortunately, you dont get any warnings or errors if you get this wrong, so you must be patient and keep trying. Here is an example of a Python class created to produce a Java class. In this case, the Python le is used to build a Java .class le, so the class le is the desired target:
# Jython/PythonToJavaClass.py # A Python class converted into a Java class # Compile with: # jythonc --package python.java.test PythonToJavaClass.py from jarray import array import java class PythonToJavaClass(java.lang.Object):
108
# The @sig signature string is used to create the # proper signature in the resulting Java code: def __init__(self): "@sig public PythonToJavaClass()" print("Constructor for PythonToJavaClass") def simple(self): "@sig public void simple()" print("simple()") # Returning values to Java: def returnString(self): "@sig public java.lang.String returnString()" return "howdy" # You must construct arrays to return along # with the type of the array: def returnArray(self): "@sig public java.lang.String[] returnArray()" test = [ "fee", "fi", "fo", "fum" ] return array(test, java.lang.String) def ints(self): "@sig public java.lang.Integer[] ints()" test = [ 1, 3, 5, 7, 11, 13, 17, 19, 23 ] return array(test, java.lang.Integer) def doubles(self): "@sig public java.lang.Double[] doubles()" test = [ 1, 3, 5, 7, 11, 13, 17, 19, 23 ] return array(test, java.lang.Double) # Passing arguments in from Java: def argIn1(self, a): "@sig public void argIn1(java.lang.String a)" print("a: %s" % a) print("a.__class__", a.__class__) def argIn2(self, a): "@sig public void argIn1(java.lang.Integer a)" print("a + 100: %d" % (a + 100))
109
print("a.__class__", a.__class__) def argIn3(self, a): "@sig public void argIn3(java.util.List a)" print("received List:", a, a.__class__) print("element type:", a[0].__class__) print("a[3] + a[5]:", a[5] + a[7]) #! print("a[2:5]:", a[2:5]) # Doesnt work def argIn4(self, a): "@sig public void \ argIn4(org.python.core.PyArray a)" print("received type:", a.__class__) print("a: ", a) print("element type:", a[0].__class__) print("a[3] + a[5]:", a[5] + a[7]) print("a[2:5]:", a[2:5] # A real Python array) # A map must be passed in as a PyDictionary: def argIn5(self, m): "@sig public void \ argIn5(org.python.core.PyDictionary m)" print("received Map: ", m, m.__class__) print("m[3]:", m[3]) for x in m.keys(): print(x, m[x])
First note that PythonToJavaClass is inherited from java.lang.Object; if you dont do this you will quietly get a Java class without the right signatures. You are not required to inherit from Object; any other Java class will do. This class is designed to demonstrate different arguments and return values, to provide you with enough examples that youll be able to easily create your own signature strings. The rst three of these are fairly self-explanatory, but note the full qualication of the Java name in the signature string. In returnArray(), a Python array must be returned as a Java array. To do this, the Jython array() function (from the jarray module) must be used, along with the type of the class for the resulting array. Any time you need to return an array to Java, you must use array(), as seen in the methods ints() and doubles(). The last methods show how to pass arguments in from Java. Basic types happen 110 Chapter 17. Jython
automatically as long as you specify them in the @sig string, but you must use objects and you cannot pass in primitives (that is, primitives must be ensconced in wrapper objects, such as Integer). In argIn3(), you can see that a Java List is transparently converted to something that behaves just like a Python array, but is not a true array because you cannot take a slice from it. If you want a true Python array, then you must create and pass a PyArray as in argIn4(), where the slice is successful. Similarly, a Java Map must come in as a PyDictionary in order to be treated as a Python dictionary. Here is the Java program to exercise the Java classes produced by the above Python code. You cant compile TestPythonToJavaClass.java until PythonToJavaClass.class is available:
// Jython/TestPythonToJavaClass.java import java.lang.reflect.*; import java.util.*; import org.python.core.*; import java.util.*; import net.mindview.python.*; // The package with the Python-generated classes: import python.java.test.*; public class TestPythonToJavaClass { PythonToJavaClass p2j = new PythonToJavaClass(); public void testDumpClassInfo() { System.out.println( Arrays.toString( p2j.getClass().getConstructors())); Method[] methods = p2j.getClass().getMethods(); for(int i = 0; i < methods.length; i++) { String nm = methods[i].toString(); if(nm.indexOf("PythonToJavaClass") != -1) System.out.println(nm); } } public static void main(String[] args) { p2j.simple(); System.out.println(p2j.returnString()); System.out.println( Arrays.toString(p2j.returnArray())); System.out.println(
111
Arrays.toString(p2j.ints()); System.out.println( Arrays.toString(p2j.doubles())); p2j.argIn1("Testing argIn1()"); p2j.argIn2(new Integer(47)); ArrayList a = new ArrayList(); for(int i = 0; i < 10; i++) a.add(new Integer(i)); p2j.argIn3(a); p2j.argIn4( new PyArray(Integer.class, a.toArray())); Map m = new HashMap(); for(int i = 0; i < 10; i++) m.put("" + i, new Float(i)); p2j.argIn5(PyUtil.toPyDictionary(m)); } }
For Python support, youll usually only need to import the classes in org.python.core. Everything else in the above example is fairly straightforward, as PythonToJavaClass appears, from the Java side, to be just another Java class. dumpClassInfo() uses reection to verify that the method signatures specied in PythonToJavaClass.py have come through properly.
Here are the make dependency rules that I used to build the above example (the backslashes at the ends of the lines are understood by make to be line continuations):
TestPythonToJavaClass.class: \\ TestPythonToJavaClass.java \\ python\java\test\PythonToJavaClass.class javac TestPythonToJavaClass.java python\java\test\PythonToJavaClass.class: \\ PythonToJavaClass.py jythonc.bat --package python.java.test \\ PythonToJavaClass.py
The rst target, TestPythonToJavaClass.class, depends on both TestPythonToJavaClass.java and the PythonToJavaClass.class, which is the Python code thats converted to a class le. This latter, in turn, depends on the Python source code. Note that its important that the directory where the target lives be specied, so that the makele will create the Java program with the minimum necessary amount of rebuilding.
17.8 Summary
This chapter has arguably gone much deeper into Jython than required to use the interpreter design pattern. Indeed, once you decide that you need to use interpreter and that youre not going to get lost inventing your own language, the solution of installing Jython is quite simple, and you can at least get started by following the GreenHouseController example. Of course, that example is often too simple and you may need something more sophisticated, often requiring more interesting data to be passed back and forth. When I encountered the limited documentation, I felt it necessary to come up with a more thorough examination of Jython. In the process, note that there could be another equally powerful design pattern lurking in here, which could perhaps be called multiple languages or language hybridizing. This is based on the experience of having each language solve a certain class of problems better than the other; by combining languages you can solve problems much faster than with either language by itself. CORBA is another 17.8. Summary 113
way to bridge across languages, and at the same time bridging between computers and operating systems. To me, Python and Java present a very potent combination for program development because of Javas architecture and tool set, and Pythons extremely rapid development (generally considered to be 5-10 times faster than C++ or Java). Python is usually slower, however, but even if you end up re-coding parts of your program for speed, the initial fast development will allow you to more quickly esh out the system and uncover and solve the critical sections. And often, the execution speed of Python is not a problem in those cases its an even bigger win. A number of commercial products already use Java and Jython, and because of the terric productivity leverage I expect to see this happen more in the future.
17.9 Exercises
1. Modify GreenHouseLanguage.py so that it checks the times for the events and runs those events at the appropriate times. 2. Modify GreenHouseLanguage.py so that it calls a function for action instead of just printing a string. 3. Create a Swing application with a JTextField (where the user will enter commands) and a JTextArea (where the command results will be displayed). Connect to a PythonInterpreter object so that the output will be sent to the JTextArea (which should scroll). Youll need to locate the PythonInterpreter command that redirects the output to a Java stream. 4. Modify GreenHouseLanguage.py to add a master controller class (instead of the static array inside Event) and provide a run() method for each of the subclasses. Each run() should create and use an object from the standard Java library during its execution. Modify GreenHouseController.java to use this new class. 5. Modify the resulting GreenHouseLanguage.py from exercise two to produce Java classes (add the @sig documentation strings to produce the correct Java signatures, and create a makele to build the Java .class les). Write a Java program that uses these classes.
114
6. Modify GreenHouseLanguage.py so that the subclasses of Event are not discrete classes, but are instead generated by a single function which creates the class and the associated string dynamically. Changing the registry setting python.security.respectJavaAccessibility = true to false makes testing even more powerful because it allows the test script to use all methods, even protected and package- private.
17.9. Exercises
115
116
CHAPTER
EIGHTEEN
117
118
CHAPTER
NINETEEN
119
return 1 # Default
120
CHAPTER
TWENTY
121
122
CHAPTER
TWENTYONE
The trick here is that the __dict__ for the object is just assigned to the dict that is automatically created by the **kwargs argument. Although one could easily create a Messenger class and put it into a library and
123
import it, there are so few lines to describe it that it usually makes more sense to just dene it in-place whenever you need it it is probably easier for the reader to follow, as well.
124
CHAPTER
TWENTYTWO
125
126
CHAPTER
TWENTYTHREE
127
solved before, but your solution probably didnt have the kind of completeness youll see embodied in a pattern. Although theyre called design patterns, they really arent tied to the realm of design. A pattern seems to stand apart from the traditional way of thinking about analysis, design, and implementation. Instead, a pattern embodies a complete idea within a program, and thus it can sometimes appear at the analysis phase or high-level design phase. This is interesting because a pattern has a direct implementation in code and so you might not expect it to show up before low-level design or implementation (and in fact you might not realize that you need a particular pattern until you get to those phases). The basic concept of a pattern can also be seen as the basic concept of program design: adding a layer of abstraction. Whenever you abstract something youre isolating particular details, and one of the most compelling motivations behind this is to separate things that change from things that stay the same. Another way to put this is that once you nd some part of your program thats likely to change for one reason or another, youll want to keep those changes from propagating other changes throughout your code. Not only does this make the code much cheaper to maintain, but it also turns out that it is usually simpler to understand (which results in lowered costs). Often, the most difcult part of developing an elegant and cheap-to-maintain design is in discovering what I call the vector of change. (Here, vector refers to the maximum gradient and not a container class.) This means nding the most important thing that changes in your system, or put another way, discovering where your greatest cost is. Once you discover the vector of change, you have the focal point around which to structure your design. So the goal of design patterns is to isolate changes in your code. If you look at it this way, youve been seeing some design patterns already in this book. For example, inheritance can be thought of as a design pattern (albeit one implemented by the compiler). It allows you to express differences in behavior (thats the thing that changes) in objects that all have the same interface (thats what stays the same). Composition can also be considered a pattern, since it allows you to change-dynamically or statically-the objects that implement your class, and thus the way that class works. Another pattern that appears in Design Patterns is the iterator, which has been implicitly available in for loops from the beginning of the language, and was introduced as an explicit feature in Python 2.2. An iterator allows you to hide the
128
particular implementation of the container as youre stepping through and selecting the elements one by one. Thus, you can write generic code that performs an operation on all of the elements in a sequence without regard to the way that sequence is built. Thus your generic code can be used with any object that can produce an iterator.
tal than) those described in Design Patterns. These principles are based on the structure of the implementations, which is where I have seen great similarities between patterns (more than those expressed in Design Patterns). Although we generally try to avoid implementation in favor of interface, I have found that its often easier to think about, and especially to learn about, the patterns in terms of these structural principles. This book will attempt to present the patterns based on their structure instead of the categories presented in Design Patterns.
130
One could also argue for the inclusion of Analysis Pattern and Architectural Pattern in this taxonomy.
This list includes suggestions by Kevlin Henney, David Scott, and others.
131
Hiding Guarding Connector Barrier/fence Variation in behavior Notication Transaction Mirror: the ability to keep a parallel universe(s) in step with the golden world Shadow: follows your movement and does something different in a different medium (May be a variation on Proxy).
Independence or Orthogonality. Express independent ideas independently. This complements Separation, Encapsulation and Variation, and is part of the Low-Coupling-High-Cohesion message. Managed Coupling. Simply stating that we should have low coupling in a design is usually too vague - coupling happens, and the important issue is to acknowledge it and control it, to say coupling can cause problems and to compensate for those problems with a well-considered design or pattern. Subtraction: a design is nished when you cannot take anything else away 3 . Simplicity before generality . (A variation of Occams Razor, which says the simplest solution is the best). A common problem we nd in frameworks is that they are designed to be general purpose without reference to actual systems. This leads to a dizzying array of options that are often unused, misused or just not useful. However, most developers work on specic systems, and the quest for generality does not always serve them well. The best route to generality is through understanding well-dened specic examples. So, this principle acts as the tie breaker between otherwise equally viable design alternatives. Of course, it is entirely possible that the simpler solution is the more general one. Reexivity (my suggested term). One abstraction per class, one class per abstraction. Might also be called Isomorphism. Once and once only: Avoid duplication of logic and structure where the duplication is not accidental, ie where both pieces of code express the same intent for the same reason. In the process of brainstorming this idea, I hope to come up with a small handful of fundamental ideas that can be held in your head while you analyze a problem. However, other ideas that come from this list may end up being useful as a checklist while walking through and analyzing your design. But be warned: the examples are in C++. A free email publication. See www.BruceEckel.com to subscribe.
3 This idea is generally attributed to Antoine de St. Exupery from The Little Prince: La perfection est atteinte non quand il ne reste rien ajouter, mais quand il ne reste rien enlever, or: perfection is reached not when theres nothing left to add, but when theres nothing left to remove.
133
134
CHAPTER
TWENTYFOUR
THE SINGLETON
Possibly the simplest design pattern is the singleton, which is a way to provide one and only one object of a particular type. To accomplish this, you must take control of object creation out of the hands of the programmer. One convenient way to do this is to delegate to a single instance of a private nested inner class:
# Singleton/SingletonPattern.py class OnlyOne: class __OnlyOne: def __init__(self, arg): self.val = arg def __str__(self): return self + self.val instance = None def __init__(self, arg): if not OnlyOne.instance: OnlyOne.instance = OnlyOne.__OnlyOne(arg) else: OnlyOne.instance.val = arg def __getattr__(self, name): return getattr(self.instance, name) x = OnlyOne(sausage) print(x) y = OnlyOne(eggs) print(y) z = OnlyOne(spam)
135
print(z) print(x) print(y) print(x) print(y) print(z) output = <__main__.__OnlyOne instance at 0076B7AC>sausage <__main__.__OnlyOne instance at 0076B7AC>eggs <__main__.__OnlyOne instance at 0076B7AC>spam <__main__.__OnlyOne instance at 0076B7AC>spam <__main__.__OnlyOne instance at 0076B7AC>spam <__main__.OnlyOne instance at 0076C54C> <__main__.OnlyOne instance at 0076DAAC> <__main__.OnlyOne instance at 0076AA3C>
Because the inner class is named with a double underscore, it is private so the user cannot directly access it. The inner class contains all the methods that you would normally put in the class if it werent going to be a singleton, and then it is wrapped in the outer class which controls creation by using its constructor. The rst time you create an OnlyOne, it initializes instance, but after that it just ignores you. Access comes through delegation, using the __getattr__( ) method to redirect calls to the single instance. You can see from the output that even though it appears that multiple objects have been created, the same __OnlyOne object is used for both. The instances of OnlyOne are distinct but they all proxy to the same __OnlyOne object. Note that the above approach doesnt restrict you to creating only one object. This is also a technique to create a limited pool of objects. In that situation, however, you can be confronted with the problem of sharing objects in the pool. If this is an issue, you can create a solution involving a check-out and check- in of the shared objects. A variation on this technique uses the class method __new__ added in Python 2.2:
# Singleton/NewSingleton.py
136
class OnlyOne(object): class __OnlyOne: def __init__(self): self.val = None def __str__(self): return self + self.val instance = None def __new__(cls): # __new__ always a classmethod if not OnlyOne.instance: OnlyOne.instance = OnlyOne.__OnlyOne() return OnlyOne.instance def __getattr__(self, name): return getattr(self.instance, name) def __setattr__(self, name): return setattr(self.instance, name) x = OnlyOne() x.val = sausage print(x) y = OnlyOne() y.val = eggs print(y) z = OnlyOne() z.val = spam print(z) print(x) print(y) #<hr> output = <__main__.__OnlyOne <__main__.__OnlyOne <__main__.__OnlyOne <__main__.__OnlyOne <__main__.__OnlyOne
at at at at at
Alex Martelli makes the observation that what we really want with a Singleton is to have a single set of state data for all objects. That is, you could create as many objects as you want and as long as they all refer to the same state information then you achieve the effect of Singleton. He accomplishes this with what he calls
137
the Borg 1 , which is accomplished by setting all the __dict__s to the same static piece of storage:
# Singleton/BorgSingleton.py # Alex Martellis Borg class Borg: _shared_state = {} def __init__(self): self.__dict__ = self._shared_state class Singleton(Borg): def __init__(self, arg): Borg.__init__(self) self.val = arg def __str__(self): return self.val x = Singleton(sausage) print(x) y = Singleton(eggs) print(y) z = Singleton(spam) print(z) print(x) print(y) print(x) print(y) print(z) output = sausage eggs spam spam spam <__main__.Singleton instance at 0079EF2C> <__main__.Singleton instance at 0079E10C> <__main__.Singleton instance at 00798F9C>
1 From the television show Star Trek: The Next Generation. The Borg are a hive-mind collective: we are all one.
138
This has an identical effect as SingletonPattern.py does, but its more elegant. In the former case, you must wire in Singleton behavior to each of your classes, but Borg is designed to be easily reused through inheritance. A simpler version of this takes advantage of the fact that theres only one instance of a class variable:
# Singleton/ClassVariableSingleton.py class SingleTone(object): __instance = None def __new__(cls, val): if SingleTone.__instance is None: SingleTone.__instance = object.__new__(cls) SingleTone.__instance.val = val return SingleTone.__instance
Two other interesting ways to dene singleton 2 include wrapping a class and using metaclasses. The rst approach could be thought of as a class decorator (decorators will be dened later in the book), because it takes the class of interest and adds functionality to it by wrapping it in another class:
# Singleton/SingletonDecorator.py class SingletonDecorator: def __init__(self,klass): self.klass = klass self.instance = None def __call__(self,*args,**kwds): if self.instance == None: self.instance = self.klass(*args,**kwds) return self.instance class foo: pass foo = SingletonDecorator(foo) x=foo() y=foo() z=foo() x.val = sausage y.val = eggs z.val = spam
2
139
[[ Description ]] The second approach uses metaclasses, a topic I do not yet understand but which looks very interesting and powerful indeed (note that Python 2.2 has improved/simplied the metaclass syntax, and so this example may change):
# Singleton/SingletonMetaClass.py class SingletonMetaClass(type): def __init__(cls,name,bases,dict): super(SingletonMetaClass,cls)\ .__init__(name,bases,dict) original_new = cls.__new__ def my_new(cls,*args,**kwds): if cls.instance == None: cls.instance = \ original_new(cls,*args,**kwds) return cls.instance cls.instance = None cls.__new__ = staticmethod(my_new) class bar(object): __metaclass__ = SingletonMetaClass def __init__(self,val): self.val = val def __str__(self): return self + self.val x=bar(sausage) y=bar(eggs) z=bar(spam) print(x) print(y) print(z) print(x is y is z)
140
[[ Long, detailed, informative description of what metaclasses are and how they work, magically inserted here ]]
24.1 Exercises
1. SingletonPattern.py always creates an object, even if its never used. Modify this program to use lazy initialization, so the singleton object is only created the rst time that it is needed. 2. Using SingletonPattern.py as a starting point, create a class that manages a xed number of its own objects. Assume the objects are database connections and you only have a license to use a xed quantity of these at any one time. 3. Modify BorgSingleton.py so that it uses a class __new__( ) method. From Dmitry Balabanov.
24.1. Exercises
141
142
CHAPTER
TWENTYFIVE
143
# AppFrameworks/TemplateMethod.py # Simple demonstration of Template Method. class ApplicationFramework: def __init__(self): self.__templateMethod() def __templateMethod(self): for i in range(5): self.customize1() self.customize2() # Create an "application": class MyApp(ApplicationFramework): def customize1(self): print("Nudge, nudge, wink, wink! ",) def customize2(self): print("Say no more, Say no more!") MyApp()
The base-class constructor is responsible for performing the necessary initialization and then starting the engine (the template method) that runs the application (in a GUI application, this engine would be the main event loop). The client programmer simply provides denitions for customize1( ) and customize2( ) and the application is ready to run. Well see Template Method numerous other times throughout the book.
25.2 Exercises
1. Create a framework that takes a list of le names on the command line. It opens each le except the last for reading, and the last for writing. The framework will process each input le using an undetermined policy and write the output to the last le. Inherit to customize this framework to create two separate applications: (a) Converts all the letters in each le to uppercase. (b) Searches the les for words given in the rst le.
144
CHAPTER
TWENTYSIX
145
When a surrogate object is created, it is given an implementation to which to send all of the method calls. Structurally, the difference between Proxy and State is simple: a Proxy has only one implementation, while State has more than one. The application of the patterns is considered (in Design Patterns) to be distinct: Proxy is used to control access to its implementation, while State allows you to change the implementation dynamically. However, if you expand your notion of controlling access to implementation then the two t neatly together.
26.1 Proxy
If we implement Proxy by following the above diagram, it looks like this:
# Fronting/ProxyDemo.py # Simple demonstration of the Proxy pattern. class Implementation: def f(self): print("Implementation.f()") def g(self): print("Implementation.g()") def h(self): print("Implementation.h()") class Proxy: def __init__(self): self.__implementation = Implementation() # Pass method calls to the implementation: def f(self): self.__implementation.f() def g(self): self.__implementation.g() def h(self): self.__implementation.h() p = Proxy() p.f(); p.g(); p.h()
It isnt necessary that Implementation have the same interface as Proxy; as long as Proxy is somehow speaking for the class that it is referring method calls to then the basic idea is satised (note that this statement is at odds with the deni-
146
tion for Proxy in GoF). However, it is convenient to have a common interface so that Implementation is forced to fulll all the methods that Proxy needs to call. Of course, in Python we have a delegation mechanism built in, so it makes the Proxy even simpler to implement:
# Fronting/ProxyDemo2.py # Simple demonstration of the Proxy pattern. class Implementation2: def f(self): print("Implementation.f()") def g(self): print("Implementation.g()") def h(self): print("Implementation.h()") class Proxy2: def __init__(self): self.__implementation = Implementation2() def __getattr__(self, name): return getattr(self.__implementation, name) p = Proxy2() p.f(); p.g(); p.h();
The beauty of using __getattr__( ) is that Proxy2 is completely generic, and not tied to any particular implementation (in Java, a rather complicated dynamic proxy has been invented to accomplish this same thing).
26.2 State
The State pattern adds more implementations to Proxy, along with a way to switch from one implementation to another during the lifetime of the surrogate:
# Fronting/StateDemo.py # Simple demonstration of the State pattern. class State_d:
26.2. State
147
def __init__(self, imp): self.__implementation = imp def changeImp(self, newImp): self.__implementation = newImp # Delegate calls to the implementation: def __getattr__(self, name): return getattr(self.__implementation, name) class Implementation1: def f(self): print("Fiddle de dum, Fiddle de dee,") def g(self): print("Eric the half a bee.") def h(self): print("Ho ho ho, tee hee hee,") class Implementation2: def f(self): print("Were Knights of the Round Table.") def g(self): print("We dance wheneer were able.") def h(self): print("We do routines and chorus scenes") def run(b): b.f() b.g() b.h() b.g() b = State_d(Implementation1()) run(b) b.changeImp(Implementation2()) run(b)
You can see that the rst implementation is used for a bit, then the second implementation is swapped in and that is used. The difference between Proxy and State is in the problems that are solved. The common uses for Proxy as described in Design Patterns are:
148
1. Remote proxy. This proxies for an object in a different address space. A remote proxy is created for you automatically by the RMI compiler rmic as it creates stubs and skeletons. 2. Virtual proxy. This provides lazy initialization to create expensive objects on demand. 3. Protection proxy. Used when you dont want the client programmer to have full access to the proxied object. 4. Smart reference. To add additional actions when the proxied object is accessed. For example, or to keep track of the number of references that are held for a particular object, in order to implement the copy-on-write idiom and prevent object aliasing. A simpler example is keeping track of the number of calls to a particular method. You could look at a Python reference as a kind of protection proxy, since it controls access to the actual object on the heap (and ensures, for example, that you dont use a null reference). [[ Rewrite this: In Design Patterns, Proxy and State are not seen as related to each other because the two are given (what I consider arbitrarily) different structures. State, in particular, uses a separate implementation hierarchy but this seems to me to be unnecessary unless you have decided that the implementation is not under your control (certainly a possibility, but if you own all the code there seems to be no reason not to benet from the elegance and helpfulness of the single base class). In addition, Proxy need not use the same base class for its implementation, as long as the proxy object is controlling access to the object it fronting for. Regardless of the specics, in both Proxy and State a surrogate is passing method calls through to an implementation object.]]]
26.2. State
149
150
CHAPTER
TWENTYSEVEN
STATEMACHINE
While State has a way to allow the client programmer to change the implementation, StateMachine imposes a structure to automatically change the implementation from one object to the next. The current implementation represents the state that a system is in, and the system behaves differently from one state to the next (because it uses State). Basically, this is a state machine using objects. The code that moves the system from one state to the next is often a Template Method, as seen in the following framework for a basic state machine. Each state can be run( ) to perform its behavior, and (in this design) you can also pass it an input object so it can tell you what new state to move to based on that input. The key distinction between this design and the next is that here, each State object decides what other states it can move to, based on the input, whereas in the subsequent design all of the state transitions are held in a single table. Another way to put it is that here, each State object has its own little State table, and in the subsequent design there is a single master state transition table for the whole system:
# StateMachine/State.py # A State has an operation, and can be moved # into the next State given an Input: class State: def run(self): assert 0, "run not implemented" def next(self, input): assert 0, "next not implemented"
151
This class is clearly unnecessary, but it allows us to say that something is a State object in code, and provide a slightly different error message when all the methods are not implemented. We could have gotten basically the same effect by saying:
class State: pass
because we would still get exceptions if run( ) or next( ) were called for a derived type, and they hadnt been implemented. The StateMachine keeps track of the current state, which is initialized by the constructor. The runAll( ) method takes a list of Input objects. This method not only moves to the next state, but it also calls run( ) for each state object - thus you can see its an expansion of the idea of the State pattern, since run( ) does something different depending on the state that the system is in:
# StateMachine/StateMachine.py # Takes a list of Inputs to move from State to # State using a template method. class StateMachine: def __init__(self, initialState): self.currentState = initialState self.currentState.run() # Template method: def runAll(self, inputs): for i in inputs: print(i) self.currentState = self.currentState.next(i) self.currentState.run()
Ive also treated runAll( ) as a template method. This is typical, but certainly not required - you could concievably want to override it, but typically the behavior change will occur in States run( ) instead. At this point the basic framework for this style of StateMachine (where each state decides the next states) is complete. As an example, Ill use a fancy mousetrap
152
that can move through several states in the process of trapping a mouse 1 . The mouse classes and information are stored in the mouse package, including a class representing all the possible moves that a mouse can make, which will be the inputs to the state machine:
# StateMachine/mouse/MouseAction.py class MouseAction: def __init__(self, action): self.action = action def __str__(self): return self.action def __cmp__(self, other): return cmp(self.action, other.action) # Necessary when __cmp__ or __eq__ is defined # in order to make this class usable as a # dictionary key: def __hash__(self): return hash(self.action) # Static fields; an enumeration of instances: MouseAction.appears = MouseAction("mouse appears") MouseAction.runsAway = MouseAction("mouse runs away") MouseAction.enters = MouseAction("mouse enters trap") MouseAction.escapes = MouseAction("mouse escapes") MouseAction.trapped = MouseAction("mouse trapped") MouseAction.removed = MouseAction("mouse removed")
Youll note that __cmp__( ) has been overidden to implement a comparison between action values. Also, each possible move by a mouse is enumerated as a MouseAction object, all of which are static elds in MouseAction. For creating test code, a sequence of mouse inputs is provided from a text le:
# StateMachine/mouse/MouseMoves.txt mouse appears mouse runs away mouse appears mouse enters trap mouse escapes mouse appears
1
153
enters trap trapped removed appears runs away appears enters trap trapped removed
With these tools in place, its now possible to create the rst version of the mousetrap program. Each State subclass denes its run( ) behavior, and also establishes its next state with an if-else clause:
# StateMachine/mousetrap1/MouseTrapTest.py # State Machine pattern using if statements # to determine the next state. import string, sys sys.path += [../stateMachine, ../mouse] from State import State from StateMachine import StateMachine from MouseAction import MouseAction # A different subclass for each state: class Waiting(State): def run(self): print("Waiting: Broadcasting cheese smell") def next(self, input): if input == MouseAction.appears: return MouseTrap.luring return MouseTrap.waiting class Luring(State): def run(self): print("Luring: Presenting Cheese, door open") def next(self, input): if input == MouseAction.runsAway: return MouseTrap.waiting if input == MouseAction.enters:
154
return MouseTrap.trapping return MouseTrap.luring class Trapping(State): def run(self): print("Trapping: Closing door") def next(self, input): if input == MouseAction.escapes: return MouseTrap.waiting if input == MouseAction.trapped: return MouseTrap.holding return MouseTrap.trapping class Holding(State): def run(self): print("Holding: Mouse caught") def next(self, input): if input == MouseAction.removed: return MouseTrap.waiting return MouseTrap.holding class MouseTrap(StateMachine): def __init__(self): # Initial state StateMachine.__init__(self, MouseTrap.waiting) # Static variable initialization: MouseTrap.waiting = Waiting() MouseTrap.luring = Luring() MouseTrap.trapping = Trapping() MouseTrap.holding = Holding() moves = map(string.strip, open("../mouse/MouseMoves.txt").readlines()) MouseTrap().runAll(map(MouseAction, moves))
The StateMachine class simply denes all the possible states as static objects, and also sets up the initial state. The UnitTest creates a MouseTrap and then tests it with all the inputs from a MouseMoveList.
155
While the use of if statements inside the next( ) methods is perfectly reasonable, managing a large number of these could become difcult. Another approach is to create tables inside each State object dening the various next states based on the input. Initially, this seems like it ought to be quite simple. You should be able to dene a static table in each State subclass that denes the transitions in terms of the other State objects. However, it turns out that this approach generates cyclic initialization dependencies. To solve the problem, Ive had to delay the initialization of the tables until the rst time that the next( ) method is called for a particular State object. Initially, the next( ) methods can appear a little strange because of this. The StateT class is an implementation of State (so that the same StateMachine class can be used from the previous example) that adds a Map and a method to initialize the map from a two-dimensional array. The next( ) method has a baseclass implementation which must be called from the overridden derived class next( ) methods after they test for a null Map (and initialize it if its null):
# StateMachine/mousetrap2/MouseTrap2Test.py # A better mousetrap using tables import string, sys sys.path += [../stateMachine, ../mouse] from State import State from StateMachine import StateMachine from MouseAction import MouseAction class StateT(State): def __init__(self): self.transitions = None def next(self, input): if self.transitions.has_key(input): return self.transitions[input] else: raise "Input not supported for current state" class Waiting(StateT): def run(self): print("Waiting: Broadcasting cheese smell") def next(self, input): # Lazy initialization: if not self.transitions:
156
self.transitions = { MouseAction.appears : MouseTrap.luring } return StateT.next(self, input) class Luring(StateT): def run(self): print("Luring: Presenting Cheese, door open") def next(self, input): # Lazy initialization: if not self.transitions: self.transitions = { MouseAction.enters : MouseTrap.trapping, MouseAction.runsAway : MouseTrap.waiting } return StateT.next(self, input) class Trapping(StateT): def run(self): print("Trapping: Closing door") def next(self, input): # Lazy initialization: if not self.transitions: self.transitions = { MouseAction.escapes : MouseTrap.waiting, MouseAction.trapped : MouseTrap.holding } return StateT.next(self, input) class Holding(StateT): def run(self): print("Holding: Mouse caught") def next(self, input): # Lazy initialization: if not self.transitions: self.transitions = { MouseAction.removed : MouseTrap.waiting } return StateT.next(self, input) class MouseTrap(StateMachine):
157
def __init__(self): # Initial state StateMachine.__init__(self, MouseTrap.waiting) # Static variable initialization: MouseTrap.waiting = Waiting() MouseTrap.luring = Luring() MouseTrap.trapping = Trapping() MouseTrap.holding = Holding() moves = map(string.strip, open("../mouse/MouseMoves.txt").readlines()) mouseMoves = map(MouseAction, moves) MouseTrap().runAll(mouseMoves)
The rest of the code is identical - the difference is in the next( ) methods and the StateT class. If you have to create and maintain a lot of State classes, this approach is an improvement, since its easier to quickly read and understand the state transitions from looking at the table.
158
Goals: Direct translation of state diagram Vector of change: the state diagram representation Reasonable implementation No excess of states (you could represent every single change with a new state) Simplicity and exibility Observations: States are trivial - no information or functions/data, just an identity Not like the State pattern! The machine governs the move from state to state Similar to yweight Each state may move to many others Condition & action functions must also be external to states Centralize description in a single table containing all variations, for ease of conguration Example: State Machine & Table-Driven Code Implements a vending machine Uses several other patterns Separates common state-machine code from specic application (like template method) Each input causes a seek for appropriate solution (like chain of responsibility) 27.1. Table-Driven State Machine 159
Tests and transitions are encapsulated in function objects (objects that hold functions) Java constraint: methods are not rst-class objects
160
The Condition evaluates the Input to decide whether this row in the table is the correct transition:
# StateMachine/stateMachine2/Condition.py # Condition function object for state machine class Condition: boolean condition(input) : assert 0, "condition() not implemented"
161
162
163
def __init__(self): StateMachine.__init__(State.makesChange, { # Current state, input (State.makesChange, HasChange.no) : # test, transition, next state: (null, null, State.noChange), (State.noChange, HasChange.yes) : (null, null, State.noChange) }) class Money: def __init__(self, name, value): self.name = name self.value = value def __str__(self): return self.name def getValue(self): return self.value Money.quarter = Money("Quarter", 25) Money.dollar = Money("Dollar", 100) class Quit: def __str__(self): return "Quit" Quit.quit = Quit() class Digit: def __init__(self, name, value): self.name = name self.value = value def __str__(self): return self.name def getValue(self): return self.value class FirstDigit(Digit): pass FirstDigit.A = FirstDigit("A", FirstDigit.B = FirstDigit("B", FirstDigit.C = FirstDigit("C", FirstDigit.D = FirstDigit("D",
0) 1) 2) 3)
164
SecondDigit.three = SecondDigit("three", 2) SecondDigit.four = SecondDigit("four", 3) class ItemSlot: id = 0 def __init__(self, price, quantity): self.price = price self.quantity = quantity def __str__(self): return ItemSlot.id def getPrice(self): return self.price def getQuantity(self): return self.quantity def decrQuantity(self): self.quantity -= 1 class VendingMachine(StateMachine): changeAvailable = ChangeAvailable() amount = 0 FirstDigit first = null ItemSlot[][] items = ItemSlot[4][4] # Conditions: def notEnough(self, input): i1 = first.getValue() i2 = input.getValue() return items[i1][i2].getPrice() > amount def itemAvailable(self, input): i1 = first.getValue() i2 = input.getValue() return items[i1][i2].getQuantity() > 0 def itemNotAvailable(self, input): return !itemAvailable.condition(input) #i1 = first.getValue() #i2 = input.getValue() #return items[i1][i2].getQuantity() == 0 # Transitions: def clearSelection(self, input): i1 = first.getValue() i2 = input.getValue() ItemSlot is = items[i1][i2]
165
print ( "Clearing selection: item " + is + " costs " + is.getPrice() + " and has quantity " + is.getQuantity()) first = null def dispense(self, input): i1 = first.getValue() i2 = input.getValue() ItemSlot is = items[i1][i2] print(("Dispensing item " + is + " costs " + is.getPrice() + " and has quantity " + is.getQuantity())) items[i1][i2].decrQuantity() print ("Quantity " + is.getQuantity()) amount -= is.getPrice() print("Amount remaining " + amount) def showTotal(self, input): amount += ((Money)input).getValue() print("Total amount = " + amount) def returnChange(self, input): print("Returning " + amount) amount = 0 def showDigit(self, input): first = (FirstDigit)input print("First Digit= "+ first) def __init__(self): StateMachine.__init__(self, State.quiescent) for(int i = 0 i < items.length i++) for(int j = 0 j < items[i].length j++) items[i][j] = ItemSlot((j+1)*25, 5) items[3][0] = ItemSlot(25, 0) """ buildTable(Object[][][]{ ::State.quiescent, # Current state
166
# Input, test, transition, next state: :Money.class, null, showTotal, State.collecting, ::State.collecting, # Current state # Input, test, transition, next state: :Quit.quit, null, returnChange, State.quiescent, :Money.class, null, showTotal, State.collecting, :FirstDigit.class, null, showDigit, State.selecting, ::State.selecting, # Current state # Input, test, transition, next state: :Quit.quit, null, returnChange, State.quiescent, :SecondDigit.class, notEnough, clearSelection, State.collecting, :SecondDigit.class, itemNotAvailable, clearSelection, State.unavailable, :SecondDigit.class, itemAvailable, dispense, State.wantMore, ::State.unavailable, # Current state # Input, test, transition, next state: :Quit.quit, null, returnChange, State.quiescent, :FirstDigit.class, null, showDigit, State.selecting, ::State.wantMore, # Current state # Input, test, transition, next state: :Quit.quit, null, returnChange, State.quiescent, :FirstDigit.class, null, showDigit, State.selecting, ) """
167
# StateMachine/vendingmachine/VendingMachineTest.py # Demonstrates use of StateMachine.py vm = VendingMachine() for input in [ Money.quarter, Money.quarter, Money.dollar, FirstDigit.A, SecondDigit.two, FirstDigit.A, SecondDigit.two, FirstDigit.C, SecondDigit.three, FirstDigit.D, SecondDigit.one, Quit.quit]: vm.nextState(input)
27.2 Tools
Another approach, as your state machine gets bigger, is to use an automation tool whereby you congure a table and let the tool generate the state machine code for you. This can be created yourself using a language like Python, but there are also free, open-source tools such as Libero, at http://www.imatix.com.
27.3 Exercises
1. Create an example of the virtual proxy. 2. Create an example of the Smart reference proxy where you keep count of the number of method calls to a particular object. 3. Create a program similar to certain DBMS systems that only allow a certain number of connections at any time. To implement this, use a singletonlike system that controls the number of connection objects that it creates. 168 Chapter 27. StateMachine
When a user is nished with a connection, the system must be informed so that it can check that connection back in to be reused. To guarantee this, provide a proxy object instead of a reference to the actual connection, and design the proxy so that it will cause the connection to be released back to the system. 4. Using the State, make a class called UnpredictablePerson which changes the kind of response to its hello( ) method depending on what kind of Mood its in. Add an additional kind of Mood called Prozac. 5. Create a simple copy-on write implementation. 6. Apply TransitionTable.py to the Washer problem. 7. Create a StateMachine system whereby the current state along with input information determines the next state that the system will be in. To do this, each state must store a reference back to the proxy object (the state controller) so that it can request the state change. Use a HashMap to create a table of states, where the key is a String naming the new state and the value is the new state object. Inside each state subclass override a method nextState( ) that has its own state-transition table. The input to nextState( ) should be a single word that comes from a text le containing one word per line. 8. Modify the previous exercise so that the state machine can be congured by creating/modifying a single multi-dimensional array. 9. Modify the mood exercise from the previous session so that it becomes a state machine using StateMachine.py 10. Create an elevator state machine system using StateMachine.py 11. Create a heating/air-conditioning system using StateMachine.py 12. A generator is an object that produces other objects, just like a factory, except that the generator function doesnt require any arguments. Create a MouseMoveGenerator which produces correct MouseMove actions as outputs each time the generator function is called (that is, the mouse must move in the proper sequence, thus the possible moves are based on the previous move - its another state machine). Add a method to produce an iterator, but this method should take an int argument that species the number of moves to produce before hasNext() returns false.
27.3. Exercises
169
170
CHAPTER
TWENTYEIGHT
171
172
The key to using this method is to nd the particular combination you want. So, once youve found the drink you would like, here is how you would use it, as shown in the CoffeeShop class in the following code:
# Decorator/nodecorators/CoffeeShop.py # Coffee example with no decorators class Espresso: pass class DoubleEspresso: pass class EspressoConPanna: pass class Cappuccino: def __init__(self): self.cost = 1 self.description = "Cappucino" def getCost(self): return self.cost def getDescription(self):
173
return self.description class class class class class class class CappuccinoDecaf: pass CappuccinoDecafWhipped: pass CappuccinoDry: pass CappuccinoDryWhipped: pass CappuccinoExtraEspresso: pass CappuccinoExtraEspressoWhipped: pass CappuccinoWhipped: pass
class CafeMocha: pass class CafeMochaDecaf: pass class CafeMochaDecafWhipped: def __init__(self): self.cost = 1.25 self.description = \ "Cafe Mocha decaf whipped cream" def getCost(self): return self.cost def getDescription(self): return self.description class class class class class class class class class class class class class CafeMochaExtraEspresso: pass CafeMochaExtraEspressoWhipped: pass CafeMochaWet: pass CafeMochaWetWhipped: pass CafeMochaWhipped: pass CafeLatte: pass CafeLatteDecaf: pass CafeLatteDecafWhipped: pass CafeLatteExtraEspresso: pass CafeLatteExtraEspressoWhipped: pass CafeLatteWet: pass CafeLatteWetWhipped: pass CafeLatteWhipped: pass
174
You can see that creating the particular combination you want is easy, since you are just creating an instance of a class. However, there are a number of problems with this approach. Firstly, the combinations are xed statically so that any combination a customer may wish to order needs to be created up front. Secondly, the resulting menu is so huge that nding your particular combination is difcult and time consuming.
175
Methods invoked on the Decorator can in turn invoke methods in the component, and can of course perform processing before or after the invocation. So if we added getTotalCost() and getDescription() methods to the DrinkComponent interface, an Espresso looks like this:
# Decorator/alldecorators/EspressoDecorator.py class Espresso(Decorator): cost = 0.75f description = " espresso" def __init__(DrinkComponent): Decorator.__init__(self, component) def getTotalCost(self): return self.component.getTotalCost() + cost def getDescription(self): return self.component.getDescription() + description
You combine the components to create a drink as follows, as shown in the code below:
# Decorator/alldecorators/CoffeeShop.py # Coffee example using decorators class DrinkComponent: def getDescription(self): return self.__class__.__name__ def getTotalCost(self): return self.__class__.cost class Mug(DrinkComponent): cost = 0.0 class Decorator(DrinkComponent): def __init__(self, drinkComponent): self.component = drinkComponent def getTotalCost(self): return self.component.getTotalCost() + \ DrinkComponent.getTotalCost(self)
176
def getDescription(self): return self.component.getDescription() + \ + DrinkComponent.getDescription(self) class Espresso(Decorator): cost = 0.75 def __init__(self, drinkComponent): Decorator.__init__(self, drinkComponent) class Decaf(Decorator): cost = 0.0 def __init__(self, drinkComponent): Decorator.__init__(self, drinkComponent) class FoamedMilk(Decorator): cost = 0.25 def __init__(self, drinkComponent): Decorator.__init__(self, drinkComponent) class SteamedMilk(Decorator): cost = 0.25 def __init__(self, drinkComponent): Decorator.__init__(self, drinkComponent) class Whipped(Decorator): cost = 0.25 def __init__(self, drinkComponent): Decorator.__init__(self, drinkComponent) class Chocolate(Decorator): cost = 0.25 def __init__(self, drinkComponent): Decorator.__init__(self, drinkComponent) cappuccino = Espresso(FoamedMilk(Mug())) print(cappuccino.getDescription().strip() + \) ": $" + cappuccino.getTotalCost() cafeMocha = Espresso(SteamedMilk(Chocolate( Whipped(Decaf(Mug())))))
177
This approach would certainly provide the most exibility and the smallest menu. You have a small number of components to choose from, but assembling the description of the coffee then becomes rather arduous. If you want to describe a plain cappuccino, you create it with:
plainCap = Espresso(FoamedMilk(Mug()))
Creating a decaf Cafe Mocha with whipped cream requires an even longer description.
28.5 Compromise
The previous approach takes too long to describe a coffee. There will also be certain combinations that you will describe regularly, and it would be convenient to have a quick way of describing them. The 3rd approach is a mixture of the rst 2 approaches, and combines exibility with ease of use. This compromise is achieved by creating a reasonably sized menu of basic selections, which would often work exactly as they are, but if you wanted to decorate them (whipped cream, decaf etc.) then you would use decorators to make the modications. This is the type of menu you are presented with in most coffee shops.
178
28.5. Compromise
179
class Decorator(DrinkComponent): def __init__(self, drinkComponent): self.component = drinkComponent def getTotalCost(self): return self.component.getTotalCost() + \ DrinkComponent.getTotalCost(self) def getDescription(self): return self.component.getDescription() + \ + DrinkComponent.getDescription(self) class ExtraEspresso(Decorator): cost = 0.75 def __init__(self, drinkComponent): Decorator.__init__(self, drinkComponent) class Whipped(Decorator): cost = 0.50 def __init__(self, drinkComponent): Decorator.__init__(self, drinkComponent) class Decaf(Decorator): cost = 0.0 def __init__(self, drinkComponent): Decorator.__init__(self, drinkComponent) class Dry(Decorator): cost = 0.0 def __init__(self, drinkComponent): Decorator.__init__(self, drinkComponent) class Wet(Decorator): cost = 0.0 def __init__(self, drinkComponent): Decorator.__init__(self, drinkComponent) cappuccino = Cappuccino() print(cappuccino.getDescription() + ": $" + \) cappuccino.getTotalCost() cafeMocha = Whipped(Decaf(CafeMocha())) print(cafeMocha.getDescription() + ": $" + \)
180
cafeMocha.getTotalCost()
You can see that creating a basic selection is quick and easy, which makes sense since they will be described regularly. Describing a decorated drink is more work than when using a class per combination, but clearly less work than when only using decorators. The nal result is not too many classes, but not too many decorators either. Most of the time its possible to get away without using any decorators at all, so we have the benets of both approaches.
28.7 Exercises
1. Add a Syrup class to the decorator approach described above. Then create a Caf Latte (youll need to use steamed milk with an espresso) with syrup. 2. Repeat Exercise 1 for the compromise approach. 3. Implement the decorator pattern to create a Pizza restaurant, which has a set menu of choices as well as the option to design your own pizza. Follow the compromise approach to create a menu consisting of a Margherita, Hawaiian, Regina, and Vegetarian pizzas, with toppings (decorators) of Garlic, Olives, Spinach, Avocado, Feta and Pepperdews. Create a Hawaiian
181
pizza, as well as a Margherita decorated with Spinach, Feta, Pepperdews and Olives.
182
CHAPTER
TWENTYNINE
183
youve used iterators - in the form of the Enumeration in Java 1.0/1.1 and the Iterator in Java 2. So you should already be familiar with their general use. If not, see Chapter 9, Holding Your Objects, under Iterators in Thinking in Java, 3rd edition (freely downloadable from www.BruceEckel.com). Because the Java 2 containers rely heavily on iterators they become excellent candidates for generic/functional programming techniques. This chapter will explore these techniques by converting the STL algorithms to Java, for use with the Java 2 container library.
184
185
186
CHAPTER
THIRTY
187
188
shape.draw() shape.erase()
The factory( ) takes an argument that allows it to determine what type of Shape to create; it happens to be a String in this case but it could be any set of data. The factory( ) is now the only other code in the system that needs to be changed when a new type of Shape is added (the initialization data for the objects will presumably come from somewhere outside the system, and not be a hard-coded array as in the above example). Note that this example also shows the new Python 2.2 staticmethod( ) technique for creating static methods in a class. I have also used a tool which is new in Python 2.2 called a generator. A generator is a special case of a factory: its a factory that takes no arguments in order to create a new object. Normally you hand some information to a factory in order to tell it what kind of object to create and how to create it, but a generator has some kind of internal algorithm that tells it what and how to build. It generates out of thin air rather than being told what to create. Now, this may not look consistent with the code you see above:
for i in shapeNameGen(7)
looks like theres an initialization taking place. This is where a generator is a bit strange - when you call a function that contains a yield statement (yield is a new keyword that determines that a function is a generator), that function actually returns a generator object that has an iterator. This iterator is implicitly used in the for statement above, so it appears that you are iterating through the generator function, not what it returns. This was done for convenience of use. Thus, the code that you write is actually a kind of factory, that creates the generator objects that do the actual generation. You can use the generator explicitly if you want, for example:
gen = shapeNameGen(7) print(gen.next())
So next( ) is the iterator method thats actually called to generate the next object, and it takes no arguments. shapeNameGen( ) is the factory, and gen is the
189
generator. Inside the generator-factory, you can see the call to __subclasses__( ), which produces a list of references to each of the subclasses of Shape (which must be inherited from object for this to work). You should be aware, however, that this only works for the rst level of inheritance from Item, so if you were to inherit a new class from Circle, it wouldnt show up in the list generated by __subclasses__( ). If you need to create a deeper hierarchy this way, you must recurse the __subclasses__( ) list. Also note that in shapeNameGen( ) the statement:
types = Shape.__subclasses__()
Is only executed when the generator object is produced; each time the next( ) method of this generator object is called (which, as noted above, may happen implicitly), only the code in the for loop will be executed, so you dont have wasteful execution (as you would if this were an ordinary function).
190
if type == "Square": return Square() assert 0, "Bad shape creation: " + type def shapeNameGen(n): for i in range(n): yield factory(random.choice(["Circle", "Square"])) # Circle() # Not defined for shape in shapeNameGen(7): shape.draw() shape.erase()
191
# A Template Method: def createShape(id): if not ShapeFactory.factories.has_key(id): ShapeFactory.factories[id] = \ eval(id + .Factory()) return ShapeFactory.factories[id].create() createShape = staticmethod(createShape) class Shape(object): pass class Circle(Shape): def draw(self): print("Circle.draw") def erase(self): print("Circle.erase") class Factory: def create(self): return Circle() class Square(Shape): def draw(self): print("Square.draw") def erase(self): print("Square.erase") class Factory: def create(self): return Square() def shapeNameGen(n): types = Shape.__subclasses__() for i in range(n): yield random.choice(types).__name__ shapes = [ ShapeFactory.createShape(i) for i in shapeNameGen(7)] for shape in shapes: shape.draw() shape.erase()
Now the factory method appears in its own class, ShapeFactory, as the create( ) method. The different types of shapes must each create their own factory with a create( ) method to create an object of their own type. The actual creation of shapes is performed by calling ShapeFactory.createShape( ), which is a static method that uses the dictionary in ShapeFactory to nd the appropriate factory 192 Chapter 30. Factory: Encapsulating Object Creation
object based on an identier that you pass it. The factory is immediately used to create the shape object, but you could imagine a more complex problem where the appropriate factory object is returned and then used by the caller to create an object in a more sophisticated way. However, it seems that much of the time you dont need the intricacies of the polymorphic factory method, and a single static method in the base class (as shown in ShapeFactory1.py) will work ne. Notice that the ShapeFactory must be initialized by loading its dictionary with factory objects, which takes place in the static initialization clause of each of the shape implementations.
193
print("Kitty has encountered a", obstacle.action()) class KungFuGuy(Character): def interactWith(self, obstacle): print("KungFuGuy now battles a", obstacle.action()) class Puzzle(Obstacle): def action(self): print("Puzzle") class NastyWeapon(Obstacle): def action(self): print("NastyWeapon") # The Abstract Factory: class GameElementFactory: def makeCharacter(self): pass def makeObstacle(self): pass # Concrete factories: class KittiesAndPuzzles(GameElementFactory): def makeCharacter(self): return Kitty() def makeObstacle(self): return Puzzle() class KillAndDismember(GameElementFactory): def makeCharacter(self): return KungFuGuy() def makeObstacle(self): return NastyWeapon() class GameEnvironment: def __init__(self, factory): self.factory = factory self.p = factory.makeCharacter() self.ob = factory.makeObstacle() def play(self): self.p.interactWith(self.ob) g1 = GameEnvironment(KittiesAndPuzzles()) g2 = GameEnvironment(KillAndDismember()) g1.play()
194
g2.play()
In this environment, Character objects interact with Obstacle objects, but there are different types of Characters and obstacles depending on what kind of game youre playing. You determine the kind of game by choosing a particular GameElementFactory, and then the GameEnvironment controls the setup and play of the game. In this example, the setup and play is very simple, but those activities (the initial conditions and the state change) can determine much of the games outcome. Here, GameEnvironment is not designed to be inherited, although it could very possibly make sense to do that. This also contains examples of Double Dispatching and the Factory Method, both of which will be explained later. Of course, the above scaffolding of Obstacle, Character and GameElementFactory (which was translated from the Java version of this example) is unnecessary - its only required for languages that have static type checking. As long as the concrete Python classes follow the form of the required classes, we dont need any base classes:
# Factory/Games2.py # Simplified Abstract Factory. class Kitty: def interactWith(self, obstacle): print("Kitty has encountered a", obstacle.action()) class KungFuGuy: def interactWith(self, obstacle): print("KungFuGuy now battles a", obstacle.action()) class Puzzle: def action(self): print("Puzzle") class NastyWeapon: def action(self): print("NastyWeapon") # Concrete factories: class KittiesAndPuzzles:
195
def makeCharacter(self): return Kitty() def makeObstacle(self): return Puzzle() class KillAndDismember: def makeCharacter(self): return KungFuGuy() def makeObstacle(self): return NastyWeapon() class GameEnvironment: def __init__(self, factory): self.factory = factory self.p = factory.makeCharacter() self.ob = factory.makeObstacle() def play(self): self.p.interactWith(self.ob) g1 = GameEnvironment(KittiesAndPuzzles()) g2 = GameEnvironment(KillAndDismember()) g1.play() g2.play()
Another way to put this is that all inheritance in Python is implementation inheritance; since Python does its type-checking at runtime, theres no need to use interface inheritance so that you can upcast to the base type. You might want to study the two examples for comparison, however. Does the rst one add enough useful information about the pattern that its worth keeping some aspect of it? Perhaps all you need is tagging classes like this:
class Obstacle: pass class Character: pass class GameElementFactory: pass
Then the inheritance serves only to indicate the type of the derived classes.
30.4 Exercises
1. Add a class Triangle to ShapeFactory1.py 2. Add a class Triangle to ShapeFactory2.py 196 Chapter 30. Factory: Encapsulating Object Creation
3. Add a new type of GameEnvironment called GnomesAndFairies to GameEnvironment.py 4. Modify ShapeFactory2.py so that it uses an Abstract Factory to create different sets of shapes (for example, one particular type of factory object creates thick shapes, another creates thin shapes, but each factory object can create all the shapes: circles, squares, triangles etc.).
30.4. Exercises
197
198
CHAPTER
THIRTYONE
FUNCTION OBJECTS
In Advanced C++:Programming Styles And Idioms (Addison-Wesley, 1992), Jim Coplien coins the term functor which is an object whose sole purpose is to encapsulate a function (since functor has a meaning in mathematics, in this book I shall use the more explicit term function object). The point is to decouple the choice of function to be called from the site where that function is called. This term is mentioned but not used in Design Patterns. However, the theme of the function object is repeated in a number of patterns in that book.
199
class NewBrain(Command): def execute(self): print("You might even need a new brain.") class Afford(Command): def execute(self): print("I couldnt afford a whole new brain.") # An object that holds commands: class Macro: def __init__(self): self.commands = [] def add(self, command): self.commands.append(command) def run(self): for c in self.commands: c.execute() macro = Macro() macro.add(Loony()) macro.add(NewBrain()) macro.add(Afford()) macro.run()
The primary point of Command is to allow you to hand a desired action to a method or object. In the above example, this provides a way to queue a set of actions to be performed collectively. In this case, it allows you to dynamically create new behavior, something you can normally only do by writing new code but in the above example could be done by interpreting a script (see the Interpreter pattern if what you need to do gets very complex). Design Patterns says that Commands are an object-oriented replacement for callbacks. However, I think that the word back is an essential part of the concept of callbacks. That is, I think a callback actually reaches back to the creator of the callback. On the other hand, with a Command object you typically just create it and hand it to some method or object, and are not otherwise connected over time to the Command object. Thats my take on it, anyway. Later in this book, I combine a group of design patterns under the heading of callbacks.
200
201
def __init__(self, strategy): self.strategy = strategy def minima(self, line): return self.strategy.algorithm(line) def changeAlgorithm(self, newAlgorithm): self.strategy = newAlgorithm solver = MinimaSolver(LeastSquares()) line = [1.0, 2.0, 1.0, 2.0, -1.0, 3.0, 4.0, 5.0, 4.0] print(solver.minima(line)) solver.changeAlgorithm(Bisection()) print(solver.minima(line))
Note similarity with template method - TM claims distinction that it has more than one method to call, does things piecewise. However, its not unlikely that strategy object would have more than one method call; consider Shalloways order fulfullment system with country information in each strategy. Strategy example from standard Python: sort( ) takes a second optional argument that acts as a comparator object; this is a strategy. Note: A better, real world example is numerical integration, shown here: http://www.rosettacode.org/wiki/Numerical_Integration#Python
202
Strategies is successful. Instead of calling a single method to satisfy a request, multiple methods in the chain have a chance to satisfy the request, so it has the avor of an expert system. Since the chain is effectively a linked list, it can be dynamically created, so you could also think of it as a more general, dynamically-built switch statement. In the GoF, theres a fair amount of Thidiscussion of how to create the chain of responsibility as a linked list. However, when you look at the pattern it really shouldnt matter how the chain is maintained; thats an implementation detail. Since GoF was written before the Standard Template Library (STL) was incorporated into most C++ compilers, the reason for this is most likely (1) there was no list and thus they had to create one and (2) data structures are often taught as a fundamental skill in academia, and the idea that data structures should be standard tools available with the programming language may not have occurred to the GoF authors. I maintain that the implementation of Chain of Responsibility as a chain (specically, a linked list) adds nothing to the solution and can just as easily be implemented using a standard Python list, as shown below. Furthermore, youll see that Ive gone to some effort to separate the chain-management parts of the implementation from the various Strategies, so that the code can be more easily reused. In StrategyPattern.py, above, what you probably want is to automatically nd a solution. Chain of Responsibility provides a way to do this by chaining the Strategy objects together and providing a mechanism for them to automatically recurse through each one in the chain:
# FunctionObjects/ChainOfResponsibility.py # Carry the information into the strategy: class Messenger: pass # The Result object carries the result data and # whether the strategy was successful: class Result: def __init__(self): self.succeeded = 0 def isSuccessful(self): return self.succeeded def setSuccessful(self, succeeded): self.succeeded = succeeded
203
class Strategy: def __call__(messenger): pass def __str__(self): return "Trying " + self.__class__.__name__ \ + " algorithm" # Manage the movement through the chain and # find a successful result: class ChainLink: def __init__(self, chain, strategy): self.strategy = strategy self.chain = chain self.chain.append(self) def next(self): # Where this link is in the chain: location = self.chain.index(self) if not self.end(): return self.chain[location + 1] def end(self): return (self.chain.index(self) + 1 >= len(self.chain)) def __call__(self, messenger): r = self.strategy(messenger) if r.isSuccessful() or self.end(): return r return self.next()(messenger) # For this example, the Messenger # and Result can be the same type: class LineData(Result, Messenger): def __init__(self, data): self.data = data def __str__(self): return self.data class LeastSquares(Strategy): def __call__(self, messenger): print(self) linedata = messenger # [ Actual test/calculation here ]
204
result = LineData([1.1, 2.2]) # Dummy data result.setSuccessful(0) return result class NewtonsMethod(Strategy): def __call__(self, messenger): print(self) linedata = messenger # [ Actual test/calculation here ] result = LineData([3.3, 4.4]) # Dummy data result.setSuccessful(0) return result class Bisection(Strategy): def __call__(self, messenger): print(self) linedata = messenger # [ Actual test/calculation here ] result = LineData([5.5, 6.6]) # Dummy data result.setSuccessful(1) return result class ConjugateGradient(Strategy): def __call__(self, messenger): print(self) linedata = messenger # [ Actual test/calculation here ] result = LineData([7.7, 8.8]) # Dummy data result.setSuccessful(1) return result solutions = [] ChainLink(solutions, ChainLink(solutions, ChainLink(solutions, ChainLink(solutions,
line = LineData([ 1.0, 2.0, 1.0, 2.0, -1.0, 3.0, 4.0, 5.0, 4.0 ])
205
print(solutions[0](line))
31.4 Exercises
1. Use Command in Chapter 3, Exercise 1. 2. Implement Chain of Responsibility to create an expert system that solves problems by successively trying one solution after another until one matches. You should be able to dynamically add solutions to the expert system. The test for solution should just be a string match, but when a solution ts, the expert system should return the appropriate type of ProblemSolver object. What other pattern/patterns show up here? Design Patterns, Page 235.
206
CHAPTER
THIRTYTWO
32.1 Adapter
When youve got this, and you need that, Adapter solves the problem. The only requirement is to produce a that, and there are a number of ways you can accomplish this adaptation:
# ChangeInterface/Adapter.py # Variations on the Adapter pattern. class WhatIHave: def g(self): pass def h(self): pass class WhatIWant: def f(self): pass class ProxyAdapter(WhatIWant): def __init__(self, whatIHave):
207
self.whatIHave = whatIHave def f(self): # Implement behavior using # methods in WhatIHave: self.whatIHave.g() self.whatIHave.h() class WhatIUse: def op(self, whatIWant): whatIWant.f() # Approach 2: build adapter use into op(): class WhatIUse2(WhatIUse): def op(self, whatIHave): ProxyAdapter(whatIHave).f() # Approach 3: build adapter into WhatIHave: class WhatIHave2(WhatIHave, WhatIWant): def f(self): self.g() self.h() # Approach 4: use an inner class: class WhatIHave3(WhatIHave): class InnerAdapter(WhatIWant): def __init__(self, outer): self.outer = outer def f(self): self.outer.g() self.outer.h() def whatIWant(self): return WhatIHave3.InnerAdapter(self) whatIUse = WhatIUse() whatIHave = WhatIHave() adapt= ProxyAdapter(whatIHave) whatIUse2 = WhatIUse2() whatIHave2 = WhatIHave2() whatIHave3 = WhatIHave3()
208
Im taking liberties with the term proxy here, because in Design Patterns they assert that a proxy must have an identical interface with the object that it is a surrogate for. However, if you have the two words together: proxy adapter, it is perhaps more reasonable.
32.2 Faade
A general principle that I apply when Im casting about trying to mold requirements into a rst-cut object is If something is ugly, hide it inside an object. This is basically what Faade accomplishes. If you have a rather confusing collection of classes and interactions that the client programmer doesnt really need to see, then you can create an interface that is useful for the client programmer and that only presents whats necessary. Faade is often implemented as singleton abstract factory. Of course, you can easily get this effect by creating a class containing static factory methods:
# ChangeInterface/Facade.py class A: def __init__(self, x): pass class B: def __init__(self, x): pass class C: def __init__(self, x): pass # Other classes that arent exposed by the # facade go here ... class Facade: def makeA(x): return A(x)
32.2. Faade
209
makeA = staticmethod(makeA) def makeB(x): return B(x) makeB = staticmethod(makeB) def makeC(x): return C(x) makeC = staticmethod(makeC) # # a b c The client programmer gets the objects by calling the static methods: = Facade.makeA(1); = Facade.makeB(1); = Facade.makeC(1.0);
[rewrite this section using research from Larmans book] Example for Facade (?): my nicer version of the XML library.
32.3 Exercises
1. Create an adapter class that automatically loads a two-dimensional array of objects into a dictionary as key-value pairs.
210
CHAPTER
THIRTYTHREE
211
212
CHAPTER
THIRTYFOUR
OBSERVER
Decoupling code behavior Observer, and a category of callbacks called multiple dispatching (not in Design Patterns) including the Visitor from Design Patterns. Like the other forms of callback, this contains a hook point where you can change code. The difference is in the observers completely dynamic nature. It is often used for the specic case of changes based on other objects change of state, but is also the basis of event management. Anytime you want to decouple the source of the call from the called code in a completely dynamic way. The observer pattern solves a fairly common problem: What if a group of objects needs to update themselves when some object changes state? This can be seen in the model-view aspect of Smalltalks MVC (model-view-controller), or the almost-equivalent Document-View Architecture. Suppose that you have some data (the document) and more than one view, say a plot and a textual view. When you change the data, the two views must know to update themselves, and thats what the observer facilitates. Its a common enough problem that its solution has been made a part of the standard java.util library. There are two types of objects used to implement the observer pattern in Python. The Observable class keeps track of everybody who wants to be informed when a change happens, whether the state has changed or not. When someone says OK, everybody should check and potentially update themselves, the Observable class performs this task by calling the notifyObservers( ) method for each one on the list. The notifyObservers( ) method is part of the base class Observable.
213
There are actually two things that change in the observer pattern: the quantity of observing objects and the way an update occurs. That is, the observer pattern allows you to modify both of these without affecting the surrounding code. Observer is an interface class that only has one member function, update( ). This function is called by the object thats being observed, when that object decides its time to update all its observers. The arguments are optional; you could have an update( ) with no arguments and that would still t the observer pattern; however this is more general-it allows the observed object to pass the object that caused the update (since an Observer may be registered with more than one observed object) and any extra information if thats helpful, rather than forcing the Observer object to hunt around to see who is updating and to fetch any other information it needs. The observed object that decides when and how to do the updating will be called the Observable. Observable has a ag to indicate whether its been changed. In a simpler design, there would be no ag; if something happened, everyone would be notied. The ag allows you to wait, and only notify the Observers when you decide the time is right. Notice, however, that the control of the ags state is protected, so that only an inheritor can decide what constitutes a change, and not the end user of the resulting derived Observer class. Most of the work is done in notifyObservers( ). If the changed ag has not been set, this does nothing. Otherwise, it rst clears the changed ag so repeated calls to notifyObservers( ) wont waste time. This is done before notifying the observers in case the calls to update( ) do anything that causes a change back to this Observable object. Then it moves through the set and calls back to the update( ) member function of each Observer. At rst it may appear that you can use an ordinary Observable object to manage the updates. But this doesnt work; to get an effect, you must inherit from Observable and somewhere in your derived-class code call setChanged( ). This is the member function that sets the changed ag, which means that when you call notifyObservers( ) all of the observers will, in fact, get notied. Where you call setChanged( ) depends on the logic of your program.
214
But this rapidly becomes tedious to write and to read. Peter Norvig provided me with a much nicer solution:
# Util/Synchronization.py Simple emulation of Javas synchronized keyword, from Peter Norvig. import threading def synchronized(method): def f(*args): self = args[0] self.mutex.acquire(); # print(method.__name__, acquired)
215
try: return apply(method, args) finally: self.mutex.release(); # print(method.__name__, released) return f def synchronize(klass, names=None): """Synchronize methods in the given class. Only synchronize the methods whose names are given, or all methods if names=None.""" if type(names)==type(): names = names.split() for (name, val) in klass.__dict__.items(): if callable(val) and name != __init__ and \ (names == None or name in names): # print("synchronizing", name) klass.__dict__[name] = synchronized(val) # You can create your own self.mutex, or inherit # from this class: class Synchronization: def __init__(self): self.mutex = threading.RLock()
The synchronized( ) function takes a method and wraps it in a function that adds the mutex functionality. The method is called inside this function:
return apply(method, args)
and as the return statement passes through the nally clause, the mutex is released. This is in some ways the Decorator design pattern, but much simpler to create and use. All you have to say is:
myMethod = synchronized(myMethod)
To surround your method with a mutex. synchronize( ) is a convenience function that applies synchronized( ) to an entire class, either all the methods in the class (the default) or selected methods which 216 Chapter 34. Observer
are named in a string as the second argument. Finally, for synchronized( ) to work there must be a self.mutex created in every class that uses synchronized( ). This can be created by hand by the class author, but its more consistent to use inheritance, so the base class Synchronization is provided. Heres a simple test of the Synchronization module:
# Util/TestSynchronization.py from Synchronization import * # To use for a method: class C(Synchronization): def __init__(self): Synchronization.__init__(self) self.data = 1 def m(self): self.data += 1 return self.data m = synchronized(m) def f(self): return 47 def g(self): return spam # So m is synchronized, f and g are not. c = C() # On the class level: class D(C): def __init__(self): C.__init__(self) # You must override an un-synchronized method # in order to synchronize it (just like Java): def f(self): C.f(self) # Synchronize every (defined) method in the class: synchronize(D) d = D() d.f() # Synchronized d.g() # Not synchronized d.m() # Synchronized (in the base class)
217
class E(C): def __init__(self): C.__init__(self) def m(self): C.m(self) def g(self): C.g(self) def f(self): C.f(self) # Only synchronizes m and g. Note that m ends up # being doubly-wrapped in synchronization, which # doesnt hurt anything but is inefficient: synchronize(E, m g) e = E() e.f() e.g() e.m()
You must call the base class constructor for Synchronization, but thats all. In class C you can see the use of synchronized( ) for m, leaving f and g alone. Class D has all its methods synchronized en masse, and class E uses the convenience function to synchronize m and g. Note that since m ends up being synchronized twice, it will be entered and left twice for every call, which isnt very desirable [there may be a x for this]:
# Util/Observer.py # Class support for "observer" pattern. from Synchronization import * class Observer: def update(observable, arg): Called when the observed object is modified. You call an Observable objects notifyObservers method to notify all the objects observers of the change. pass class Observable(Synchronization): def __init__(self): self.obs = [] self.changed = 0 Synchronization.__init__(self) def addObserver(self, observer):
218
if observer not in self.obs: self.obs.append(observer) def deleteObserver(self, observer): self.obs.remove(observer) def notifyObservers(self, arg = None): If changed indicates that this object has changed, notify all its observers, then call clearChanged(). Each observer has its update() called with two arguments: this observable object and the generic arg. self.mutex.acquire() try: if not self.changed: return # Make a local copy in case of synchronous # additions of observers: localArray = self.obs[:] self.clearChanged() finally: self.mutex.release() # Updating is not required to be synchronized: for observer in localArray: observer.update(self, arg) def def def def def deleteObservers(self): self.obs = [] setChanged(self): self.changed = 1 clearChanged(self): self.changed = 0 hasChanged(self): return self.changed countObservers(self): return len(self.obs)
synchronize(Observable, "addObserver deleteObserver deleteObservers " + "setChanged clearChanged hasChanged " + "countObservers")
219
import sys sys.path += [../util] from Observer import Observer, Observable class Flower: def __init__(self): self.isOpen = 0 self.openNotifier = Flower.OpenNotifier(self) self.closeNotifier= Flower.CloseNotifier(self) def open(self): # Opens its petals self.isOpen = 1 self.openNotifier.notifyObservers() self.closeNotifier.open() def close(self): # Closes its petals self.isOpen = 0 self.closeNotifier.notifyObservers() self.openNotifier.close() def closing(self): return self.closeNotifier class OpenNotifier(Observable): def __init__(self, outer): Observable.__init__(self) self.outer = outer self.alreadyOpen = 0 def notifyObservers(self): if self.outer.isOpen and \ not self.alreadyOpen: self.setChanged() Observable.notifyObservers(self) self.alreadyOpen = 1 def close(self): self.alreadyOpen = 0 class CloseNotifier(Observable): def __init__(self, outer): Observable.__init__(self) self.outer = outer self.alreadyClosed = 0 def notifyObservers(self): if not self.outer.isOpen and \ not self.alreadyClosed:
220
self.setChanged() Observable.notifyObservers(self) self.alreadyClosed = 1 def open(self): alreadyClosed = 0 class Bee: def __init__(self, name): self.name = name self.openObserver = Bee.OpenObserver(self) self.closeObserver = Bee.CloseObserver(self) # An inner class for observing openings: class OpenObserver(Observer): def __init__(self, outer): self.outer = outer def update(self, observable, arg): print("Bee " + self.outer.name + \) "s breakfast time!" # Another inner class for closings: class CloseObserver(Observer): def __init__(self, outer): self.outer = outer def update(self, observable, arg): print("Bee " + self.outer.name + \) "s bed time!" class Hummingbird: def __init__(self, name): self.name = name self.openObserver = \ Hummingbird.OpenObserver(self) self.closeObserver = \ Hummingbird.CloseObserver(self) class OpenObserver(Observer): def __init__(self, outer): self.outer = outer def update(self, observable, arg): print("Hummingbird " + self.outer.name + \ "s breakfast time!") class CloseObserver(Observer): def __init__(self, outer):
221
self.outer = outer def update(self, observable, arg): print("Hummingbird " + self.outer.name + \ "s bed time!") f = Flower() ba = Bee("Eric") bb = Bee("Eric 0.5") ha = Hummingbird("A") hb = Hummingbird("B") f.openNotifier.addObserver(ha.openObserver) f.openNotifier.addObserver(hb.openObserver) f.openNotifier.addObserver(ba.openObserver) f.openNotifier.addObserver(bb.openObserver) f.closeNotifier.addObserver(ha.closeObserver) f.closeNotifier.addObserver(hb.closeObserver) f.closeNotifier.addObserver(ba.closeObserver) f.closeNotifier.addObserver(bb.closeObserver) # Hummingbird 2 decides to sleep in: f.openNotifier.deleteObserver(hb.openObserver) # A change that interests observers: f.open() f.open() # Its already open, no change. # Bee 1 doesnt want to go to bed: f.closeNotifier.deleteObserver(ba.closeObserver) f.close() f.close() # Its already closed; no change f.openNotifier.deleteObservers() f.open() f.close()
The events of interest are that a Flower can open or close. Because of the use of the inner class idiom, both these events can be separately observable phenomena. OpenNotier and CloseNotier both inherit Observable, so they have access to setChanged( ) and can be handed to anything that needs an Observable. The inner class idiom also comes in handy to dene more than one kind of Observer, in Bee and Hummingbird, since both those classes may want to independently observe Flower openings and closings. Notice how the inner class idiom provides something that has most of the benets of inheritance (the ability to access the private data in the outer class, for example) without the same 222 Chapter 34. Observer
restrictions. In main( ), you can see one of the prime benets of the observer pattern: the ability to change behavior at run time by dynamically registering and un- registering Observers with Observables. If you study the code above youll see that OpenNotier and CloseNotier use the basic Observable interface. This means that you could inherit other completely different Observer classes; the only connection the Observers have with Flowers is the Observer interface.
223
cp.setLayout(GridLayout(grid, grid)) for(int x = 0 x < grid x++) for(int y = 0 y < grid y++) cp.add(OCBox(x, y, notifier)) def main(self, String[] args): grid = 8 if(args.length > 0) grid = Integer.parseInt(args[0]) JFrame f = BoxObserver(grid) f.setSize(500, 400) f.setVisible(1) # JDK 1.3: f.setDefaultCloseOperation(EXIT_ON_CLOSE) # Add a WindowAdapter if you have JDK 1.2 class OCBox(JPanel) implements Observer: Color cColor = newColor() colors = [ Color.black, Color.blue, Color.cyan, Color.darkGray, Color.gray, Color.green, Color.lightGray, Color.magenta, Color.orange, Color.pink, Color.red, Color.white, Color.yellow ] def newColor(): return colors[ (int)(Math.random() * colors.length) ] def __init__(self, x, y, Observable notifier): self.x = x self.y = y notifier.addObserver(self) self.notifier = notifier addMouseListener(ML()) def paintComponent(self, Graphics g): super.paintComponent(g) g.setColor(cColor) Dimension s = getSize()
224
g.fillRect(0, 0, s.width, s.height) class ML(MouseAdapter): def mousePressed(self, MouseEvent e): notifier.notifyObservers(OCBox.self) def update(self, Observable o, Object arg): OCBox clicked = (OCBox)arg if(nextTo(clicked)): cColor = clicked.cColor repaint() def nextTo(OCBox b): return Math.abs(x - b.x) <= 1 && Math.abs(y - b.y) <= 1
When you rst look at the online documentation for Observable, its a bit confusing because it appears that you can use an ordinary Observable object to manage the updates. But this doesnt work; try it-inside BoxObserver, create an Observable object instead of a BoxObservable object and see what happens: nothing. To get an effect, you must inherit from Observable and somewhere in your derivedclass code call setChanged( ). This is the method that sets the changed ag, which means that when you call notifyObservers( ) all of the observers will, in fact, get notied. In the example above setChanged( ) is simply called within notifyObservers( ), but you could use any criterion you want to decide when to call setChanged( ). BoxObserver contains a single Observable object called notier, and every time an OCBox object is created, it is tied to notier. In OCBox, whenever you click the mouse the notifyObservers( ) method is called, passing the clicked object in as an argument so that all the boxes receiving the message (in their update( ) method) know who was clicked and can decide whether to change themselves or not. Using a combination of code in notifyObservers( ) and update( ) you can work out some fairly complex schemes. It might appear that the way the observers are notied must be frozen at compile time in the notifyObservers( ) method. However, if you look more closely at the code above youll see that the only place in BoxObserver or OCBox where youre aware that youre working with a BoxObservable is at the point of creation of the Observable object-from then on everything uses the basic Observable interface.
225
This means that you could inherit other Observable classes and swap them at run time if you want to change notication behavior then. Here is a version of the above that doesnt use the Observer pattern, written by Kevin Altis using PythonCard, and placed here as a starting point for a translation that does include Observer:
# Observer/BoxObserverPythonCard.py """ Written by Kevin Altis as a first-cut for converting BoxObserver to Python. The Observer hasnt been integrated yet. To run this program, you must: Install WxPython from http://www.wxpython.org/download.php Install PythonCard. See: http://pythoncard.sourceforge.net """ from PythonCardPrototype import log, model import random GRID = 8 class ColorBoxesTest(model.Background): def on_openBackground(self, event): self.document = [] for row in range(GRID): line = [] for column in range(GRID): line.append(self.createBox(row, column)) self.document.append(line[:]) def createBox(self, row, column): colors = [black, blue, cyan, darkGray, gray, green, lightGray, magenta, orange, pink, red, white, yellow] width, height = self.panel.GetSizeTuple() boxWidth = width / GRID boxHeight = height / GRID log.info("width:" + str(width) + " height:" + str(height)) log.info("boxWidth:" + str(boxWidth) +
226
" boxHeight:" + str(boxHeight)) # use an empty image, though some other # widgets would work just as well boxDesc = {type:Image, size:(boxWidth, boxHeight), file:} name = box-%d-%d % (row, column) # There is probably a 1 off error in the # calculation below since the boxes should # probably have a slightly different offset # to prevent overlaps boxDesc[position] = \ (column * boxWidth, row * boxHeight) boxDesc[name] = name boxDesc[backgroundColor] = \ random.choice(colors) self.components[name] = boxDesc return self.components[name] def changeNeighbors(self, row, column, color): # # # # # # # # # # This algorithm will result in changing the color of some boxes more than once, so an OOP solution where only neighbors are asked to change or boxes check to see if they are neighbors before changing would be better per the original example does the whole grid need to change its state at once like in a Life program? should the color change in the propogation of another notification event?
for r in range(max(0, row - 1), min(GRID, row + 2)): for c in range(max(0, column - 1), min(GRID, column + 2)): self.document[r][c].backgroundColor=color # # # # this is a background handler, so it isnt specific to a single widget. Image widgets dont have a mouseClick event (wxCommandEvent in wxPython)
227
def on_mouseUp(self, event): target = event.target prefix, row, column = target.name.split(-) self.changeNeighbors(int(row), int(column), target.backgroundColor) if __name__ == __main__: app = model.PythonCardApp(ColorBoxesTest) app.MainLoop()
This is the resource le for running the program (see PythonCard for details):
# Observer/BoxObserver.rsrc.py {stack:{type:Stack, name:BoxObserver, backgrounds: [ { type:Background, name:bgBoxObserver, title:Demonstrates Observer pattern, position:(5, 5), size:(500, 400), components: [ ] } ] } # end components # end background # end backgrounds }
34.1.2 Exercises
1. Using the approach in Synchronization.py, create a tool that will automatically wrap all the methods in a class to provide an execution trace, so that you can see the name of the method and when it is entered and exited. 2. Create a minimal Observer-Observable design in two classes. Just create the bare minimum in the two classes, then demonstrate your design by creating one Observable and many Observers, and cause the Observable to update the Observers.
228
3. Modify BoxObserver.py to turn it into a simple game. If any of the squares surrounding the one you clicked is part of a contiguous patch of the same color, then all the squares in that patch are changed to the color you clicked on. You can congure the game for competition between players or to keep track of the number of clicks that a single player uses to turn the eld into a single color. You may also want to restrict a players color to the rst one that was chosen.
229
230
CHAPTER
THIRTYFIVE
MULTIPLE DISPATCHING
When dealing with multiple types which are interacting, a program can get particularly messy. For example, consider a system that parses and executes mathematical expressions. You want to be able to say Number + Number, Number * Number, etc., where Number is the base class for a family of numerical objects. But when you say a + b, and you dont know the exact type of either a or b, so how can you get them to interact properly? The answer starts with something you probably dont think about: Python performs only single dispatching. That is, if you are performing an operation on more than one object whose type is unknown, Python can invoke the dynamic binding mechanism on only one of those types. This doesnt solve the problem, so you end up detecting some types manually and effectively producing your own dynamic binding behavior. The solution is called multiple dispatching. Remember that polymorphism can occur only via member function calls, so if you want double dispatching to occur, there must be two member function calls: the rst to determine the rst unknown type, and the second to determine the second unknown type. With multiple dispatching, you must have a polymorphic method call to determine each of the types. Generally, youll set up a conguration such that a single member function call produces more than one dynamic member function call and thus determines more than one type in the process. To get this effect, you need to work with more than one polymorphic method call: youll need one call for each dispatch. The methods in the following example are called compete( ) and eval( ), and are both members of the same type. (In this case there will be only two dispatches, which is referred to as double dispatching). If you are working with two different type
231
hierarchies that are interacting, then youll have to have a polymorphic method call in each hierarchy. Heres an example of multiple dispatching:
# MultipleDispatching/PaperScissorsRock.py # Demonstration of multiple dispatching. from __future__ import generators import random # An enumeration type: class Outcome: def __init__(self, value, name): self.value = value self.name = name def __str__(self): return self.name def __eq__(self, other): return self.value == other.value Outcome.WIN = Outcome(0, "win") Outcome.LOSE = Outcome(1, "lose") Outcome.DRAW = Outcome(2, "draw") class Item(object): def __str__(self): return self.__class__.__name__ class Paper(Item): def compete(self, item): # First dispatch: self was Paper return item.evalPaper(self) def evalPaper(self, item): # Item was Paper, were in Paper return Outcome.DRAW def evalScissors(self, item): # Item was Scissors, were in Paper return Outcome.WIN def evalRock(self, item): # Item was Rock, were in Paper return Outcome.LOSE class Scissors(Item):
232
def compete(self, item): # First dispatch: self was Scissors return item.evalScissors(self) def evalPaper(self, item): # Item was Paper, were in Scissors return Outcome.LOSE def evalScissors(self, item): # Item was Scissors, were in Scissors return Outcome.DRAW def evalRock(self, item): # Item was Rock, were in Scissors return Outcome.WIN class Rock(Item): def compete(self, item): # First dispatch: self was Rock return item.evalRock(self) def evalPaper(self, item): # Item was Paper, were in Rock return Outcome.WIN def evalScissors(self, item): # Item was Scissors, were in Rock return Outcome.LOSE def evalRock(self, item): # Item was Rock, were in Rock return Outcome.DRAW def match(item1, item2): print("%s <--> %s : %s" % ( item1, item2, item1.compete(item2))) # Generate the items: def itemPairGen(n): # Create a list of instances of all Items: Items = Item.__subclasses__() for i in range(n): yield (random.choice(Items)(), random.choice(Items)()) for item1, item2 in itemPairGen(20): match(item1, item2)
233
This was a fairly literal translation from the Java version, and one of the things you might notice is that the information about the various combinations is encoded into each type of Item. It actually ends up being a kind of table, except that it is spread out through all the classes. This is not very easy to maintain if you ever expect to modify the behavior or to add a new Item class. Instead, it can be more sensible to make the table explicit, like this:
# MultipleDispatching/PaperScissorsRock2.py # Multiple dispatching using a table from __future__ import generators import random class Outcome: def __init__(self, value, name): self.value = value self.name = name def __str__(self): return self.name def __eq__(self, other): return self.value == other.value Outcome.WIN = Outcome(0, "win") Outcome.LOSE = Outcome(1, "lose") Outcome.DRAW = Outcome(2, "draw") class Item(object): def compete(self, item): # Use a tuple for table lookup: return outcome[self.__class__, item.__class__] def __str__(self): return self.__class__.__name__ class Paper(Item): pass class Scissors(Item): pass class Rock(Item): pass outcome = { (Paper, Rock): Outcome.WIN, (Paper, Scissors): Outcome.LOSE, (Paper, Paper): Outcome.DRAW, (Scissors, Paper): Outcome.WIN, (Scissors, Rock): Outcome.LOSE,
234
(Scissors, Scissors): Outcome.DRAW, (Rock, Scissors): Outcome.WIN, (Rock, Paper): Outcome.LOSE, (Rock, Rock): Outcome.DRAW, } def match(item1, item2): print("%s <--> %s : %s" % ( item1, item2, item1.compete(item2))) # Generate the items: def itemPairGen(n): # Create a list of instances of all Items: Items = Item.__subclasses__() for i in range(n): yield (random.choice(Items)(), random.choice(Items)()) for item1, item2 in itemPairGen(20): match(item1, item2)
Its a tribute to the exibility of dictionaries that a tuple can be used as a key just as easily as a single object.
235
236
CHAPTER
THIRTYSIX
VISITOR
The visitor pattern is implemented using multiple dispatching, but people often confuse the two, because they look at the implementation rather than the intent. The assumption is that you have a primary class hierarchy that is xed; perhaps its from another vendor and you cant make changes to that hierarchy. However, your intent is that youd like to add new polymorphic methods to that hierarchy, which means that normally youd have to add something to the base class interface. So the dilemma is that you need to add methods to the base class, but you cant touch the base class. How do you get around this? The design pattern that solves this kind of problem is called a visitor (the nal one in the Design Patterns book), and it builds on the double dispatching scheme shown in the last section. The visitor pattern allows you to extend the interface of the primary type by creating a separate class hierarchy of type Visitor to virtualize the operations performed upon the primary type. The objects of the primary type simply accept the visitor, then call the visitors dynamically-bound member function:
# Visitor/FlowerVisitors.py # Demonstration of "visitor" pattern. from __future__ import generators import random # The Flower hierarchy cannot be changed: class Flower(object): def accept(self, visitor):
237
visitor.visit(self) def pollinate(self, pollinator): print(self, "pollinated by", pollinator) def eat(self, eater): print(self, "eaten by", eater) def __str__(self): return self.__class__.__name__ class Gladiolus(Flower): pass class Runuculus(Flower): pass class Chrysanthemum(Flower): pass class Visitor: def __str__(self): return self.__class__.__name__ class Bug(Visitor): pass class Pollinator(Bug): pass class Predator(Bug): pass # Add the ability to do "Bee" activities: class Bee(Pollinator): def visit(self, flower): flower.pollinate(self) # Add the ability to do "Fly" activities: class Fly(Pollinator): def visit(self, flower): flower.pollinate(self) # Add the ability to do "Worm" activities: class Worm(Predator): def visit(self, flower): flower.eat(self) def flowerGen(n): flwrs = Flower.__subclasses__() for i in range(n): yield random.choice(flwrs)() # Its almost as if I had a method to Perform
238
# various "Bug" operations on all Flowers: bee = Bee() fly = Fly() worm = Worm() for flower in flowerGen(10): flower.accept(bee) flower.accept(fly) flower.accept(worm)
36.1 Exercises
1. Create a business-modeling environment with three types of Inhabitant: Dwarf (for engineers), Elf (for marketers) and Troll (for managers). Now create a class called Project that creates the different inhabitants and causes them to interact( ) with each other using multiple dispatching. 2. Modify the above example to make the interactions more detailed. Each Inhabitant can randomly produce a Weapon using getWeapon( ): a Dwarf uses Jargon or Play, an Elf uses InventFeature or SellImaginaryProduct, and a Troll uses Edict and Schedule. You must decide which weapons win and lose in each interaction (as in PaperScissorsRock.py). Add a battle( ) member function to Project that takes two Inhabitants and matches them against each other. Now create a meeting( ) member function for Project that creates groups of Dwarf, Elf and Manager and battles the groups against each other until only members of one group are left standing. These are the winners. 3. Modify PaperScissorsRock.py to replace the double dispatching with a table lookup. The easiest way to do this is to create a Map of Maps, with the key of each Map the class of each object. Then you can do the lookup by saying: ((Map)map.get(o1.getClass())).get(o2.getClass()) Notice how much easier it is to recongure the system. When is it more appropriate to use this approach vs. hard-coding the dynamic dispatches? Can you create a system that has the syntactic simplicity of use of the dynamic dispatch but uses a table lookup? 4. Modify Exercise 2 to use the table lookup technique described in Exercise 3. 36.1. Exercises 239
240
CHAPTER
THIRTYSEVEN
PATTERN REFACTORING
Note: This chapter has not had any signicant translation yet. This chapter will look at the process of solving a problem by applying design patterns in an evolutionary fashion. That is, a rst cut design will be used for the initial solution, and then this solution will be examined and various design patterns will be applied to the problem (some of which will work, and some of which wont). The key question that will always be asked in seeking improved solutions is what will change? This process is similar to what Martin Fowler talks about in his book Refactoring: Improving the Design of Existing Code [#]_ (although he tends to talk about pieces of code more than pattern-level designs). You start with a solution, and then when you discover that it doesnt continue to meet your needs, you x it. Of course, this is a natural tendency but in computer programming its been extremely difcult to accomplish with procedural programs, and the acceptance of the idea that we can refactor code and designs adds to the body of proof that object-oriented programming is a good thing.
241
This is not a trivial design because it has an added constraint. Thats what makes it interesting-its more like the messy problems youre likely to encounter in your work. The extra constraint is that the trash arrives at the trash recycling plant all mixed together. The program must model the sorting of that trash. This is where RTTI comes in: you have a bunch of anonymous pieces of trash, and the program gures out exactly what type they are:
# PatternRefactoring/recyclea/RecycleA.py # Recycling with RTTI. class Trash: def __init__(self, wt): self.weight = wt abstract def getValue() def getWeight(): return weight # Sums the value of Trash in a bin: def sumValue(Iterator it): val = 0.0f while(it.hasNext()): # One kind of RTTI: # A dynamically-checked cast Trash t = (Trash)it.next() # Polymorphism in action: val += t.getWeight() * t.getValue() print ( "weight of " + # Using RTTI to get type # information about the class: t.getClass().getName() + " = " + t.getWeight()) print("Total value = " + val) class Aluminum(Trash): val = 1.67f def __init__(self, wt): Trash.__init__(wt) def getValue(): return self.val setValue(newval): val = newval
242
class Paper(Trash): val = 0.10f def __init__(self, wt): Trash.__init__(wt) def getValue(): return self.val def setValue(self, newval): val = newval class Glass(Trash): val = 0.23f def __init__(self, wt): Trash.__init__(wt) def getValue(self): return self.val def setValue(self, newval): val = newval class RecycleA(UnitTest): bin = ArrayList() glassBin = ArrayList() paperBin = ArrayList() alBin = ArrayList() def __init__(self): # Fill up the Trash bin: for(int i = 0 i < 30 i++) switch((int)(Math.random() * 3)): case 0: bin.add(new Aluminum(Math.random() * 100)) break case 1: bin.add(new Paper(Math.random() * 100)) break case 2: bin.add(new Glass(Math.random() * 100)) def test(self): Iterator sorter = bin.iterator() # Sort the Trash:
243
while(sorter.hasNext()): Object t = sorter.next() # RTTI to show class membership: if(t instanceof Aluminum) alBin.add(t) if(t instanceof Paper) paperBin.add(t) if(t instanceof Glass) glassBin.add(t) Trash.sumValue(alBin.iterator()) Trash.sumValue(paperBin.iterator()) Trash.sumValue(glassBin.iterator()) Trash.sumValue(bin.iterator()) def main(self, String args[]): RecycleA().test()
In the source code listings available for this book, this le will be placed in the subdirectory recyclea that branches off from the subdirectory patternRefactoring. The unpacking tool takes care of placing it into the correct subdirectory. The reason for doing this is that this chapter rewrites this particular example a number of times and by putting each version in its own directory (using the default package in each directory so that invoking the program is easy), the class names will not clash. Several ArrayList objects are created to hold Trash references. Of course, ArrayLists actually hold Objects so theyll hold anything at all. The reason they hold Trash (or something derived from Trash) is only because youve been careful to not put in anything except Trash. If you do put something wrong into the ArrayList, you wont get any compile-time warnings or errors-youll nd out only via an exception at run time. When the Trash references are added, they lose their specic identities and become simply Object references (they are upcast). However, because of polymorphism the proper behavior still occurs when the dynamically-bound methods are called through the Iterator sorter, once the resulting Object has been cast back to Trash. sumValue( ) also takes an Iterator to perform operations on every object in the ArrayList. It looks silly to upcast the types of Trash into a container holding base type ref244 Chapter 37. Pattern Refactoring
erences, and then turn around and downcast. Why not just put the trash into the appropriate receptacle in the rst place? (Indeed, this is the whole enigma of recycling). In this program it would be easy to repair, but sometimes a systems structure and exibility can benet greatly from downcasting. The program satises the design requirements: it works. This might be ne as long as its a one-shot solution. However, a useful program tends to evolve over time, so you must ask, What if the situation changes? For example, cardboard is now a valuable recyclable commodity, so how will that be integrated into the system (especially if the program is large and complicated). Since the above typecheck coding in the switch statement could be scattered throughout the program, you must go nd all that code every time a new type is added, and if you miss one the compiler wont give you any help by pointing out an error. The key to the misuse of RTTI here is that every type is tested. If youre looking for only a subset of types because that subset needs special treatment, thats probably ne. But if youre hunting for every type inside a switch statement, then youre probably missing an important point, and denitely making your code less maintainable. In the next section well look at how this program evolved over several stages to become much more exible. This should prove a valuable example in program design.
capsulations. It turns out that this process also cleans up the rest of the code considerably.
This is denitely messy, and also a place where you must change code whenever a new type is added. If new types are commonly added, a better solution is a single method that takes all of the necessary information and produces a reference to an object of the correct type, already upcast to a trash object. In Design Patterns this is broadly referred to as a creational pattern (of which there are several). The specic pattern that will be applied here is a variant of the Factory Method. Here, the factory method is a static member of Trash, but more commonly it is a method that is overridden in the derived class. 246 Chapter 37. Pattern Refactoring
The idea of the factory method is that you pass it the essential information it needs to know to create your object, then stand back and wait for the reference (already upcast to the base type) to pop out as the return value. From then on, you treat the object polymorphically. Thus, you never even need to know the exact type of object thats created. In fact, the factory method hides it from you to prevent accidental misuse. If you want to use the object without polymorphism, you must explicitly use RTTI and casting. But theres a little problem, especially when you use the more complicated approach (not shown here) of making the factory method in the base class and overriding it in the derived classes. What if the information required in the derived class requires more or different arguments? Creating more objects solves this problem. To implement the factory method, the Trash class gets a new method called factory. To hide the creational data, theres a new class called Messenger that carries all of the necessary information for the factory method to create the appropriate Trash object (weve started referring to Messenger as a design pattern, but its simple enough that you may not choose to elevate it to that status). Heres a simple implementation of Messenger:
# PatternRefactoring/clip2.py class Messenger: # Must change this to add another type: MAX_NUM = 4 def __init__(self, typeNum, val): self.type = typeNum % MAX_NUM self.data = val
A Messenger objects only job is to hold information for the factory( ) method. Now, if theres a situation in which factory( ) needs more or different information to create a new type of Trash object, the factory( ) interface doesnt need to be changed. The Messenger class can be changed by adding new data and new constructors, or in the more typical object-oriented fashion of subclassing. The factory( ) method for this simple example looks like this:
# PatternRefactoring/clip3.py def factory(messenger): switch(messenger.type): default: # To quiet the compiler case 0: return Aluminum(messenger.data)
247
case 1: return Paper(messenger.data) case 2: return Glass(messenger.data) # Two lines here: case 3: return Cardboard(messenger.data)
Here, the determination of the exact type of object is simple, but you can imagine a more complicated system in which factory( ) uses an elaborate algorithm. The point is that its now hidden away in one place, and you know to come to this place when you add new types. The creation of new objects is now much simpler in main( ):
# PatternRefactoring/clip4.py for(int i = 0 i < 30 i++) bin.add( Trash.factory( Messenger( (int)(Math.random() * Messenger.MAX_NUM), Math.random() * 100)))
A Messenger object is created to pass the data into factory( ), which in turn produces some kind of Trash object on the heap and returns the reference thats added to the ArrayList bin. Of course, if you change the quantity and type of argument, this statement will still need to be modied, but that can be eliminated if the creation of the Messenger object is automated. For example, an ArrayList of arguments can be passed into the constructor of a Messenger object (or directly into a factory( ) call, for that matter). This requires that the arguments be parsed and checked at run time, but it does provide the greatest exibility. You can see from this code what vector of change problem the factory is responsible for solving: if you add new types to the system (the change), the only code that must be modied is within the factory, so the factory isolates the effect of that change.
248
249
# PatternRefactoring/trash/Trash.py # Base class for Trash recycling examples. class Trash: def __init__(self, wt): self.weight = wt def getValue(self): pass def getWeight(self): return weight # Sums the value of Trash given an # Iterator to any container of Trash: def sumValue(self, it): val = 0.0f while(it.hasNext()): # One kind of RTTI: # A dynamically-checked cast Trash t = (Trash)it.next() val += t.getWeight() * t.getValue() print ( "weight of " + # Using RTTI to get type # information about the class: t.getClass().getName() + " = " + t.getWeight()) print("Total value = " + val) # Remainder of class provides # support for prototyping: trashTypes = ArrayList() def factory(self, info): for i in trashTypes: # Somehow determine the type # to create, and create one: tc = trashTypes.get(i) if (tc.getName().index(info.id) != -1): try: # Get the dynamic constructor method # that takes a double argument: ctor = tc.getConstructor(type(double)) # Call the constructor # to create a object: return (Trash)ctor.newInstance()
250
except ex: ex.printStackTrace(System.err) raise "Cannot Create Trash" # Class was not in the list. Try to load it, # but it must be in your class path! try: print("Loading " + info.id) trashTypes.add(Class.forName(info.id)) except e: e.printStackTrace(System.err) raise "Prototype not found" # Loaded successfully. # Recursive call should work: return factory(info) class Messenger: def __init__(self, name, val): self.id = name self.data = val The basic **Trash** class and **sumValue( )** remain as before. The rest of the class supports the prototyping pattern. You first see two inner classes (which are made **static**, so they are inner classes only for code organization purposes) describing exceptions that can occur. This is followed by an **ArrayList** called **trashTypes**, which is used to hold the **Class** references.
In Trash.factory( ), the String inside the Messenger object id (a different version of the Messenger class than that of the prior discussion) contains the type name of the Trash to be created; this String is compared to the Class names in the list. If theres a match, then thats the object to create. Of course, there are many ways to determine what object you want to make. This one is used so that information read in from a le can be turned into objects. Once youve discovered which kind of Trash to create, then the reection methods come into play. The getConstructor( ) method takes an argument thats an array of Class references. This array represents the arguments, in their proper order, for the constructor that youre looking for. Here, the array is dynamically created using the Java 1.1 array-creation syntax: 37.3. A Pattern for Prototyping Creation 251
Class[]:double.class
This code assumes that every Trash type has a constructor that takes a double (and notice that double.class is distinct from Double.class). Its also possible, for a more exible solution, to call getConstructors( ), which returns an array of the possible constructors. What comes back from getConstructor( ) is a reference to a Constructor object (part of java.lang.reect). You call the constructor dynamically with the method newInstance( ), which takes an array of Object containing the actual arguments. This array is again created using the Java 1.1 syntax:
Object[]{Double(Messenger.data)
In this case, however, the double must be placed inside a wrapper class so that it can be part of this array of objects. The process of calling newInstance( ) extracts the double, but you can see it is a bit confusing-an argument might be a double or a Double, but when you make the call you must always pass in a Double. Fortunately, this issue exists only for the primitive types. Once you understand how to do it, the process of creating a new object given only a Class reference is remarkably simple. Reection also allows you to call methods in this same dynamic fashion. Of course, the appropriate Class reference might not be in the trashTypes list. In this case, the return in the inner loop is never executed and youll drop out at the end. Here, the program tries to rectify the situation by loading the Class object dynamically and adding it to the trashTypes list. If it still cant be found something is really wrong, but if the load is successful then the factory method is called recursively to try again. As youll see, the beauty of this design is that this code doesnt need to be changed, regardless of the different situations it will be used in (assuming that all Trash subclasses contain a constructor that takes a single double argument).
Java reection handles everything else. Here are the different types of Trash, each in their own le but part of the Trash package (again, to facilitate reuse within the chapter):
# PatternRefactoring/trash/Aluminum.py # The Aluminum class with prototyping. class Aluminum(Trash): val = 1.67f def __init__(self, wt): Trash.__init__(wt) def getValue(self): return val def setValue(self, newVal): self.val = newVal::
# PatternRefactoring/trash/Paper.py # The Paper class with prototyping. class Paper(Trash): val = 0.10f def __init__(self, wt): Trash.__init__(wt) def getValue(self): return self.val def setValue(self, newVal): self.val = newVal::
# PatternRefactoring/trash/Glass.py # The Glass class with prototyping. class Glass(Trash): val = 0.23f def __init__(self, wt): Trash.__init__(wt) def getValue(self): return self.val def setValue(self, newVal): self.val = newVal
253
class Cardboard(Trash): val = 0.23f def __init__(self, wt): Trash.__init__(wt) def getValue(self): return self.val def setValue(self, newVal): self.val = newVal
You can see that, other than the constructor, theres nothing special about any of these classes.
254
patternRefactoring.trash.Glass:54 patternRefactoring.trash.Aluminum:36 patternRefactoring.trash.Aluminum:93 patternRefactoring.trash.Glass:93 patternRefactoring.trash.Paper:80 patternRefactoring.trash.Glass:36 patternRefactoring.trash.Glass:12 patternRefactoring.trash.Glass:60 patternRefactoring.trash.Paper:66 patternRefactoring.trash.Aluminum:36 patternRefactoring.trash.Cardboard:22
Note that the class path must be included when giving the class names, otherwise the class will not be found. This le is read using the previously-dened StringList tool, and each line is picked aparat using the String method indexOf( ) to produce the index of the :. This is rst used with the String method substring( ) to extract the name of the trash type, and next to get the weight that is turned into a double with the static Double.valueOf( ) method. The trim( ) method removes white space at both ends of a string. The Trash parser is placed in a separate le since it will be reused throughout this chapter:
# PatternRefactoring/trash/ParseTrash.py # Parse file contents into Trash objects, # placing each into a Fillable holder. class ParseTrash: def fillBin(String filename, Fillable bin): for line in open(filename).readlines(): String type = line.substring(0, line.index(:)).strip() weight = Double.valueOf( line.substring(line.index(:) + 1) .strip()).doubleValue() bin.addTrash( Trash.factory( Trash.Messenger(type, weight)))
255
# Special case to handle Collection: def fillBin(String filename, Bin): fillBin(filename, FillableCollection(bin))
In RecycleA.py, an ArrayList was used to hold the Trash objects. However, other types of containers can be used as well. To allow for this, the rst version of llBin( ) takes a reference to a Fillable, which is simply an interface that supports a method called addTrash( ):
# PatternRefactoring/trash/Fillable.py # Any object that can be filled with Trash. class Fillable: def addTrash(self, Trash t)
Anything that supports this interface can be used with llBin. Of course, Collection doesnt implement Fillable, so it wont work. Since Collection is used in most of the examples, it makes sense to add a second overloaded llBin( ) method that takes a Collection. Any Collection can then be used as a Fillable object using an adapter class:
# PatternRefactoring/trash/FillableCollection.py # Adapter that makes a Collection Fillable. class FillableCollection(Fillable): def __init__(self, cc): self.c = cc def addTrash(self, t): self.c.add(t)
You can see that the only job of this class is to connect Fillables addTrash( ) method to Collections add( ). With this class in hand, the overloaded llBin( ) method can be used with a Collection in ParseTrash.py:
def fillBin(filename, bin): fillBin(filename, FillableCollection(bin))
256
This approach works for any container class thats used frequently. Alternatively, the container class can provide its own adapter that implements Fillable. (Youll see this later, in DynaTrash.py.)
257
All of the Trash objects, as well as the ParseTrash and support classes, are now part of the package patternRefactoring.trash, so they are simply imported. The process of opening the data le containing Trash descriptions and the parsing of that le have been wrapped into the static method ParseTrash.llBin( ), so now its no longer a part of our design focus. You will see that throughout the rest of the chapter, no matter what new classes are added, ParseTrash.llBin( ) will continue to work without change, which indicates a good design. In terms of object creation, this design does indeed severely localize the changes you need to make to add a new type to the system. However, theres a signicant problem in the use of RTTI that shows up clearly here. The program seems to run ne, and yet it never detects any cardboard, even though there is cardboard in the list! This happens because of the use of RTTI, which looks for only the types that you tell it to look for. The clue that RTTI is being misused is that every type in the system is being tested, rather than a single type or subset of types. As you will see later, there are ways to use polymorphism instead when youre testing for every type. But if you use RTTI a lot in this fashion, and you add a new type to your system, you can easily forget to make the necessary changes in your program and produce a difcult-to- nd bug. So its worth trying to eliminate RTTI in this case, not just for aesthetic reasons-it produces more maintainable code.
258
The TrashSorter object initialization must now be changed whenever a new type of Trash is added to the model. You could imagine that the TrashSorter class might look something like this:
class TrashSorter(ArrayList): def sort(self, Trash t): /* ... */
That is, TrashSorter is an ArrayList of references to ArrayLists of Trash references, and with add( ) you can install another one, like so:
TrashSorter ts = TrashSorter() ts.add(ArrayList())
Now, however, sort( ) becomes a problem. How does the statically-coded method deal with the fact that a new type has been added? To solve this, the type information must be removed from sort( ) so that all it needs to do is call a generic method that takes care of the details of type. This, of course, is another way to describe a dynamically-bound method. So sort( ) will simply move through the sequence and call a dynamically-bound method for each ArrayList. Since the job of this method is to grab the pieces of trash it is interested in, its called grab(Trash). The structure now looks like:
259
TrashSorter needs to call each grab( ) method and get a different result depending on what type of Trash the current ArrayList is holding. That is, each ArrayList must be aware of the type it holds. The classic approach to this problem is to create a base Trash bin class and inherit a new derived class for each different type you want to hold. If Java had a parameterized type mechanism that would probably be the most straightforward approach. But rather than hand-coding all the classes that such a mechanism should be building for us, further observation can produce a better approach. A basic OOP design principle is Use data members for variation in state, use polymorphism for variation in behavior. Your rst thought might be that the grab( ) method certainly behaves differently for an ArrayList that holds Paper than for one that holds Glass. But what it does is strictly dependent on the type, and nothing else. This could be interpreted as a different state, and since Java has a class to represent type (Class) this can be used to determine the type of Trash a particular Tbin will hold. The constructor for this Tbin requires that you hand it the Class of your choice. This tells the ArrayList what type it is supposed to hold. Then the grab( ) method uses Class BinType and RTTI to see if the Trash object youve handed it matches the type its supposed to grab. Here is the new version of the program:
260
# PatternRefactoring/recycleb/RecycleB.py # Containers that grab objects of interest. # A container that admits only the right type # of Trash (established in the constructor): class Tbin: def __init__(self, binType): self.list = ArrayList() self.type = binType def grab(self, t): # Comparing class types: if(t.getClass().equals(self.type)): self.list.add(t) return True # Object grabbed return False # Object not grabbed def iterator(self): return self.list.iterator() class TbinList(ArrayList): def sort(self, Trash t): Iterator e = iterator() # Iterate over self while(e.hasNext()) if(((Tbin)e.next()).grab(t)) return # Need a Tbin for this type: add(Tbin(t.getClass())) sort(t) # Recursive call class RecycleB(UnitTest): Bin = ArrayList() TbinList trashBins = TbinList() def __init__(self): ParseTrash.fillBin("../trash/Trash.dat",bin) def test(self): Iterator it = bin.iterator() while(it.hasNext()) trashBins.sort((Trash)it.next()) Iterator e = trashBins.iterator() while(e.hasNext()):
261
Tbin contains a Class reference type which establishes in the constructor what what type it should grab. The grab() method checks this type against the object you pass it. Note that in this design, grab() only accepts Trash objects so you get compile-time type checking on the base type, but you could also just accept Object and it would still work. TTbinList holds a set of Tbin references, so that sort( ) can iterate through the Tbins when its looking for a match for the Trash object youve handed it. If it doesnt nd a match, it creates a new Tbin for the type that hasnt been found, and makes a recursive call to itself - the next time around, the new bin will be found. Notice the genericity of this code: it doesnt change at all if new types are added. If the bulk of your code doesnt need changing when a new type is added (or some other change occurs) then you have an easily extensible system.
that were all of a particular type. But whenever you nd yourself picking out particular types, stop and think. The whole idea of polymorphism (dynamicallybound method calls) is to handle type-specic information for you. So why are you hunting for types? The answer is something you probably dont think about: Python performs only single dispatching. That is, if you are performing an operation on more than one object whose type is unknown, Python will invoke the dynamic binding mechanism on only one of those types. This doesnt solve the problem, so you end up detecting some types manually and effectively producing your own dynamic binding behavior. The solution is called multiple dispatching, which means setting up a conguration such that a single method call produces more than one dynamic method call and thus determines more than one type in the process. To get this effect, you need to work with more than one type hierarchy: youll need a type hierarchy for each dispatch. The following example works with two hierarchies: the existing Trash family and a hierarchy of the types of trash bins that the trash will be placed into. This second hierarchy isnt always obvious and in this case it needed to be created in order to produce multiple dispatching (in this case there will be only two dispatches, which is referred to as double dispatching).
263
The new hierarchy is TypedBin, and it contains its own method called add( ) that is also used polymorphically. But heres an additional twist: add( ) is overloaded to take arguments of the different types of trash. So an essential part of the double dispatching scheme also involves overloading. Redesigning the program produces a dilemma: its now necessary for the base class Trash to contain an addToBin( ) method. One approach is to copy all of the code and change the base class. Another approach, which you can take when you dont have control of the source code, is to put the addToBin( ) method into an interface, leave Trash alone, and inherit new specic types of Aluminum, Paper, Glass, and Cardboard. This is the approach that will be taken here. Most of the classes in this design must be public, so they are placed in their own les. Heres the interface:
# # # # PatternRefactoring/doubledispatch/TypedBinMember.py An class for adding the double dispatching method to the trash hierarchy without modifying the original hierarchy.
class TypedBinMember:
264
In each particular subtype of Aluminum, Paper, Glass, and Cardboard, the addToBin( ) method in the interface TypedBinMember is implemented, but it looks like the code is exactly the same in each case:
# PatternRefactoring/doubledispatch/DDAluminum.py # Aluminum for double dispatching. class DDAluminum(Aluminum, TypedBinMember): def __init__(self, wt): Aluminum.__init__(wt) def addToBin(self, TypedBin[] tb): for(int i = 0 i < tb.length i++) if(tb[i].add(self)): return True return False::
# PatternRefactoring/doubledispatch/DDPaper.py # Paper for double dispatching. class DDPaper(Paper, TypedBinMember): def __init__(self, wt): Paper.__init__(wt) def addToBin(self, TypedBin[] tb): for(int i = 0 i < tb.length i++) if(tb[i].add(self)) return True return False::
# PatternRefactoring/doubledispatch/DDGlass.py # Glass for double dispatching. class DDGlass(Glass, TypedBinMember): def __init__(self, wt): Glass.__init__(wt) def addToBin(self, TypedBin[] tb): for(int i = 0 i < tb.length i++) if(tb[i].add(self)) return True return False::
265
# PatternRefactoring/doubledispatch/DDCardboard.py # Cardboard for double dispatching. class DDCardboard(Cardboard, TypedBinMember): def __init__(self, wt): Cardboard.__init__(wt) def addToBin(self, TypedBin[] tb): for(int i = 0 i < tb.length i++) if(tb[i].add(self)) return True return False
The code in each addToBin( ) calls add( ) for each TypedBin object in the array. But notice the argument: this. The type of this is different for each subclass of Trash, so the code is different. (Although this code will benet if a parameterized type mechanism is ever added to Java.) So this is the rst part of the double dispatch, because once youre inside this method you know youre Aluminum, or Paper, etc. During the call to add( ), this information is passed via the type of this. The compiler resolves the call to the proper overloaded version of add( ). But since tb[i] produces a reference to the base type TypedBin, this call will end up calling a different method depending on the type of TypedBin thats currently selected. That is the second dispatch. Heres the base class for TypedBin:
# PatternRefactoring/doubledispatch/TypedBin.py # A container for the second dispatch. class TypedBin: c = ArrayList() def addIt(self, Trash t): c.add(t) return True def iterator(self): return c.iterator() def add(self, DDAluminum a): return False
266
def add(self, DDPaper a): return False def add(self, DDGlass a): return False def add(self, DDCardboard a): return False
You can see that the overloaded add( ) methods all return false. If the method is not overloaded in a derived class, it will continue to return false, and the caller (addToBin( ), in this case) will assume that the current Trash object has not been added successfully to a container, and continue searching for the right container. In each of the subclasses of TypedBin, only one overloaded method is overridden, according to the type of bin thats being created. For example, CardboardBin overrides add(DDCardboard). The overridden method adds the trash object to its container and returns true, while all the rest of the add( ) methods in CardboardBin continue to return false, since they havent been overridden. This is another case in which a parameterized type mechanism in Java would allow automatic generation of code. (With C++ templates, you wouldnt have to explicitly write the subclasses or place the addToBin( ) method in Trash.) Since for this example the trash types have been customized and placed in a different directory, youll need a different trash data le to make it work. Heres a possible DDTrash.dat:
# PatternRefactoring/doubledispatch/DDTrash.dat DDGlass:54 DDPaper:22 DDPaper:11 DDGlass:17 DDAluminum:89 DDPaper:88 DDAluminum:76 DDCardboard:96 DDAluminum:25 DDAluminum:34 DDGlass:11 DDGlass:68
267
DDGlass:43 DDAluminum:27 DDCardboard:44 DDAluminum:18 DDPaper:91 DDGlass:63 DDGlass:50 DDGlass:80 DDAluminum:81 DDCardboard:12 DDGlass:12 DDGlass:54 DDAluminum:36 DDAluminum:93 DDGlass:93 DDPaper:80 DDGlass:36 DDGlass:12 DDGlass:60 DDPaper:66 DDAluminum:36 DDCardboard:22
268
class CardboardBin(TypedBin): def add(self, DDCardboard a): return addIt(a) class TrashBinSet: binSet = [ AluminumBin(), PaperBin(), GlassBin(), CardboardBin() ] def sortIntoBins(self, bin): Iterator e = bin.iterator() while(e.hasNext()): TypedBinMember t = (TypedBinMember)e.next() if(!t.addToBin(binSet)) System.err.println("Couldnt add " + t) def binSet(): return binSet class DoubleDispatch(UnitTest): Bin = ArrayList() TrashBinSet bins = TrashBinSet() def __init__(self): # ParseTrash still works, without changes: ParseTrash.fillBin("DDTrash.dat", bin) def test(self): # Sort from the master bin into # the individually-typed bins: bins.sortIntoBins(bin) TypedBin[] tb = bins.binSet() # Perform sumValue for each bin... for(int i = 0 i < tb.length i++) Trash.sumValue(tb[i].c.iterator()) # ... and for the master bin Trash.sumValue(bin.iterator()) def main(self, String args[]):
269
DoubleDispatch().test()
TrashBinSet encapsulates all of the different types of TypedBins, along with the sortIntoBins( ) method, which is where all the double dispatching takes place. You can see that once the structure is set up, sorting into the various TypedBins is remarkably easy. In addition, the efciency of two dynamic method calls is probably better than any other way you could sort. Notice the ease of use of this system in main( ), as well as the complete independence of any specic type information within main( ). All other methods that talk only to the Trash base-class interface will be equally invulnerable to changes in Trash types. The changes necessary to add a new type are relatively isolated: you modify TypedBin, inherit the new type of Trash with its addToBin( ) method, then inherit a new TypedBin (this is really just a copy and simple edit), and nally add a new type into the aggregate initialization for TrashBinSet.
the visitor, then call the visitors dynamically-bound method. It looks like this:
271
272
uses double dispatching to cause two polymorphic method calls: the rst one to select Aluminums version of accept( ), and the second one within accept( ) when the specic version of visit( ) is called dynamically using the base-class Visitor reference v. This conguration means that new functionality can be added to the system in the form of new subclasses of Visitor. The Trash hierarchy doesnt need to be touched. This is the prime benet of the visitor pattern: you can add new polymorphic functionality to a class hierarchy without touching that hierarchy (once the accept( ) methods have been installed). Note that the benet is helpful here but not exactly what we started out to accomplish, so at rst blush you might decide that this isnt the desired solution. But look at one thing thats been accomplished: the visitor solution avoids sorting from the master Trash sequence into individual typed sequences. Thus, you can leave everything in the single master sequence and simply pass through that sequence using the appropriate visitor to accomplish the goal. Although this behavior seems to be a side effect of visitor, it does give us what we want (avoiding RTTI). The double dispatching in the visitor pattern takes care of determining both the type of Trash and the type of Visitor. In the following example, there are two implementations of Visitor: PriceVisitor to both determine and sum the price, and WeightVisitor to keep track of the weights. You can see all of this implemented in the new, improved version of the recycling program. As with DoubleDispatch.py, the Trash class is left alone and a new interface is created to add the accept( ) method:
# # # # PatternRefactoring/trashvisitor/Visitable.py An class to add visitor functionality to the Trash hierarchy without modifying the base class.
class Visitable:
273
Since theres nothing concrete in the Visitor base class, it can be created as an interface:
# PatternRefactoring/trashvisitor/Visitor.py # The base class for visitors. class Visitor: def visit(self, def visit(self, def visit(self, def visit(self,
However, we seem to be encountering an explosion of interfaces: basic Trash, special versions for double dispatching, and now more special versions for visitor. Of course, this explosion of interfaces is arbitrary-one could simply put the additional methods in the Trash class. If we ignore that we can instead see an opportunity to use the Decorator pattern: it seems like it should be possible to create a Decorator that can be wrapped around an ordinary Trash object and will
274
produce the same interface as Trash and add the extra accept( ) method. In fact, its a perfect example of the value of Decorator. The double dispatch creates a problem, however. Since it relies on overloading of both accept( ) and visit( ), it would seem to require specialized code for each different version of the accept( ) method. With C++ templates, this would be fairly easy to accomplish (since templates automatically generate typespecialized code) but Python has no such mechanism-at least it does not appear to. However, reection allows you to determine type information at run time, and it turns out to solve many problems that would seem to require templates (albeit not as simply). Heres the decorator that does the trick 1 :
# PatternRefactoring/trashvisitor/VisitableDecorator.py # A decorator that adapts the generic Trash # classes to the visitor pattern. class VisitableDecorator(Trash, Visitable): def __init__(self, t): self.delegate = t try: self.dispatch = Visitor.class.getMethod ( "visit", Class[]: t.getClass() ) except ex: ex.printStackTrace() def getValue(self): return delegate.getValue() def getWeight(self): return delegate.getWeight() def accept(self, Visitor v): self.dispatch.invoke(v, delegate)
[[ Description of Reection use ]] The only other tool we need is a new type of Fillable adapter that automatically decorates the objects as they are being created from the original Trash.dat le. But this might as well be a decorator itself, decorating any kind of Fillable:
1
Addison-Wesley, 1999.
275
# # # #
PatternRefactoring/trashvisitor/FillableVisitor.py Adapter Decorator that adds the visitable decorator as the Trash objects are being created.
class FillableVisitor(Fillable): def __init__(self, ff): self.f = ff def addTrash(self, t): self.f.addTrash(VisitableDecorator(t))
Now you can wrap it around any kind of existing Fillable, or any new ones that havent yet been created. The rest of the program creates specic Visitor types and sends them through a single list of Trash objects:
# PatternRefactoring/trashvisitor/TrashVisitor.py # The "visitor" pattern with VisitableDecorators. # Specific group of algorithms packaged # in each implementation of Visitor: class PriceVisitor(Visitor): alSum = 0.0 # Aluminum pSum = 0.0 # Paper gSum = 0.0 # Glass cSum = 0.0 # Cardboard def visit(self, al): v = al.getWeight() * al.getValue() print("value of Aluminum= " + v) alSum += v def visit(self, p): v = p.getWeight() * p.getValue() print("value of Paper= " + v) pSum += v def visit(self, g): v = g.getWeight() * g.getValue() print("value of Glass= " + v) gSum += v
276
def visit(self, c): v = c.getWeight() * c.getValue() print("value of Cardboard = " + v) cSum += v def total(self): print ( "Total Aluminum: $" + alSum + "\n Total Paper: $" + pSum + "\nTotal Glass: $" + gSum + "\nTotal Cardboard: $" + cSum + "\nTotal: $" + (alSum + pSum + gSum + cSum)) class WeightVisitor(Visitor): alSum = 0.0 # Aluminum pSum = 0.0 # Paper gSum = 0.0 # Glass cSum = 0.0 # Cardboard def visit(self, Aluminum al): alSum += al.getWeight() print ("weight of Aluminum = " + al.getWeight()) def visit(self, Paper p): pSum += p.getWeight() print ("weight of Paper = " + p.getWeight()) def visit(self, Glass g): gSum += g.getWeight() print ("weight of Glass = " + g.getWeight()) def visit(self, Cardboard c): cSum += c.getWeight() print ("weight of Cardboard = " + c.getWeight()) def total(self): print (
277
"Total weight Aluminum: " + alSum + "\nTotal weight Paper: " + pSum + "\nTotal weight Glass: " + gSum + "\nTotal weight Cardboard: " + cSum + "\nTotal weight: " + (alSum + pSum + gSum + cSum)) class TrashVisitor(UnitTest): Bin = ArrayList() PriceVisitor pv = PriceVisitor() WeightVisitor wv = WeightVisitor() def __init__(self): ParseTrash.fillBin("../trash/Trash.dat", FillableVisitor( FillableCollection(bin))) def test(self): Iterator it = bin.iterator() while(it.hasNext()): Visitable v = (Visitable)it.next() v.accept(pv) v.accept(wv) pv.total() wv.total() def main(self, String args[]): TrashVisitor().test()
In **Test( )**, note how visitability is added by simply creating a different kind of bin using the decorator. Also notice that the **FillableCollection** adapter has the appearance of being used as a decorator (for **ArrayList**) i this situation. However, it completely changes the interface of the **ArrayList**, whereas the definition of *Decorator* is that the interface of the decorated class must still be there after decoration.
Note that the shape of the client code (shown in the Test class) has changed again, from the original approaches to the problem. Now theres only a single Trash bin. The two Visitor objects are accepted into every element in the sequence, and they perform their operations. The visitors keep their own internal data to tally the total weights and prices. 278 Chapter 37. Pattern Refactoring
Finally, theres no run time type identication other than the inevitable cast to Trash when pulling things out of the sequence. This, too, could be eliminated with the implementation of parameterized types in Java. One way you can distinguish this solution from the double dispatching solution described previously is to note that, in the double dispatching solution, only one of the overloaded methods, add( ), was overridden when each subclass was created, while here each one of the overloaded visit( ) methods is overridden in every subclass of Visitor.
279
miss any you wont get help from the compiler. However, RTTI doesnt automatically create non-extensible code. Lets revisit the trash recycler once more. This time, a new tool will be introduced, which I call a TypeMap. It contains a HashMap that holds ArrayLists, but the interface is simple: you can add( ) a new object, and you can get( ) an ArrayList containing all the objects of a particular type. The keys for the contained HashMap are the types in the associated ArrayList. The beauty of this design (suggested by Larry OBrien) is that the TypeMap dynamically adds a new pair whenever it encounters a new type, so whenever you add a new type to the system (even if you add the new type at run time), it adapts. Our example will again build on the structure of the Trash types in package patternRefactoring.Trash (and the Trash.dat le used there can be used here without change):
# # # # # PatternRefactoring/dynatrash/DynaTrash.py Using a Map of Lists and RTTI to automatically sort trash into ArrayLists. This solution, despite the use of RTTI, is extensible.
# Generic TypeMap works in any situation: class TypeMap: t = HashMap() def add(self, Object o): Class type = o.getClass() if(self.t.has_key(type)) self.t.get(type).add(o) else: List v = ArrayList() v.add(o) t.put(type,v) def get(self, Class type): return (List)t.get(type) def keys(self): return t.keySet().iterator() # Adapter class to allow callbacks # from ParseTrash.fillBin():
280
class TypeMapAdapter(Fillable): TypeMap map def __init__(self, TypeMap tm): map = tm def addTrash(self, Trash t): map.add(t) class DynaTrash(UnitTest): TypeMap bin = TypeMap() def __init__(self): ParseTrash.fillBin("../trash/Trash.dat", TypeMapAdapter(bin)) def test(self): Iterator keys = bin.keys() while(keys.hasNext()) Trash.sumValue( bin.get((Class)keys.next()).iterator()) def main(self, String args[]): DynaTrash().test()
Although powerful, the denition for TypeMap is simple. It contains a HashMap, and the add( ) method does most of the work. When you add( ) a new object, the reference for the Class object for that type is extracted. This is used as a key to determine whether an ArrayList holding objects of that type is already present in the HashMap. If so, that ArrayList is extracted and the object is added to the ArrayList. If not, the Class object and a new ArrayList are added as a key-value pair. You can get an Iterator of all the Class objects from keys( ), and use each Class object to fetch the corresponding ArrayList with get( ). And thats all there is to it. The ller( ) method is interesting because it takes advantage of the design of ParseTrash.llBin( ), which doesnt just try to ll an ArrayList but instead anything that implements the Fillable interface with its addTrash( ) method. All ller( ) needs to do is to return a reference to an interface that implements Fillable, and then this reference can be used as an argument to llBin( ) like this: ParseTrash.llBin(Trash.dat, bin.ller()) To produce this reference, an anonymous inner class (described in Thinking in Java) is used. You never need a named class to implement Fillable, you just need a 37.7. RTTI Considered Harmful? 281
reference to an object of that class, thus this is an appropriate use of anonymous inner classes. An interesting thing about this design is that even though it wasnt created to handle the sorting, llBin( ) is performing a sort every time it inserts a Trash object into bin. Much of class DynaTrash should be familiar from the previous examples. This time, instead of placing the new Trash objects into a bin of type ArrayList, the bin is of type TypeMap, so when the trash is thrown into bin its immediately sorted by TypeMaps internal sorting mechanism. Stepping through the TypeMap and operating on each individual ArrayList becomes a simple matter. As you can see, adding a new type to the system wont affect this code at all, and the code in TypeMap is completely independent. This is certainly the smallest solution to the problem, and arguably the most elegant as well. It does rely heavily on RTTI, but notice that each key-value pair in the HashMap is looking for only one type. In addition, theres no way you can forget to add the proper code to this system when you add a new type, since there isnt any code you need to add.
37.8 Summary
Coming up with a design such as TrashVisitor.py that contains a larger amount of code than the earlier designs can seem at rst to be counterproductive. It pays to notice what youre trying to accomplish with various designs. Design patterns in general strive to separate the things that change from the things that stay the same. The things that change can refer to many different kinds of changes. Perhaps the change occurs because the program is placed into a new environment or because something in the current environment changes (this could be: The user wants to add a new shape to the diagram currently on the screen). Or, as in this case, the change could be the evolution of the code body. While previous versions of the trash sorting example emphasized the addition of new types of Trash to the system, TrashVisitor.py allows you to easily add new functionality without disturbing the Trash hierarchy. Theres more code in TrashVisitor.py, but adding new functionality to Visitor is cheap. If this is something that happens a lot, then its worth the extra effort and code to make it happen more easily. The discovery of the vector of change is no trivial matter; its not something
282
that an analyst can usually detect before the program sees its initial design. The necessary information will probably not appear until later phases in the project: sometimes only at the design or implementation phases do you discover a deeper or more subtle need in your system. In the case of adding new types (which was the focus of most of the recycle examples) you might realize that you need a particular inheritance hierarchy only when you are in the maintenance phase and you begin extending the system! One of the most important things that youll learn by studying design patterns seems to be an about-face from what has been promoted so far in this book. That is: OOP is all about polymorphism. This statement can produce the two-yearold with a hammer syndrome (everything looks like a nail). Put another way, its hard enough to get polymorphism, and once you do, you try to cast all your designs into that one particular mold. What design patterns say is that OOP isnt just about polymorphism. Its about separating the things that change from the things that stay the same. Polymorphism is an especially important way to do this, and it turns out to be helpful if the programming language directly supports polymorphism (so you dont have to wire it in yourself, which would tend to make it prohibitively expensive). But design patterns in general show other ways to accomplish the basic goal, and once your eyes have been opened to this you will begin to search for more creative designs. Since the Design Patterns book came out and made such an impact, people have been searching for other patterns. You can expect to see more of these appear as time goes on. Here are some sites recommended by Jim Coplien, of C++ fame (http://www.bell-labs.com/~cope), who is one of the main proponents of the patterns movement: http://st-www.cs.uiuc.edu/users/patterns http://c2.com/cgi/wiki http://c2.com/ppr http://www.bell-labs.com/people/cope/Patterns/Process/index.html http://www.bell-labs.com/cgi-user/OrgPatterns/OrgPatterns http://stwww.cs.uiuc.edu/cgi-bin/wikic/wikic http://www.cs.wustl.edu/~schmidt/patterns.html http://www.espinc.com/patterns/overview.html Also note there has been a yearly conference on design patterns, called PLOP, that produces a published proceedings, the third of which came out in late 1997 (all published by Addison-Wesley).
37.8. Summary
283
37.9 Exercises
1. Add a class Plastic to TrashVisitor.py. 2. Add a class Plastic to DynaTrash.py. 3. Create a decorator like VisitableDecorator, but for the multiple dispatching example, along with an adapter decorator class like the one created for VisitableDecorator. Build the rest of the example and show that it works. This was a solution created by Jaroslav Tulach in a design patterns class that I gave in Prague.
284
CHAPTER
THIRTYEIGHT
PROJECTS
Note: This chapter has not had any signicant translation yet. A number of more challenging projects for you to solve. [[Some of these may turn into examples in the book, and so at some point might disappear from here]]
dead end, it terminates itself after reporting the results of its nal investigation to the blackboard. The goal is to completely map the maze, but you must also determine whether the end condition will be naturally found or whether the blackboard must be responsible for the decision. An example implementation by Jeremy Meyer:
# Projects/ratsAndMazes/Maze.py class Maze(Canvas): lines = [] # a line is a char array width = -1 height = -1 main(self): if (args.length < 1): print("Enter filename") System.exit(0) Maze m = Maze() m.load(args[0]) Frame f = Frame() f.setSize(m.width*20, m.height*20) f.add(m) Rat r = Rat(m, 0, 0) f.setVisible(1) def __init__(self): lines = Vector() setBackground(Color.lightGray) def isEmptyXY(x, y): if (x < 0) x += width if (y < 0) y += height # Use mod arithmetic to bring rat in line: byte[] by = (byte[])(lines.elementAt(y%height)) return by[x%width]== def setXY(x, y, byte newByte): if (x < 0) x += width
286
if (y < 0) y += height byte[] by = (byte[])(lines.elementAt(y%height)) by[x%width] = newByte repaint() def load(String filename): String currentLine = null BufferedReader br = BufferedReader( FileReader(filename)) for(currentLine = br.readLine() currentLine != null currentLine = br.readLine()) : lines.addElement(currentLine.getBytes()) if(width < 0 || currentLine.getBytes().length > width) width = currentLine.getBytes().length height = len(lines) br.close() def update(self, Graphics g): paint(g) def paint(Graphics g): canvasHeight = self.getBounds().height canvasWidth = self.getBounds().width if (height < 1 || width < 1) return # nothing to do width = ((byte[])(lines.elementAt(0))).length for (int y = 0 y < len(lines) y++): byte[] b b = (byte[])(lines.elementAt(y)) for (int x = 0 x < width x++): switch(b[x]): case : # empty part of maze g.setColor(Color.lightGray) g.fillRect( x*(canvasWidth/width), y*(canvasHeight/height), canvasWidth/width, canvasHeight/height)
287
break case *: # a wall g.setColor(Color.darkGray) g.fillRect( x*(canvasWidth/width), y*(canvasHeight/height), (canvasWidth/width)-1, (canvasHeight/height)-1) break default: # must be rat g.setColor(Color.red) g.fillOval(x*(canvasWidth/width), y*(canvasHeight/height), canvasWidth/width, canvasHeight/height) break::
# Projects/ratsAndMazes/Rat.py class Rat: ratCount = 0 prison = Maze() vertDir = 0 horizDir = 0 x = 0, y = 0 myRatNo = 0 def __init__(self, maze, xStart, yStart): myRatNo = ratCount++ print ("Rat no." + myRatNo + " ready to scurry.") prison = maze x = xStart y = yStart prison.setXY(x,y, (byte)R) def run(self): scurry().start() def scurry(self): # Try and maintain direction if possible. # Horizontal backward boolean ratCanMove = 1
288
while(ratCanMove): ratCanMove = 0 # South if (prison.isEmptyXY(x, y + 1)): vertDir = 1 horizDir = 0 ratCanMove = 1 # North if (prison.isEmptyXY(x, y - 1)) if (ratCanMove) Rat(prison, x, y-1) # Rat can move already, so give # this choice to the next rat. else: vertDir = -1 horizDir = 0 ratCanMove = 1 # West if (prison.isEmptyXY(x-1, y)) if (ratCanMove) Rat(prison, x-1, y) # Rat can move already, so give # this choice to the next rat. else: vertDir = 0 horizDir = -1 ratCanMove = 1 # East if (prison.isEmptyXY(x+1, y)) if (ratCanMove) Rat(prison, x+1, y) # Rat can move already, so give # this choice to the next rat. else: vertDir = 0 horizDir = 1 ratCanMove = 1 if (ratCanMove): # Move original rat. x += horizDir y += vertDir prison.setXY(x,y,(byte)R)
289
# If not then the rat will die. Thread.sleep(2000) print ("Rat no." + myRatNo + " cant move..dying..aarrgggh.")
290
CHAPTER
THIRTYNINE
291
292
INDEX
C
canonical form script command-line, 120 command-line canonical form, script, 120 comprehension generator, 73 list, 73 concurrency, 79 coroutines, 79
L
Language differences Python 3, 56 list comprehension, 73
M
messenger (data transfer object), 121 multiprocessing, 79
D
data transfer object (messenger), 121 decorator: Python decorators, 57
P
parallelism, 79 Python 3 Language differences, 56
G
generator comprehension, 73 generators, 71 GIL: Global Interpreter Lock, 79
S
script command-line canonical form, 120
T
threads, 79
I
iterators, 71 itertools, 71
293