newstalk

newstalk

:: like newspeak, minus the ungood bits. ::

newstalk RSS Feed
 
 
 
 

The terrible secrets of dynamic Python.

In honor of the new “geeky” category, I’d like to skim over the joys and terrors of dynamic Python.

The Problem

In my current programming project, I have in-game characters that need a set of personal attributes defined–you know, things like “strength” and “intelligence”. However, I haven’t settled on a stat system yet, so I wanted to make a flexible architecture which would do all the heavy lifting, and later down the road when I have my system figured out, it would be trivial to implement.

Enter the CharGen class, which is a generic sort of class that keeps track of four different kinds of stats–physical/mental/social “attributes”, “skills”, “meta” attributes, for string-values like names, etc., and “persona”, mainly for numeric values which defy classification. These naturally implemented themselves as dictionaries, so if you want to look at the strength attribute, you just do

chargen.attr['strength']

If you want to check a character’s skill at driving, you look at

chargen.skill['drive']

This also makes it easy later to iterate over, say, all the attributes, or over all the skills. Easy, right?

But, what if you want to write a method that needs to check or set an arbitrary stat? Well, first you have to check the stat’s type. Then you have to go to that dictionary and look up the attribute itself. The code looks like this:

attr = 'strength'
attrtype = self.all[attr]['type']
getattr(self, attrtype)[attr]

Seems like a lot of work just to check a lousy variable, eh? And, it’s UgLy. I tried stuffing it into a method you could call, and that was going to work okay. But then I thought, “what about when I want to set a stat?” The code is almost the same, using setattr instead of getattr. But it doesn’t allow for any sanity-checking–for instance, if I were using a stat system where strength couldn’t be any higher than 20, I could still set chargen.attr['strength'] = 50, and nobody would complain. A generic method wouldn’t work either, because the CharGen method is meant to be subclassed, and I had no idea what my future self might need as far as checks and balances.

What to do? What to do?

A Half-Answer

At the same time, I thought, “boy, it sure would be convenient, and nicer to look at, if I could just say

chargen.strength

instead of

chargen.attr["strength"]

Well, Python has this unassuming little feature called managed properties, where you store the actual data anywhere you want (like in a dictionary, or a private variable, or whatever), then you define a getter and a setter method for that data, and then you just assign chargen.strength to a special property object that knows about the getter and setter, and, voila,

chargen.strength

works just like

chargen.attr['strength']

Cool, huh? And even better, you could set up any sort of rules you want in the setter method, so you can rest assured that nobody will ever be able to set chargen.strength to 50 if you don’t want them to. (They could always use chargen.attr['strength'] to commit whatever foul crimes they like, but I just have to trust my future self not to be so nefarious.)

But I immediately hit another snag: where do the getters and setters come from, if I won’t know the stats and constraints until subclassing time?

The Terrible Secret

Here’s where we start delving into the dark arts of a dynamic language like Python. In a static language, I would be stuck having to do some of the work at subclassing time. Define a stat, then manually define a getter and a setter for it. It’s all very simple code, and it’s very boring to type over and over again. I code because it’s fun, and I avoid coding stuff that’s not fun. This would not be fun.

Then I discovered a recipe for a function factory that makes instance methods. If that didn’t make sense to you, that’s okay, because it’s pretty abstract, and I still don’t quite get it either. In fact, part of me is surprised every time I run the unit test and it passes. Stuff like this shouldn’t work–it’s too subtle, too sleight of hand. And whatever else it may be, it sure ain’t obvious. If I’d read about this two years ago, when I was first learning Python’s syntax, I would only have been able to muster a blank stare. I’ve come a long way.

Now, all my future self has to do is subclass CharGen, define a dictionary of stats with type and constraints, then call CharGen’s init method, and it all happens, dictionaries, properties, getters, setters, everything as if by magic. It’s so cool, it’s creepy.

Possibly related:

    None Found

Like what you see?

Don't forget to subscribe via RSS or email to receive updates when I post new material. Thanks!

Comments are closed.

Feedback: I'd love to hear what you have to say!
  1. I don't have time to manage or moderate a live comments forum, but I'd love to hear from you.
  2. (required)
  3. (valid email required)
 

cforms contact form by delicious:days


subscribe