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.

icon:rant #599 (old comments) Creeping featurism

Fredrik criticizes the current state of Python development. In addition, a recent python-dev post by JP Calderone states: "How many features that everyone hates can Python support? The Zen of Python might state that practicality beats purity, but if it continues to do so, time after time, at some point we're going to look up and wonder where that clean, readable, maintainable programming language we all loved has gone."

I tend to agree. I sometimes jokingly refer to Python 1.5.2 as the "One True Python", but there is some truth in it. Ask yourself: do the features added since 2.0 make Python a better language? If not, then why were they added in the first place?

Adding one feature is not a problem, even if it's a dubious one. Adding lots of features *is* a problem, because eventually it will make the language more complex, and more difficult to learn completely. People often wave this problem away... "oh, it's just a small change, that doesn't make the language more difficult, and it doesn't use new keywords or operators, so it doesn't require new syntax." Yeah, OK.

It appears to me that many of the recent (2.0 and up) changes deal with corner cases, designed to solve problems that *sometimes* occur, rather than problems that most people have. Sets, a decimal data type, reverse iteration, weak references, importing from zip files... I suppose they're nice to have in *some* situations. I got along just fine without them, though, and apparently I'm not the only one, considering how much Python code was written before their introduction.

More harmful are the features that don't seem to have a legitimate use in the wild... those are just baggage. Rich comparisons. Function attributes. Who actually uses these?

We've also seen gratuitous features that we could do perfectly well without... the boolean type one of the prime examples. IMHO, it just adds complexity, and adds practically nothing. And I like print >>, but it wasn't exactly necessary either.

Generators and list comprehensions are useful. I think. Listcomps are a nice shorthand and an alternative for map and filter. Generators are nice when you don't want to create a large bulky list in memory. (Although in my personal and work projects, I have yet to see a list that is large enough to be a problem. And it was possible to write lazy iterators before.)

New-style classes are a noble attempt at unification of types and classes, and making things more simple. It doesn't really succeed though... so far it has only made things more difficult. Different rules for new-style classes, different attribute lookup, different inheritance rules, clunky super function, hackish classmethods and static methods, metaclasses, unpythonic properties. It still pays to use old-style classes, because they do the job, yet are simpler.

But whether you consider Python's new features a success or not, the language clearly suffers from creeping featurism. If I want Ruby, or Perl, I know where to find it. What happened to the small, elegant language?

I'm not ready to abandon Python yet, but if it keeps going down the same path, that moment will come eventually. Right now I'm enjoying Scheme for a toy project. I'm not really considering Scheme as an alternative to Python... but it's telling that that language seems refreshingly simple and clean, after a few years of almost exclusively using Python.

Who knows, maybe Python 3.0 will fix everything... or maybe Python 3000 then? emoticon:smile

/* Posted by Hans Nowak at 2004-08-03 13:33 */

icon:scheme #598 (old comments) In the grand scheme of things

To keep in the spirit of the previous post... About four years ago I wrote a program called Astrologica. This was basically an interactive Python interpreter with a number of objects pre-loaded, most importantly a collection of horoscopes. It had an interface to astrolog.exe, so you could compute horoscopes, add them to the collection, then query horoscopes ("what aspect does Pluto make?") or the collection ("who all has Moon in Aries?").

I'm now trying my hand at a similar project, except that it's written in Scheme. PLT Scheme to be precise.

Compared to Python, this is quite a different programming experience. DrScheme (the primary PLT Scheme implementation) has a "working area" next to an interactive interpreter. You can write some code, press Execute, and try it out immediately, interactively. [1] This is a great boon when growing a program, function by function.

