Wednesday, January 26, 2005
Useful wxPython info
In an old blog, that I just stumbled upon, Golden spud gives some interesting information about wxPython. Perhaps it is possible to use his technique to change the language used in a GUI without having to restart the application under Windows.
Tuesday, January 11, 2005
Teaching an old Python keyword new tricks
In my last post, I provided some additional thoughts about maintaining Python's pseudocode appearance while adding static typing information. The notation I suggested also allowed for easy inclusion of pre-conditions and post-conditions. However, this came as a cost of adding where as a new keyword to Python. After giving some more thoughts to this idea I realise that a new keyword was not needed at all. This can be seen starting from the following example using the keyword where as introduced before:
01 def gcd(a, b):If we remove the line with where: and change the indentation level, we have essentially valid (today!) Python syntax, but with many (two in this case) lines starting with the keyword assert. If we allow this keyword to introduce the beginning of a bloc of statements, we could then write:
02 where:
03 assert isinstance(a, int)
04 assert isinstance(b, int)
05 '''Returns the Greatest Common Divisor,
06 implementing Euclid's algorithm.
07 Input arguments must be integers.'''
08 while a:
09 a, b = b%a, a
10 return b
01 def gcd(a, b):The idea of a keyword that allows both a bloc structure with a colon, as well as a single line expression is not new: it has been introduced in Python with the addition of list comprehensions. For example, we can have
02 assert:
03 isinstance(a, int)
04 isinstance(b, int)
05 '''Returns the Greatest Common Divisor,
06 implementing Euclid's algorithm.
07 Input arguments must be integers.'''
08 while a:
09 a, b = b%a, a
10 return b
01 for i in range(10):as well as
02 ...
03 ...
a = [i for i in range(10)]With this suggested syntax, pre-conditions can easily be added.
01 def gcd(a, b):Keeping this block structure in mind, we can add type information on return values as follows:
02 assert:
03 isinstance(a, int)
04 isinstance(b, int)
05 a != 0 and b != 0 # fake pre-condition
06 '''Returns the Greatest Common Divisor,
07 implementing Euclid's algorithm.
08 Input arguments must be integers.'''
09 while a:
10 a, b = b%a, a
11 return b
01 def gcd(a, b):as well as adding pre- and post-conditions:
02 assert:
03 isinstance(a, int)
04 isinstance(b, int)
05 return c assert:
06 isinstance(c, int)
07 '''Returns the Greatest Common Divisor,
08 implementing Euclid's algorithm.
09 Input arguments must be integers;
10 return value is an integer.'''
11 while a:
12 a, b = b%a, a
13 return b
01 def gcd(a, b):To move further towards something similar to what I suggested in my previous post, we need to identify, as Guido van Rossum had himself suggested, the statement
02 assert:
03 isinstance(a, int)
04 isinstance(b, int)
05 a != 0 and b != 0 # fake pre-condition
06 return c assert:
07 isinstance(c, int)
08 c != 0 # fake post-condition
09 '''Returns the Greatest Common Divisor,
10 implementing Euclid's algorithm.
11 Input arguments must be integers;
12 return value is an integer.'''
13 while a:
14 a, b = b%a, a
15 return b
isinstance(object, class-or-type-or-tuple)with
object: class-or-type-or-tupleDoing so allows us to rewrite the last example as
01 def gcd(a, b):which is essentially what was written in the previous post, but with the replacement of a new keyword (where) by an old one (assert). The general form would be:
02 assert:
03 a: int
04 b: int
05 a != 0 and b != 0 # fake pre-condition
06 return c assert:
07 c: int
08 c != 0 # fake post-condition
09 '''Returns the Greatest Common Divisor,
10 implementing Euclid's algorithm.
11 Input arguments must be integers;
12 return value is an integer.'''
13 while a:
14 a, b = b%a, a
15 return b
01 def foo(...):As this post is already long enough, I will not discuss the interface issue here; the ideas introduced in the previous two posts are easily translated by replacing where by assert everywhere.
02 assert:
. type information and/or
. pre-conditions
. return [...] assert:
. type information and/or
. post-conditions
. '''docstring'''
. ...body...
Sunday, January 09, 2005
Python as pseudo-code and Optional Static Typing
In my last post, I gave a series of examples intended to show how Python's pseudocode syntax could be preserved while adding optional static typing. While I felt that the syntax was self-explanatory (as pseudocode should be), I thought it might be useful to examine in more details the why's and the how's of my suggestion.
Before proceeding further, it might be argued that this is a case of premature optimization: Python does not currently have (optional) static typing so the concept should be discussed first, before implementation details and syntax can be looked at objectively. Yet, one of the strength of Python is its syntax, and many objections (NIMPY as GvR described it) to the proposed addition(s) to the language came from an unease with the suggested syntax.
1. Functions and optional typing information
I will use GvR's version of a Greatest Common Divisor finding function as an example and build from it; the complexity of the examples will increase as we go in an attempt to fully illustrate the suggested syntax. This function is defined as follows:
the function body.
The docstring is between the typing information and the actual code, allowing to better separate visually the two of them. The docstring has been updated as well to inform a potential user
reading the documention as to the particular of this function. I have given some thoughts about including the typing information within the docstring (à la doctest) in order to avoid writing about the type information twice, as in the following:
of the docstring); 3) it doesn't "scale" well with added features like pre-conditions and post-conditions.
2. Pre- and post-conditions
Building on the previous examples, here is how pre- and post-conditions might might be added within the suggested new syntax with, in this case, somewhat artificial conditions (not required by this algorithm).
The post-condition assertion
is an internal verification that the code is correct. It is irrelevant to the documentation which is one more reason to separate it from the docstring, even though such information is sometimes including in docstring. This notation, using the standard assert statement is different from what I suggested in my previous post and respects Python's current syntax.
One can easily imagine that such typing information and conditions might be shared by many functions. Rather than replicating code, it might be possible to define abstract functions, which are functions that have no body, only type information and/or conditions. For example, we could have
I realise that this might be starting to look like a return to past suggestions during the decorator debate. However, given that the optional static typing proposal is new, it should be looked at objectively, without rejecting off-hand proposals that were rejected in a different context.
3. Interfaces
Instead of dealing with functions, in oop we have classes and methods. Classes can implement interfaces, which share some similarities with what we called abstract functions above. Here is how, using the notation we have suggested, we might introduce interfaces in the language. Once again I am using examples suggested by Python's BDFL in his blog.
I suggest using a semi-colon to separate based classes from interfaces for clarity. Thus we could have:
Classes that do not derived from a base class (aka 'old-style') might still be accommodated via
Before proceeding further, it might be argued that this is a case of premature optimization: Python does not currently have (optional) static typing so the concept should be discussed first, before implementation details and syntax can be looked at objectively. Yet, one of the strength of Python is its syntax, and many objections (NIMPY as GvR described it) to the proposed addition(s) to the language came from an unease with the suggested syntax.
1. Functions and optional typing information
I will use GvR's version of a Greatest Common Divisor finding function as an example and build from it; the complexity of the examples will increase as we go in an attempt to fully illustrate the suggested syntax. This function is defined as follows:
I have included an optional docstring which, following Python's normal syntax, is indented at the same level as the function's body. The docstring is intended to be easily extractable for documentation purpose and is intended for human readers only. Suppose that it is required to provide static typing information. Using the notation I used in my previous post, here's how one might write additional syntax information.
01 def gcd(a, b):
02 '''Returns the Greatest Common Divisor,
03 implementing Euclid's algorithm.'''
04 while a:
05 a, b = b%a, a
06 return b
01 def gcd(a, b):The suggested where keyword, followed by a mandatory colon, follows the usual Python syntax of introducing a bloc of code identified by its indentation level. So we now have three structural items at the same indentation level: the static typing information, the docstring, and
02 where:
03 a: int, b: int
04 return c where:
05 c: int
06 '''Returns the Greatest Common Divisor,
07 implementing Euclid's algorithm.
08 Input arguments must be integers;
09 return value is an integer.'''
10 while a:
11 a, b = b%a, a
12 return b
the function body.
The docstring is between the typing information and the actual code, allowing to better separate visually the two of them. The docstring has been updated as well to inform a potential user
reading the documention as to the particular of this function. I have given some thoughts about including the typing information within the docstring (à la doctest) in order to avoid writing about the type information twice, as in the following:
01 def gcd(a, b):However, I find 1) that it is not easier to read; 2) that it would complicate extraction of the docstring for inclusion in a standard documentation (i.e. spaces are significant for only part
02 '''Returns the Greatest Common Divisor,
03 implementing Euclid's algorithm.
04 where:
05 a: int, b: int
06 return c where:
07 c: int'''
08 while a:
09 a, b = b%a, a
10 return b
of the docstring); 3) it doesn't "scale" well with added features like pre-conditions and post-conditions.
2. Pre- and post-conditions
Building on the previous examples, here is how pre- and post-conditions might might be added within the suggested new syntax with, in this case, somewhat artificial conditions (not required by this algorithm).
01 def gcd(a, b):
02 where:
03 a: int, b: int
04 assert a != 0 and b != 0
05 return c where:
06 c: int
07 assert c <= abs(a) and c <= abs(b)
08 '''Returns the Greatest Common Divisor,
09 implementing Euclid's algorithm.
10 Input arguments must be integers,
11 and can not be both zero;
12 return value is an integer.'''
13 while a:
14 a, b = b%a, a
15 return b
The post-condition assertion
07 assert c <= abs(a) and c <= abs(b)
is an internal verification that the code is correct. It is irrelevant to the documentation which is one more reason to separate it from the docstring, even though such information is sometimes including in docstring. This notation, using the standard assert statement is different from what I suggested in my previous post and respects Python's current syntax.
One can easily imagine that such typing information and conditions might be shared by many functions. Rather than replicating code, it might be possible to define abstract functions, which are functions that have no body, only type information and/or conditions. For example, we could have
01 def two_integer_input(i, j): # abstract function: no body
02 where:
03 i: int, j: int
04 '''Accepts only two integers as input.'''
05
06
07 def _gcd_return_info(a, b): # abstract function: no body
08 where:
09 return c where:
10 c: int
11 assert c <= abs(a) and c <= abs(b)
12
13
14 def gcd(a, b):
15 where:
16 two_integer_input(a, b)
17 assert a != 0 and b != 0
18 _gcd_return_info(a, b)
19 '''Returns the Greatest Common Divisor,
20 implementing Euclid's algorithm.
21 Input arguments must be integers,
22 and can not be both zero;
23 return value is an integer.'''
24 while a:
25 a, b = b%a, a
26 return b
I realise that this might be starting to look like a return to past suggestions during the decorator debate. However, given that the optional static typing proposal is new, it should be looked at objectively, without rejecting off-hand proposals that were rejected in a different context.
3. Interfaces
Instead of dealing with functions, in oop we have classes and methods. Classes can implement interfaces, which share some similarities with what we called abstract functions above. Here is how, using the notation we have suggested, we might introduce interfaces in the language. Once again I am using examples suggested by Python's BDFL in his blog.
01 interface I:
02 def foo(x):
03 where:
04 x: int
05 x > 0
06 return r where:
07 r: str
08
09 class C(object; I): # derived from object; implements I
10 def foo(self, x):
11 return str(x)
I suggest using a semi-colon to separate based classes from interfaces for clarity. Thus we could have:
01 interface I1(I2, I3): # derived from I2 and I3, both interfacesFor a class that does not implement interfaces, the following statements would be equivalent
02 def foo(a: t1, b: t2):
03 where:
04 a: t1, b: t2
05 return c where:
06 c: t3
07
08 class C(C1, C2; I1): # derived from C1, C2; implements I1
09 def foo(a, b):
10 return a+b
class C(object): # preferred
# and
class C(object;): # allowed
Classes that do not derived from a base class (aka 'old-style') might still be accommodated via
class C(; I1, I2)although presumably they will have disappeared from the language by the time static typing information will have been implemented.
Friday, January 07, 2005
"where" keyword and Python as pseudo-code
There has been much discussion lately about what is now a series of blogs by Guido van Rossum (see Optional Static Typing -- Stop the Flames! for the latest discussion) about optional static typing in Python and related extensions to the language. A recent post
comp.lang.python by Andrey Tatarinov about using where as a new keyword in the absence of anonymous functions gave me some ideas about how such a new keyword could be useful in rendering pythonic the addition of optional static typing. However, I do not use where in the same context as Tatarinov.
Python is often described as executable pseudo-code. Therefore, rather than explaining in words my ideas, I will let the code (examples taken from Python's BDFL blogs) speak for itself. Note that the amount of code versus type declarations is not really representative of real life in the following examples. I also deliberately did not add blank lines, which could have made this more readable.
1. Optional typing.
2. Optional typing with pre- and post- conditions.
3. Interfaces
4. More interfaces
5. Formerly anonymous functions.
6. Formerly anonymous functions with typing information
7. Other optional typing style, meant to be equivalent to above
If you "grok" the above, I have made my point. If not, feel free to comment!
comp.lang.python by Andrey Tatarinov about using where as a new keyword in the absence of anonymous functions gave me some ideas about how such a new keyword could be useful in rendering pythonic the addition of optional static typing. However, I do not use where in the same context as Tatarinov.
Python is often described as executable pseudo-code. Therefore, rather than explaining in words my ideas, I will let the code (examples taken from Python's BDFL blogs) speak for itself. Note that the amount of code versus type declarations is not really representative of real life in the following examples. I also deliberately did not add blank lines, which could have made this more readable.
1. Optional typing.
def gcd(a, b):
where:
a: int, b: int
return c where:
c: int
while a:
a, b = b%a, a
return b
2. Optional typing with pre- and post- conditions.
def gcd(a, b):
where:
a: int, b: int
a > 0, b > 0
return c where:
c: int
c > 0
c <= a and c <= b
while a:
a, b = b%a, a
return b
3. Interfaces
interface I:
def foo(x):
where:
x: int
x > 0
return r where:
r: str
class C(object; I): # derived from object; implements I
def foo(self, x):
return str(x)
4. More interfaces
interface I1(I2, I3):
def foo(a: t1, b: t2):
where:
a: t1, b: t2
return c where:
c: t3
class C(C1, C2; I1): # derived from C1, C2; implements I1
def foo(a, b):
return a+b
5. Formerly anonymous functions.
list_of_powers = [ powers(x) for x in my_list]:
def powers(x):
return (x, x*x, x*x*x)
6. Formerly anonymous functions with typing information
list_of_powers = [ powers(x) for x in my_list]:
def powers(x):
where:
x: int
return (a, b, c) where:
a: int, b: int, c: int
return (x, x*x, x*x*x)
7. Other optional typing style, meant to be equivalent to above
def type_safe_gcd(a, b):
where:
a: int, b: int
return c where:
c: int
def gcd(a, b):
where type_safe_gcd(a, b);
while a:
a, b = b%a, a
return b
If you "grok" the above, I have made my point. If not, feel free to comment!
Sunday, January 02, 2005
Avoiding spaghetti code
Note to myself:
functions/methods should either
1) change some internal attributes;
2) return a value/object
3) change some attribute of an object created by their parent class.
They should not directly change the state of some objects that calls them. I had the following situation:
X created Y.
X created Z.
A method of X called a method of Z which was then changing the state of an attribute of Y through a pointer reference created for that purpose. Ugly.... and difficult to maintain.
functions/methods should either
1) change some internal attributes;
2) return a value/object
3) change some attribute of an object created by their parent class.
They should not directly change the state of some objects that calls them. I had the following situation:
X created Y.
X created Z.
A method of X called a method of Z which was then changing the state of an attribute of Y through a pointer reference created for that purpose. Ugly.... and difficult to maintain.
wxPython problem solved
Well, I did manage (with some help) to solve my wxPython problems. I'll update this post to indicate the solution soon.
Sunday, December 26, 2004
wxPython woes
I've been trying to understand better how to use wxPython but I still have problems with doing "custom" double-buffering displays other than for simple programs. For example, the techniques used in the program below could be used as the basis for a simple pac-man like game. It works well, using the built-in buffered device contexts but I'd like to understand how to do my own buffered drawings on scrolled windows (It's "easy" to get things working with fixed-sized windows). For those interested, have a look at the onPaint() and drawImage() methods.
One of the problems that I am encountering is the incompleteness (for non-expert like me) of the available documentation on wxPython. So, I resolved to write my wxPython based programs with lots of comments in the hope that they will be useful to other people
One of the problems that I am encountering is the incompleteness (for non-expert like me) of the available documentation on wxPython. So, I resolved to write my wxPython based programs with lots of comments in the hope that they will be useful to other people
""" This wxPython-based program illustrates:
* the use of foreground and background program-generated images;
* the use of cursor keys to move an image;
* the automatic scrolling of windows;
* double-buffering of images using "built-in" bufferedDC's.
Using the arrow-keys, we move a simple image (circle) on a checkered
background inside a scrolled-window, scrolling automatically as needed to
keep the circle always visible.
"""
import wx
custom_buffer = False
class CheckeredBackground(object):
"""Checkered-like image pattern defined so that we can better
visualise the moving canvas."""
# Note: there is no real need to define this as a class; it could
# have been simply defined as a function of MyCanvas below.
# Note 2: default values are provided but not used in this program.
def __init__(self, width=600, height=480, side=40):
#-- prepare to create bitmap image
self.image = wx.EmptyBitmap(width, height)
# Before we can "draw", we need to specify what the context will be.
#-- Device context (DC) will be computer memory
offDC = wx.MemoryDC()
#-- prepare to work with the image
offDC.SelectObject(self.image)
#-- select a background colour
offDC.SetBackground(wx.Brush("WHEAT"))
#-- "paint" over the entire object with the background colour
offDC.Clear()
#-- create the pattern in "separate" memory
nb_col = width//side
nb_row = height//side
squares = []
for i in range(0, nb_col, 2):
for j in range(0, nb_row, 2):
x = i*side
y = j*side
squares.append( (x, y, side, side) )
for i in range(1, nb_col, 2):
for j in range(1, nb_row, 2):
x = i*side
y = j*side
squares.append( (x, y, side, side) )
#-- Choose square outline colour
offDC.SetPen(wx.Pen("PALE GREEN", side/8))
#-- Choose square interior (fill) colour
offDC.SetBrush(wx.Brush("LIGHT STEEL BLUE"))
#-- "draw" the squares in DC memory
offDC.DrawRectangleList(squares)
#-- release bitmap image from drawing context. This amounts,
# as I understand, to undoing SelectObject().
del offDC
class MovingCircle(object):
"""Creates a simple circle on transparent background"""
# Note: there is no real need to define this as a class.
# It is done here for later re-use in other examples.
# Note 2: default values are provided but not used in this program.
def __init__(self, radius = 10, string_colour = "RED"):
#-- prepare to create bitmap image
self.image = wx.EmptyBitmap(2*radius, 2*radius)
#-- Device context (DC) will be computer memory
offDC = wx.MemoryDC()
#-- prepare to work with the image
offDC.SelectObject(self.image)
""" Preparing to draw a shape with a transparent background.
I thought that choosing a wx.Brush with a TRANSPARENT_BRUSH
colour, and clearing the DC with it would work - but it
does not. What does work is to select an "unused" colour
for the background and set up a mask with it."""
#-- choose an "unusual" background colour ...
offDC.SetBackground(wx.Brush(wx.Colour(2,2,2), wx.SOLID))
#-- ... and set it everywhere
offDC.Clear()
#-- different colour for object
offDC.SetPen(wx.Pen(string_colour, 1)) # outline
offDC.SetBrush(wx.Brush(string_colour)) # interior
offDC.DrawCircle(radius, radius, radius)# center: (x, y) and radius.
#-- release bitmap image from drawing context to process it further
del offDC
#-- set up a mask with our "unusual" colour
mask = wx.Mask(self.image, wx.Colour(2,2,2))
self.image.SetMask(mask)
#-- only regions where colour != wx.Colour(2,2,2) survives.
class MyCanvas(wx.ScrolledWindow):
def __init__(self, parent, id = -1, size = wx.DefaultSize):
wx.ScrolledWindow.__init__(self, parent, id, (0, 0), size=size,
style=wx.SUNKEN_BORDER)
# sets dimensions so that image will be larger than window,
# and scrolling can occur.
self.maxWidth = 1200
self.maxHeight = 800
# Set the size of the total window, of which only a small part
# will be displayed; apparently SetVirtualSize needs
# a single (tuple) argument, which explains the double (( )).
self.SetVirtualSize((self.maxWidth, self.maxHeight))
# Set the scrolling rate; use same value in both horizontal and
scrollRate = 20 # vertical directions.
self.SetScrollRate(scrollRate, scrollRate)
# Create the background image
side = 40
self.background = CheckeredBackground(self.maxWidth,
self.maxHeight, side)
# Create small foreground images; normally, such an image might
# be imported from a file
self.radius = 40
self.red_circle = MovingCircle(self.radius, "RED")
# sets its position (used later)
self.circle_x = 0 # position of top left corner of enclosing box
self.circle_y = 0
# bind the key events that will be used to move the small image
self.bindEvents()
# Initialize the buffer bitmap. No real DC is needed at this point.
self.buffer = wx.EmptyBitmap(self.maxWidth, self.maxHeight)
self.drawImage()
def bindEvents(self):
# use the old style [still works!] instead of self.Bind(evt, fn)
wx.EVT_PAINT(self, self.OnPaint)
wx.EVT_CHAR(self, self.MyArrowKeys)
##-- onPaint() and drawImage() are the two methods I'd like to know how
##-- to do with custom-based buffered DCs.
def OnPaint(self, event):
if custom_buffer:
pass
# I haven't been able to figure out how to do this
else:
dc = wx.BufferedPaintDC(self, self.buffer)
def drawImage(self):
if custom_buffer:
pass
# I haven't been able to figure out how to do this.
else:
dc = wx.BufferedDC(None, self.buffer)
dc.Clear()
dc.BeginDrawing()
# First copy the background image onto the buffer
dc.DrawBitmap(self.background.image, 0, 0, True)
# Next, superimpose the foreground image
dc.DrawBitmap(self.red_circle.image, self.circle_x,
self.circle_y, True)
dc.EndDrawing()
del dc
def MoveCircle(self, x, y):
self.circle_x += x
self.circle_y += y
#-- Prevent the circle from moving out of bounds
# (of the full background image, not the visible part.)
if self.circle_x < circle_x =" 0" circle_y =" 0"> self.maxWidth - 2*self.radius:
self.circle_x = self.maxWidth - 2*self.radius
if self.circle_y > self.maxHeight - 2*self.radius:
self.circle_y = self.maxHeight - 2*self.radius
hidden = 40 # approximate space hidden under scrollbars
# determine the position of top left visible window in
# "scrollrate" units
xView, yView = self.GetViewStart()
# corresponding amount of pixel per "scroll"
xDelta, yDelta = self.GetScrollPixelsPerUnit()
# size of wisible window
width, height = self.GetSizeTuple()
#-- Determine if window needs to be scrolled so that object
# remains visible. Assume that the object fits entirely
# in the visible view.
if self.circle_x < xview =" max(0,"> xView*xDelta + width:
xView = (self.circle_x + 2*self.radius + hidden - width)/xDelta
if self.circle_y < yview =" max(0,"> yView*yDelta + height:
yView = (self.circle_y + 2*self.radius + hidden - height)/yDelta
self.Scroll(xView, yView)
self.drawImage()
self.Refresh(False)
def MyArrowKeys(self, event):
code = event.KeyCode()
if code == wx.WXK_UP:
self.MoveCircle(0, -10) # up on screen is negative y-direction
elif code == wx.WXK_LEFT:
self.MoveCircle(-10, 0)
elif code == wx.WXK_RIGHT:
self.MoveCircle(10, 0)
elif code == wx.WXK_DOWN:
self.MoveCircle(0, 10)
else:
pass # ignore all other keys pressed
class AnimationFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1, "Animation Frame", size=(400, 300),
style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE)
doodle = MyCanvas(self, -1)
#----------------------------------------------------------------------
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = AnimationFrame(None)
frame.Show(True)
app.MainLoop()
Subscribe to:
Posts (Atom)