Efectos Especiales
Ramblings, rants, musings, ideas and observations. Topics include (but are not limited to): programming (especially Python), books, games (especially CCGs and board games), astrology, design, writing, painting, etc.#649 (old comments) Wax open issues #3: importing
Currently, the preferred way of importing Wax is from wax import *
. This works rather well; it allows for nice short names (Button
rather than wax.Button
), and doesn't pollute the importing namespace too much, because the number of objects exported is limited.
However, import wax
would be the recommended importing style. This way has some benefits (no namespace pollution, possibility for "lazy" importing) and drawbacks (having to write wax.
before everything).
(The lazy importing is possible with some hackery. A Wax user submitted a patch a while ago, and I'm still considering whether to use it or not. In this scheme, we won't actually import anything until it's explicitly requested... like, when we first refer to wax.Button
in our code, then it's imported, not earlier. On-demand importing. The question is, how useful would this be, knowing that the whole wx universe already has been imported?)
Right now, it's possible to use either way of importing. With lazy importing, only import wax
will be possible. In the end, I think it's mostly a matter of style. I will eventuallty have to make a decision, though, and use the preferred style in examples, documentation, etc.
#648 (old comments) A non-Wax issue: declarative GUI design
In a current thread on comp.lang.python, Anton Muhin writes:
[...] I mostly prefer declarative approach for the problems like that. Therefore, wouldn't it be interesting to describe the structure with class and metaclass mechanism, like:
class MainFrame(FrameDescription): class b1(ButtonDescription): size = (40, 40) text = "b1"
Many people seem to like this style of GUI building. For another example using wxPython, see here.
Personally, I don't like this style, for at least two reasons.
1. Classes and class attributes are used for "unnatural" purposes. That is not a deadly sin -- this is a programming language, and people discover innovative uses of existing constructs all the time. But regular Python constructs are used to express something entirely different. Inner classes suddenly mean "a control within a parent control". Class attributes are used to set the attributes *of an instance*. b1
is a class, but it actually represents an instance. ...On top of that, you'll need a lot of magic to make it work.
2. I think this way of GUI building is too static. It's like you define a whole frame/form/whatever, then create it in one fell swoop. My view is a bit more dynamic... you create an object, add it to a parent, maybe tinker with it before or after adding, etc.
I am not a GUI design guru, far from it... all I did was build a layer around wxPython. But the "declarative" style seems to be more at home in XML or something than in Python.
This style makes more sense, IMHO, in other environments... it seems like a good fit in SQLObject, for example.
Just for the record: Wax will never support this style. I am interested in a discussion about its pros and cons, but I am not going to change Wax to incorporate it.
#647 (old comments) Wax open issues #2: constructor/layout redundancy
There is currently some redundancy when creating Wax objects and adding them to a container. Consider:
b = Button(parent, "Click me") parent.AddComponent(b, expand='both')
This does seem redundant. First you tell the button what its parent is (the container), then you tell the container to add the button. There's a reason for this... the first line creates a Button
instance, and wxPython needs a parent control. The second line
I suppose it would be possible to delay construction of the control until it's added to the parent, like some people suggested. This approach has a few problems. By this reasoning, the parent may not really exist yet either, so we're back to a form of the "declarative" style (see the next post). Also, it makes it impossible to use the control before it is added. Like I wrote in a newsgroup post the other day, it may make a difference whether you access the control before or after adding it. For example:
from wax import * class MainFrame(Frame): def Body(self): b1 = Button(self, text="b1") b1.Size = (40, 40) self.AddComponent(b1) b2 = Button(self, text="b2") self.AddComponent(b2) b2.Size = (40, 40) self.Pack() app = Application(MainFrame) app.Run()
In future versions of Wax, it may be possible to add layout parameters in the constructor. Something like
b = Button(parent, "Click me", event=self.do_something, layout = {'expand': 'both', 'border': 2})
The values in the layout dict will vary depending on the container. A GridContainer
expects a column and row, for example.
It might also be possible to do the layout through the control itself:
b = Button(parent, "Click me") b.AddToParent(expand='both', border=2)
The parameters passed to AddToParent
(or whatever it will be called) would be passed to the parent's AddComponent
method.
I think both the layout
dict and the AddToParent
method are reasonable solutions; I don't like the "declarative" one. I'm not sure if this is such a big deal that it warrants extra constructs... after all, we're only saving one parameter. Anyway, as always, comments are welcome.
#646 (old comments) He wanted his code blocks
See the previous post. Thanks to everyone who replied.
It turns out that there are several ways to do this thing at the class level. One is with a descriptor:
class with_transaction(object): def __init__(self, fn): self.fn = fn def __get__(self, inst, cls): def _with_transaction(*args, **kwargs): print "Doing a transaction with", `inst.db` self.fn(self, *args, **kwargs) # ...! return _with_transaction class Blah: def __init__(self): self.db = "a database object" def insert_foo(self, foo): print "We're about to insert foo", `foo` insert_foo = with_transaction(insert_foo) blah = Blah() blah.insert_foo("bar")
Another is with a function that takes an unbound method:
def with_transaction(method): def _with_transaction(self, *args, **kwargs): print "Doing a transaction with", `self.db` return method(self, *args, **kwargs) return _with_transaction
I suppose that in Python 2.4, decorators can be put to good use:
class Blah: @with_transaction def insert_foo(self, foo): print "We're about to insert foo", `foo`
That's as good as it gets, though. It's the closest Python has to actual code blocks. They're not necessarily *functionally*; what can be done with a code block can be done with a function or method, using descriptors, decorators, closures or not. with_transaction
will work just fine as it is, but my point was that it would be nice if you could just grab some code and wrap "stuff" around it... kind of like you can wrap a try..except
around things, without having to put the code in the try
and except
blocks in separate functions.
Somebody asked how the descriptor example works. It's not so difficult. Normally, when you define a method, an unbound method is added to the class:
>>> class Foo: ... def bar(self): print 42 ... >>> Foo.bar <unbound method Foo.bar>
It's possible to replace this with other objects, like a descriptor:
>>> class simple_descriptor(object): ... def __init__(self, f): ... self.f = f ... def __get__(self, instance, klass): ... print "Called from instance", instance ... print "This descriptor holds method", self.f ... >>> class Foo: ... def bar(self): print 42 ... bar = simple_descriptor(bar)
Here, the descriptor is wrapped around bar
at the class level, but it works (or is capable of working) at the instance level.
>>> foo = Foo() >>> foo.bar Called from instance <__main__.Foo instance at 0x018E7C88> This descriptor holds method <function bar at 0x018E3FB0>
When we ask for foo.bar
, the descriptor's __get__
kicks in. In the example above it doesn't do anything useful, but in real life it can be used for various purposes, like controlling access to an object's attributes, for example. (Properties!)
In the case of with_transaction
, a descriptor can be used to store the original method, then return a wrapper function that calls that method when __get__
is invoked. It's a bit tricky, and using a function rather than a descriptor is shorter.
Maybe I'll write a better descriptor example later.
#645 (old comments) I want my code blocks
Python is very dynamic, and usually fits my needs quite well. But sometimes there are cases where I wish it would be a little bit more flexible.
In my code, I have a number of methods (all in the same class) looking like this:
self.dbado.conn.BeginTrans() try: ...arbitrary database code here... except: self.dbado.conn.RollbackTrans() raise else: self.dbado.conn.CommitTrans()
In other words, execute a bunch of code in a transaction; if something goes wrong, roll back the transaction, otherwise commit it.
Now, this pattern occurs several times, and I was looking for a better way to do this. A decorator-like solution comes to mind, but the presence of self
makes it impossible to do that at the class level (unless you use a descriptor, see the next post):
class Blah: ... def insert_foo(self, foo): ... insert_foo = with_transaction(insert_foo)
This doesn't work, because with_transaction
needs access to self
to use the database connection. The method wrapping is done at the class level, but the transaction handling needs an instance.
So, eventually I settled for this:
class Blah: def __init__(self): ... # wrap methods here... self.insert_foo = self.with_transaction(self.insert_foo) # ...etc... def with_transaction(self, f): def _with_transaction(*args, **kwargs): self.dbado.conn.BeginTrans() try: result = f(*args, **kwargs) except: self.dbado.conn.RollbackTrans() raise else: self.dbado.conn.CommitTrans() return result return _with_transaction def insert_foo(self, foo): ...
It works, but it's not very elegant. Sometimes I really miss code blocks like Groovy has them (or maybe Ruby). It would be nice to be able to define with_transaction
and use it whenever I need to do something in a transaction:
with_transaction { database.dothis() database.dothat() # ...etc... }
Is there a better solution? Am I missing something obvious? Suggestions welcome.
[Update #1] Bob Ippolito points out that this can be done with a descriptor.
#644 (old comments) Kamigawa comments
A few comments on the new Champions of Kamigawa set. (For those who don't know, it's the latest Magic the Gathering expansion set.)
Bushido is a bit of a strange choice of keyword. It's a new keyword for an old effect; bushido N means "when this blocks or is blocked, it gets +N/+N until end of turn". So they keyworded an effect that existed in many other Magic sets, yet they give it a name that prohibits it from being used in future blocks. The name "bushido" matches the Japanese flavor of the Kamigawa block, but would be totally out of place for future non-Japanese blocks... that's why I think it's a strange choice.
Flip cards are interesting. They are a pain for spoiler generators and card databases, but I imagine it must be a cool thing to rip open your booster and pull out a flip card.
The new mechanics don't impress me very much. They are either rehashes of existing ones (bushido, vigilance, defender), or they seem rather awkward (splice, soulshift). I wonder if R&D is slowly running out of good ideas. Then again, Mirrodin was an outstanding block, both flavor-wise and functionally... not every block will be up to the same high standard.
Overall, the set seems awfully close to Legend of the 5 Rings, as I more or less expected. Of course, it's still Magic, but they took more than one page out of the L5R book. Not just the flavor (complete with oni, rat people and snake people), but also certain game features. Unique characters (aka "legends") are very important in L5R, and have been since the very beginning. L5R also had weapons ("equipment") since the early days, something Magic introduced in the Mirrodin set. All in all, the similarities are a little too suspicious, especially considering that WotC/Hasbro used to develop L5R as well at some point.
#643 (old comments) SVN is dope
Well, it seems that way, so far.
Using Subversion seems very easy, both from the command line and using the TortoiseSVN integrated explorer tool. I've been wrestling CVS since I've known it; in contrast, my first experiences with Subversion were painless. It's possible that not everything is perfect yet, but so far it's working well for me.
#642 (old comments) Wax 0.2.33 released
It can be found in the usual place. Please report bugs etc to the usual address.
This is the first public release that uses wxPython 2.5.2.7. (Or 2.5.2.8, the bugfix release.) Earlier versions of wxPython are not recommended.
Not much has changed since 0.2.28. A few controls now support more styles and events. This is an ongoing process; not all widgets support all the options of their wx counterparts yet. But we're getting there.
wxPython 2.5.2.7 introduced a change in sizer behavior. Wax does not reflect this change, and keeps the original behavior. This has been discussed earlier.
I have this feeling there will be more updates in the near future...
#641 (old comments) Repairing my bicycle, the hard way
I usually prefer the dynamic nature of Python over the static nature of other languages, but there are situations where static code analysis is useful. I ran into such a situation today.
All I wanted to do is move a class to its own module (as the first step in a long series of refactorings). Unfortunately, this class is used all over the place. In compiled/statically typed languages, like Java or ObjectPascal, this would be trivial... just move it, recompile, compiler tells you all the things that break, go to those places and fix it. On top of that, some languages have refactoring tools, that take care of the changes for you. (Renaming a method would be a task with a similar effect, that could easily be handled by a refactoring browser.)
With Python + any old editor, this isn't possible. [1] Let's call this particular class Foo
. I have to search my code for all occurrences of Foo
, and fooparser.Foo
, which was the old location of the class. To make matters more difficult, there are also classes FooHandler
, FooSummary
, etc, which I don't need to change (yet), but which show up in the search results anyway. ...After obtaining (hopefully) all the occurrences of Foo
, I have to fire up my trusty old editor and change them by hand, one by one.
This isn't much of a problem for small projects, but unfortunately this one has over 40,000 lines. So it's not a trivial task.
What's more, after having changed all these occurrences, all I can do is hope that I didn't miss any... I have no way to be 100% certain that everything has been changed. Luckily, this project has a whole bunch of tests, which should take care of most misses. But even with a test suite I won't get 100% certainty.
Are there better/more reliable ways to do this, short of using a refactoring browser?
[1]
#640 (old comments) The many layers of Wax
Wax is the topmost of a number of layers:
- Wax
- wxPython
- wxWidgets
- native Windows / Aqua / GTK / etc
Many layers may seem like a possible problem... something was said about it in the newsgroup recently, and I've read about it somewhere before. I personally don't think this is a problem. Wax doesn't "feel" any slower than pure wxPython.
Even if it was a problem, this is as good as it gets... unless you want to reinvent the wheel and write a whole new low-level GUI interface that hooks into Windows/Aqua/GTK/whatever. I like to write stuff, but that wheel is a little too big for me; I'd rather use the existing work (wxPython, wxWidgets).
Anyway, each layer has its own purpose... the native layer provides a low-level interface specific to the system in question, often with processor- and OS-specific code. wxWidgets sits on top of that, providing a common C++ interface for multiple systems. wxPython sits on top of *that*, providing the Python interface. And because even that interface is often kind of thin and lacking and too low-level, Wax was born.
I have great plans for Wax, but it's uncertain if I will be able to fulfill them. Next month I'll probably know more.
Design and content © 2004 Electric Shock / Hans Nowak