Apropos functions... in Python I would probably tackle this problem by writing classes for collection, horoscope, maybe points in the horoscope, etc. In my current Scheme implementation (which admittedly isn't nowhere near done), I use a struct for the horoscope, but lists for everything else. Functions, not methods, perform actions like getting horoscope data, etc. The collection will probably be a list too. You can easily run a filter over it to get all the horoscopes of US presidents, or people with Mars in Taurus, or people with a Jupiter-Saturn conjunction. And you'll just get a list back, which can easily be used for further processing.

Symbols are nice too. The elements are now defined as (fire earth air water), rather than a list of strings (which would introduce possible case sensitivity issues), an enumeration, or simply constants containing arbitrary numbers.

It's interesting that even in Python you (or I, at least :-) tend to make things more difficult than necessary. Certainly, I could use a list for a horoscope collection in Python too, and lists or tuples like ("point", 20, 10, "Aquarius") to indicate positions. But not many people would do it like that. It would feel kind of unnatural in Python. ...OO thinking is cool, but it's refreshing to see that not everything needs to be a class.

Another question is: how well will it scale? If this new astrology system grows, will it still perform? [2] Will name clashes become a problem? We'll see. I don't know very much about Scheme. But so far, it looks promising.

[1] Granted, this would be possible with Python too, but that is not how most Python editors/IDEs work. Maybe an idea for Charm or its successor...

[2] I found a site with 15000 horoscopes... so I downloaded them, and I'll try to extract the relevant data from the HTML. Once this has been converted to a neutral format, it can be used as input data for Python, Scheme, etc.

/* Posted by Hans Nowak at 2004-08-01 14:12 */

icon:astrology #597 (old comments) use Astrology::Statistics;

A while ago I mentioned that certain scientific methods, like statistics and probability theory, could be useful when doing astrology.

I didn't mean that it was useful for gathering "proof" for astrology. Rather, those techniques are useful when judging how often something occurs, versus how often it should occur according to chance. This helps when determining if a certain effect or position happens as often as expected, or that something more is going on.

For example, I read in a book that "many US presidents were Aquarius". Many? Looking at the list of all US presidents, 4 of them had Sun in Aquarius, out of 43. Now, if you don't know anything about statistics, then you might think: "Yes, the book is right. 4 presidents were Aquarius." But when you think about it, that really isn't special. There are 12 signs. 43/12 ~= 3.58. Out of 43 people, I'd expect 3 or 4 to be Aquarius (or any sign, for that matter). Statistically, 4 out of 43 is totally insignificant. Now, if 8 of them were Aquarius, that would be more interesting. (Although it still wouldn't say much because the "sample size" of 43 is so small.)

I don't think the Sun sign matters when it comes to becoming president of the United States. What could be a more useful research is to check the 43 horoscopes and see if any patterns emerge. Like with the 10th house, for example?

/* Posted by Hans Nowak at 2004-08-01 13:56 */

icon:wax #596 (old comments) Wax 0.2.28: properties, autoevents, and more

OK, after considering people's comments about the so-called property parameters, I've decided to allow a single parameter properties in a constructor call. This parameter takes a dict and is used like this:

from wax import *

myproperties = {
    'Size': (300, 150),
    'BackgroundColor': 'white',

class MainFrame(VerticalFrame):
    def Body(self):
        # Method 1: Set properties through constructor
        p1 = Panel(self, border='sunken', properties=myproperties)
        self.AddComponent(p1, expand="both", border=5)
        # Method 2: Set properties through SetAttributes()
        p2 = Panel(self, border='raised')
        self.AddComponent(p2, expand="both", border=5)
        self.BackgroundColor = p1.BackgroundColor
app = Application(MainFrame)

So, in spite of my earlier plans, you *cannot* do:

b = Button(parent, Size=this, BackgroundColor=that)
# XXX not allowed

The WaxObject.SetAttributes method was already available, and it can be used to set properties (or other attributes) as well.

[I am now thinking that you can do the same with layout parameters. A layout parameter in the constructor could be an alternative for AddComponent(), at least in some cases.]


Wax 0.2.28 is an interim release. Actually, all Wax releases are, pretty much, but in this case, I'm in the middle of adding style parameters and such to classes.

Most wxPython controls allow a (large) number of style flags to be set, but their Wax equivalents didn't support many of them, so far. So I'm in the process of changing that. As usual, Wax doesn't use the actual style flags, but rather uses special parameters. The new styles.py module translates these parameters to wxPython style flags.

Currently, styles (and the properties parameter) have only been added to be a few controls (Frame, Panel, Button and NoteBook, to be precise). As long as you're aware of this, there should be no problem using them, just don't expect them to be valid everywhere.


Also, version 0.2.25 introduced a possibly INCOMPATIBLE CHANGE. FileDropTarget and its new brother TextDropTarget now automatically call SetDropTarget. These objects take a parent (or rather, a control they affect), so having to call SetDropTarget explicitly seemed redundant. (See also examples/dragdrop-1.py.)

However, problems may occur with older code that already calls this method. If it's called twice, then wxPython may crash; at least that is what happens on my box (wxPython, Windows XP). So if your app uses drag & drop and suddenly starts crashing, consider commenting out this line in your code.

I'm assuming this is a bug in wxPython that will be fixed eventually, so I'm not working around it in Wax.


Also new is "auto-events" for menus. It works like this: when you add a menu item, and you don't specify an event, and autoevents is True, then Wax will attempt to determine a method name from the menu item and its parent(s), look for that method, and hook it up to the menu item.

In other words, if I have a menu "File" with a menu item "Open", and a method Menu_File_Open exists, then this menu item will automatically be associated with that method. You don't have to specify it. "Special" method names have to start with "Menu", by the way. And if the method is not found, that's not an error. See examples/menudemo-2.py for a better example.

This feature is entirely optional, and you can happily keep doing it the old way. Maybe I will too. :-)

