Dynamic Programming Environments

Jeremy Lowery jslowery at hotmail.com
Fri Jan 4 03:41:19 EST 2002


WARNING: Below is design, implementation, practicality ramble.

I've recently been working on a MUD Engine in python, and I've noticed a few
(woa too few) people on here that are also interested in MUD development (I
believe that Python is an awesome language that could be put to work on
MUDs), so I figured I'd let my brain chatter for a little bit. I wanted the
engine to be completely OO, but I could not bring myself to use a standard
class/instance design (like mostly all mainstream OO languages, Python
included) mainly because of the following requirements:

- "Class Definitions" would need to be changed at runtime and their
"instances" would have to dynamically reflect this change all throughout the
system. You could get really messy with this if you wanted to..like have
have class version numbers and update instances to as version, and implement
rollback, etc. you could also do a lot of piddling with the new module,
doesn't seem too elegant though.
- I discovered ZODB and decided that it would be a wonderful database to
store the objects in (As opposed to other python mud engines such as Moebius
that uses a rdbms), and currently storing class definitions along with
instances of those classes in a ZODB isn't currently possible.
- The engine needs a type of code-security mechanism, and the only way I
could see implementing it would be by hacking AST objects and examining
parse trees (a task I wouldn't look forward too). Of course, rexec could
help out a bit but it would be too limited.

Those problems aside, and addressing a more generic thought, such a system
could be a rather interesting venture. Class definitions would be stored in
"one" part of the app, and perhaps you could have a global Application
object that was composed of all of the running instances of the dynamic
classes (which the Application object would be stored in ZODB). There would
also be a collection of code editing and management objects that lived in
the driver that did things like editing methods on dynamic classes, creating
dynamic classes, managing the class hierarchy, etc. You could put a facade
on that subsystem with a standard interface, allowing third parties to write
"managing" interfaces for it (like web-based, telnet, custom IDE's, XML-RPC,
Web Services, DCOM, whatever you wanted).  Heck, these things could be
installed dynamically (without sourcecode hack) with a little dynamic import
magic. Of course, I have tried to pin down some practical implications of
such a system, but my brain is just set on MUD mode (group of programmers
working in a dynamic environment, coding a system). For some more on this,
check out http://www.laputan.org/reflection/living.html (an interested
article I read that got my brain working)Such a system could be envisioned
as an interactive python interpreter with some special commands that is
working on sets of class definitions along with a bunch of other people on
interactive interpreters. If you really wanted to get crazy, you could have
a dynamic module structure (each programmer has their own little module they
can play in, and then there are system modules, organized by design goals).
It would probably be easiest to implement something like this by writing
files to disk and actually making the dynamic classes real source-code files
(and the slew of reflection, inspection it would take to handle something
like a .EditMethodCode(obj, mname, newcode, arglist).

However, I decided to go with another object-oriented approach that
facilitated a MUD engine a little better, which is an environment on which
the only thing the dynamic environment held were "objects." There are no
classes or instances or any of that. Basically an object is just a class and
a single instance tied tightly together (in a thoughtful way of course). It
has methods and data attributes. I directly got this idea from another
engine called Genesis/ColdC (http://www.cold.org) Of course, it's not unique
to that engine but there ya go.  A code hunk for explaination...

class VRObject(Base):
    __sysattrs__ = ['__name__',
                          '__parents__',
                          '__owner__',
                          '__group__',
                          #... more sysattrs here
                            ]
    def __init__(self, name, parents, owner, group, env):
        self.__name__ = name
        #XXX Define a default parent
        self.__parents__ = parents
        self.__owner__ = owner
        self.__group__ = group
        self.__userdict__ = PersistentMapping()
        self.__services__ = []
        self.__execenv__ = env
        self.__vrexec__ = VRExec(env)
        self.__adapters__ = []
        self.__interfaces__ = []
        self.__metadata__ = PersistentMapping() # Information 'About' this
object


# And here is the magic, (weirdo) stuff

    # Instance behavior overrides
    def __getattr__(self, name):
        if name in VRObject.__sysattrs__:
            try:
                return self.__dict__[name]
            except KeyError:
                raise AttributeError, name
        # NOTE : Adapaters are just an explicit delegation  mechanism.
        # Adapter map
        for a in self.__dict__['__adapters__']:
            if hasattr(a, name) and callable(getattr(a, name)):
                return getattr(a, name)
        try:
            attr = self.__dict__['__userdict__'][name]
            if hasattr(attr, '__class__') and attr.__class__ == VRMethod:
                attr.cinst = self
            return attr
        except KeyError:
            #XXX Parents assign cinst multiple times, maybe a fix is needed.
            for parent in self.__parents__:
                try:
                    attr = VRObject.__getattr__(parent, name)
                    if hasattr(attr, '__class__') and attr.__class__ ==
VRMethod:
                        attr.cinst = self
                    return attr
                except AttributeError:
                    pass
            raise AttributeError, name

    def __setattr__(self, name, value):
        if name in VRObject.__sysattrs__:
            self.__dict__[name] = value
        else:
            self.__userdict__[name] = value
        self._p_changed = 1

    def __delattr__(self, name):
        if name in VRObject.__sysattrs__:
            raise AttributeError, '%s is a reserved name and cannot be
deleted.' % name
        try:
            del self.__userdict__[name]
        except KeyError:
            raise AttributeError, name
        self._p_changed = 1

    def __repr__(self):
        return '<VR Object %s on OC %s>' % (self.__name__,
self.__owner__.name)


#///////////////////////////////////////////////////////////////////////////
/////////////////
    # Dynamic object editing methods
    def __addmethod__(self, code):
        meth = VRMethod(self, code)
        name = meth.name
        """ Add a new method to the object."""
        if name in VRObject.__sysattrs__:
            raise AttributeError, '%s is a reserved name.' % name
        if name in self.__userdict__.keys():
            raise AttributeError, '%s is already defined on %s.' % (name,
self)
        #XXX Maybe fire an event hook
        self.__userdict__[name] = meth
        self._p_changed = 1

    def __editmethod__(self, name, code):
        """ Edit a method's code on the object."""
        if name in VRObject.__sysattrs__:
            raise AttributeError, '%s is a reserved name.' % name
        if not name in self.__userdict__.keys():
            raise AttributeError, '%s is not defined on %s.' % (name, self)
        #XXX Maybe fire an event hook
        self.__userdict__[name] = VRMethod(self, name, code)
        self._p_changed = 1

    def __delmethod__(self, name):
        """ Delete a method from the object."""
        if not name in self.__userdict__.keys():
            raise AttributeError, '%s is not defined on %s' % (name, self)
        if not self.__userdict__[name].__class__ is VRMethod:
            raise AttributeError, '%s is not a method.' % name
        #XXX Maybe fire an event hook
        del self.__userdict__[name]
        self._p_changed = 1

    def __hasmethod__(self, name):
        #XXX Add adapter method lookup
        try:
            return (name in self.__userdict__.keys() and
self.__userdict__[name].__class__ is VRMethod)
        except:
            return 0

    def __hasprop__(self, name):
        try:
            return name in self.__userdict__.keys() and
self.__userdict__[name].__class__ is not VRMethod
        except:
            return 1

    def __hasattr__(self, name):
        return name in self.__userdict__.keys()

    def __derive__(self):
        """ Derives a new child object. """
        n = self.__name__
        ix = 1
        while self.__execenv__.lookupVRObject('%s_%d' % (n, ix)):
            ix += 1
        n = '%s_%d' % (n, ix)
        new = apply(self.__class__, (n, self, self.__owner__,
self.__group__, self.__execenv__))
        self.__owner__.giveObjectTo(new)
        return new


Of course, something about a design like this I don't like is that
"instances" of your object are derived children. Like take a Room object,
the actual room object would be called "Nowhere" and all of the "real" rooms
would be children of it. Of course, some distinction could be made between
subobjects and actual objects...but that would be just like class/instance
stuff! Another wheel reinvention (Turning the full circle). In a very weird
way, I noticed that this code looks a lot like Guido's metaclass article on
python.org. (Of course, I have to have drank me a few to actually see that
;) One thing that has to pop in my mind is "Could metaclasses help me?," and
then invariably, "Do I really want an answer that makes my head explode?"

And that got me to thinking about a lot of other crazy stuff. Like have a
SelfDescibingMetaClass...where the source code was stored along with the
actual methods and attributes, the metainstances would be rewired for
sniffing out method code changes or changes in the class hierarchy. Maybe
the type/class unification in 2.2 could help me out (and making the thing
store in ZODB makes my skin crawl). But perhaps the source code should be
decoupled so it all doesn't have to be in main memory (use a cache, and LRU
to disk access or something). I dunno, having a metainstance that asks a
visitor to rewrite its method code, and then having the metainstance put
that to work for the real instances seems a bit hairy. If you want to be
silly and stick metainstance locking for multiple thread access or code
locking for multiple developers accessing it at the same time...hmm.

Recently, I've been making posts on this group concerning a lot of dynamic
object manip stuff, so hopefully people will stop telling me that the exec
statement is very poor practice and should be avoided at all costs (There's
always an easier way and it's overkill!!:) Especially, when the this kind of
stuff almost definitiately demands such language uses.

I've thought about other such ways to do some things like this. One would be
to embed Python into a C++ program. (Since I'm a win32 guy, maybe even use
Mark Hammond's ActiveScript facilities and some COM, ActiveScriptHost
stuff). At this end, the system could be like similar to IIS. Instead of
.asp files being run atomically in asp.dll, there would be python class
files that run in another host, and the OutOfProc Server would manage the
instance variables running in the app, along with the dynamic nature of
changing class definitions. This system would seem fairly easy for a
disconnected, stateless environment like a web site and IIS, but it would
probably be rougher for a MUD engine.

Heh, sorry this post was so long. I guess if you made it this far then
you're either interested in what I'm saying, think what I'm saying is a
bunch of crap and want to deflate me, or you're a bored individual.

Well, my mind is pretty much cleaned out as far as the core environment
design stuff goes so there ya go. There are a lot of design mudcentric
issues and ideas running around my head, but I'll save them for another post
;)


--

Jeremy S Lowery


-- "I want you to remember that no bastard ever won a war by dying for his
country. He won it by making the other poor dumb bastard die for his
country..." - General George S. Patton, June 1944






More information about the Python-list mailing list