Due to the rather, uh, special way menus work in wxWidgets, this wasn't exactly easy to implement, and may still contain bugs. -- You'd think that if I have a menu A with submenu B containing menu item C, that B can be considered C's parent, and A can be considered B's parent. Maybe not in the "widget X is Y's parent" sense, but it would be nice if there was at least a simple way to find out which menu contains C (or B). Getting a Menu's text (label) isn't as easy as it seems either. So I ended up writing some painful code to walk over all menu items, just to determine the labels of a menu item and its "parents". Who knows, maybe a menu walker is good for something else as well... emoticon:nosmile


Anyway, 0.2.28 can be found in the usual place. Bug reports etc go to the usual address. Enjoy!

/* Posted by Hans Nowak at 2004-07-31 00:39 */

icon:programming #595 (old comments) Great hackers and lousy languages

Paul Graham's latest article, Great Hackers, is causing a lot of buzz. There are lots of ideas in that article, many of which controversial. Language wars have been started for less.

The way I see it, PG looks at languages from a designer's point of view. (Not exclusively as a language designer, by the way.)

For a silly analogy, let's assume that the painting industry was as big and important as the software industry. (The painting of paintings, not of walls, that is.) Knowing that mediocre products still sell, there would be a lot of Bob Ross type painters. They would be able to crank out acceptable (but not great) paintings, fast, for a relatively low price. Such paintings would be "good enough" for most people. But a "real" artist would never use these techniques. To him/her, painting is about expression, and about creating something new. It's not about doing a number of tricks to produce a mediocre painting that looks just like everybody else's.

Back to programming. Programming isn't 100% art, but the ideas mentioned above still apply to some extent. The "Great Hackers" in PG's essay care about the final product (software), but they also care about how it is written. They want a language that allows them to express themselves, that stays close to the way they think, that lets them do what is necessary, and that gets the job done. They don't want a language that gets in the way of these goals. They don't want a Bob Ross or paint-by-numbers language.

From this point of view, Java is a mediocre language. An unimaginative language for unimaginative people. That doesn't mean that imaginative people don't use it, or that you can't get anything done with it. You can get lots of things done with Java, and due to the sheer size of its libraries alone, maybe even better and faster than with higher-level languages. The point is not that Java doesn't have its merits; the point is that it doesn't match the features described in the previous paragraph (expressive, close to thinking, do what's necessary, get the job done).

There are quite a few languages that do match these principles, in various degrees and on various levels. They are mostly (very) high-level languages. If you think about problems at a very high level, then it's useful to have a language that matches this.

/* Posted by Hans "-1, flamebait" Nowak at 2004-07-29 14:35 */

icon:python #594 (old comments) Dark corners

Occasionally I still discover some obscure "feature" of Python that displays surprising behavior. For example, today I saw this post in the newsgroup, showing that __getattr__ is looked up in the class rather than the instance, even if it's overridden in the instance. A bit of code demonstrates this:

class A:
    def __getattr__(self, name):
        return 43

a = A()
print a.foo # 43

a.__getattr__ = lambda x: 101

print a.foo # still 43
# To make this work, you have to set A.__getattr__:

A.__getattr__ = lambda self, name: 101
print a.foo # 101

Peter Otten then remarked: "Special methods are always looked up in the class." Is that true? It seems to be... for new-style classes. In old-style classes, adding __getattr__ to an instance may not work, but it does work for other magic methods like __add__. Consider:

class A:
    def __add__(self, other):
        return 42

a = A()
print a + 2 # 42

a.__add__ = lambda x: 100
print a + 2 # 100

class B(object):
    def __add__(self, other):
        return 42

b = B()
print b + 2 # 42

b.__add__ = lambda x: 100
print b + 2 # 42

In other words, for old-style class A I can inject a new __add__ method into the instance, and it works as expected... for new-style class B, this doesn't work.

This is hardly exhaustive research, so maybe there's a number of methods for which it works, and some for which it doesn't work. I don't know. I didn't find any clues in the reference manual.

/* Posted by Hans Nowak at 2004-07-28 21:17 */

icon:books #593 (old comments) Gutenberg RSS

This isn't news, but Project Gutenberg has an RSS feed. That is so fetch! Maybe I'll read more now. Like: Atlantis: The Antediluvian World. Or stuff by H.G. Wells. Or Edgar Allan Poe. Etc.

(Astute readers will notice that this is just a placeholder post while I ponder the comments on Wax's "property parameters". emoticon:smile)

/* Posted by Hans Nowak at 2004-07-27 23:48 */

icon:programming #592 (old comments) Fix it yourself

Via Slashdot: Open Source Myths.

Number #2 is interesting: Open Source software allows you to get under the hood and fix problems. The author claims that nobody actually does this. I think that dynamic, interpreted languages have an important leg up here... you can literally grab an editor and start hacking the code. Of course, you can do this in C, Java, etc, but there's an extra hurdle since you need to compile... and getting a C or C++ program to compile is not exactly trivial, especially not if it was developed on platform X and you run platform Y. Also, some languages are easier than others. emoticon:wink

Regularly, people make (small) changes to my open source software (Wax, Firedrop, etc). After all, all they need is a Python interpreter (which they had anyway to run the program) and an editor. They get immediate feedback simply by running the changed program and see what it does. Why wait for me to fix it if you can easily fix it yourself?

Of course, a non-programmer isn't going to do this... but then again, most of my users are programmers, and Python programmers at that, so maybe my sample is biased.

/* Posted by Hans Nowak at 2004-07-26 13:56 */

icon:python #591 (old comments) Interactive generators

Most of the time, when you write generators, they are used as a "black box"... you create it, loop over it, and that's about it. There's no interaction, no way to change what the generator returns. Here's some code that experiments with some forms of interaction.

# Interactive generator.

class GeneratorManager:
    def __init__(self):
    def reset(self):
        self.command = None
        self.data = None

def intgen(gen, manager):
    for item in gen:
        if manager.command == 'stop':
            raise StopIteration
        elif manager.command == 'unget':
            yield manager.data
        elif manager.command == 'skip':
        # other commands here...
        yield item

# demonstrate it...

# simple generator producing integers, starting at n
def gintegers(n):
    while 1:
        yield n
        n = n + 1

import random
# try this 10 times to see different results
for i in range(10):
    manager = GeneratorManager()
    g = intgen(gintegers(0), manager)
    for item in g:
        print item,
        r = random.randint(1, 10)
        if r == 1:
            manager.command = 'stop'
        elif r == 2:
            manager.command = 'unget'
            manager.data = item
        elif r == 3:
            manager.command = 'skip'

This specific example isn't very useful, but it's interesting to see how the results of the generator are affected by the "commands" passed to it.

/* Posted by Hans Nowak at 2004-07-26 13:47 */

icon:wax #590 (old comments) Wax open issues #1: "property parameters"

It seems that the 0.2.x series allows for lots of improvements to Wax. However, there are still quite a few unresolved issues, some of which I'd like to discuss here. People can comment or mail me with suggestions... if nobody reacts, then I'll just solve the issue as I see fit, and you'll have to put up with it. emoticon:smile Note that you don't necessarily have to use or know Wax to have an opinion on some of these matters.

wxPython has two important generic ways to set the behavior or appearance of a control:

1. Styles. Basically you set a number of flags/constants and pass them to the style parameter. In Wax, these constants are replaced by parameters that are a bit clearer (or at least that's the goal). For example, you can use NoteBook(parent, orientation="bottom", fixedwidth=1) rather than NoteBook(parent, style=wx.NB_BOTTOM|wx.NB_FIXEDWIDTH). Starting at version 0.2.26, there's a new mechanism that deals with the various styles efficiently.

2. Assorted parameters like size, position, etc. Many, if not all, controls allow the aforementioned ones, and more. If you look closely, you'll see that these can also be set with SetXXX methods: SetSize, SetPosition, etc. In Wax, the Get- and Set-methods are the basis of (pseudo) properties. You can do e.g. widget.Size = (400, 300). (Introduced and explained here.)

The issue is now: Should I add the possibility to pass these pseudo-properties as parameters to constructors? To illustrate what I mean, here's the current situation:

b = Button(parent)
b.Size = (100, 25)
b.Position = (20, 20)
b.BackgroundColor = 'yellow'

while in the new situation you'd be able to say:

b = Button(parent, Size=(100, 25), Position=(20,20),

...and initialize everything in one statement. Come to think of it, it would probably also be useful to have a method SetProperties that can be used to set multiple properties at the same time:

widget.SetProperties(Size=this, Position=that, BackgroundColor=foo)

Anyway, allowing for properties as parameters is a bit unelegant, since regular parameters are always lowercase, while the new parameters always start with an uppercase letter. Also, it introduces either redundancy or backward incompatibility, because some controls already have a size (with lowercase) parameter. If I leave it, it'll be redundant, if I remove it, code might break. ... On the other hand, it seems that this feature could be very useful.

Whaddya think?

/* Posted by Hans "and no, 0.2.26 isn't out yet >:-)" Nowak at 2004-07-25 13:09 */