Puzzling OO design problem

Bengt Richter bokr at oz.net
Sun Apr 10 08:47:02 EDT 2005


On 9 Apr 2005 03:49:19 -0700, "George Sakkis" <gsakkis at rutgers.edu> wrote:

>"Michael Spencer" <mahs at telcopartners.com> wrote:
>>
>> George,
>>
>> since you explicit allowed metaprogramming hacks :-), how about
>something like
>> this (not tested beyond what you see):
>>
>> [snipped]
>>
>
>Nice try, but ideally all boilerplate classes would rather be avoided
>(at least being written explicitly). Also, it is not obvious in your
>solution why and which placeholder classes have to be written (like
>World2.Movable) and which do not. By the way, my current working
>solution involves copying and pasting verbatim these classes :-) Below
>is an abstracted example; note that the 'declaration string' of each
>original class is exactly the same across all different versions after
>the first (e.g. "class B(PreviousNamespace.B, A)").
>
>
>#======================================================
># version_1.py
>
>class Namespace:
>    class A(object):
>        def foo(self): return "version_1.foo()"
>    class B(A):
>        def bar(self): return "version_1.bar()"
>    class C(B):
>        def zen(self): return "version_1.zen()"
>#======================================================
># version_2.py
>
>from version_1 import Namespace as PreviousNamespace
>class Namespace(PreviousNamespace):
>    class A(PreviousNamespace.A):
>        def foo(self): return "version_2.foo()"
>    class B(PreviousNamespace.B, A):
>        pass
>    class C(PreviousNamespace.C, B):
>        pass
>#======================================================
># version_3.py
>
>from version_2 import Namespace as PreviousNamespace
>class Namespace(PreviousNamespace):
>    class A(PreviousNamespace.A):
>        pass
>    class B(PreviousNamespace.B, A):
>        def bar(self): return "version_3.bar()"
>    class C(PreviousNamespace.C, B):
>        pass
>
>#======================================================
># test.py
># command: python test.py <#version>
>
>def NamespaceFactory(version):
>    return __import__("version_%d" % version).Namespace
>
>print NamespaceFactory(2).B().foo() # "version_2.foo()"
>print NamespaceFactory(3).C().bar() # "version_3.bar()"
>
>import sys, inspect
>namespace = NamespaceFactory(int(sys.argv[1]))
># print the __mro__ of each 'inner' class
>for name,cls in inspect.getmembers(namespace,
>                                   inspect.isclass):
>    print cls
>    for ancestor in cls.__mro__:
>        print "\t", ancestor
>
>#======================================================
>
See if this does what you want:
(Note that vermeta.py preliminarily writes out the three version_?.py files, so
you can just go to a temp directory and run python24 vermeta.py)

It makes the version files look a little more cluttered with the
open(..).write('''\ ... ''') wrapping. E.g., 2 & 3 are just


----< version_2.py >-----------
from version_1 import Namespace as PreviousNamespace
class Namespace(PreviousNamespace):
    __metaclass__ = vars(PreviousNamespace)['__metaclass__']
    
    class A:
        def foo(self): return "version_2.foo()"
-------------------------------

and 

----< version_3.py >-----------
from version_2 import Namespace as PreviousNamespace
class Namespace(PreviousNamespace):
    __metaclass__ = vars(PreviousNamespace)['__metaclass__']
    
    class B:
        def bar(self): return "version_3.bar()"
-------------------------------

And you only have to change one digit in the 3-line boilerplate
and your class definitions don't have to specify inheritance,
but it could test for type classobj (classic class) and only
redefine if so ;-)

There are some limitations I think (;-) but the idea is you just have to
specify  three lines of boilerplate for a new version, and then just
the classes and methods you are interested in overriding in the new
version, and you don't have to specify the class inheritance in the new versions,
as the lazystyle metaclass function takes care of that. I hope ;-)
Version_1 has to be hand made, but after that, see what you think.

----< vermeta.py >---------------------------------------------------------------
#======================================================
# version_1.py
open('version_1.py','w').write('''\
NAMESPACE_CLASSNAMES = ['A', 'B', 'C']
def metadeco(nsname, nsbases, nsdict):
#    print '--- metadeco ---', nsbases, nsdict['__module__'], __name__
#    print 'nsname  = %r\\nnsbases = %r\\nnsdict  = %s' %(
#        nsname, nsbases, ',\\n    '.join(str(nsdict).split(', ')))
    if nsdict['__module__'] != __name__:  # exclude this first-version module
#        print '--- doing meta stuff for namespace of module %s ---'% nsdict['__module__']
        for i, cname in enumerate(NAMESPACE_CLASSNAMES):
            cbases = (vars(nsbases[0])[cname],) + (i and (nsdict[NAMESPACE_CLASSNAMES[i-1]],) or ())
            if object not in cbases: cbases += (object,)
            if cname in nsdict:
                cdict = nsdict[cname].__dict__.copy()
                #cdict['__module__'] = __name__
            else:
                cdict = {'__doc__': '(Generated by version_1.metadeco)'}
            cdict['__module__'] = nsdict['__module__']
            nsdict[cname] = type(cname, cbases, cdict)
    return type(nsname, nsbases, nsdict)
    
class Namespace(object):
    __metaclass__ = metadeco
    class A(object):
        def foo(self): return "version_1.foo()"
    class B(A):
        def bar(self): return "version_1.bar()"
    class C(B):
        def zen(self): return "version_1.zen()"
''')

#======================================================
# version_2.py
open('version_2.py','w').write('''\
from version_1 import Namespace as PreviousNamespace
class Namespace(PreviousNamespace):
    __metaclass__ = vars(PreviousNamespace)['__metaclass__']
    
    class A:
        def foo(self): return "version_2.foo()"
''')
#======================================================
# version_3.py
open('version_3.py','w').write('''\
from version_2 import Namespace as PreviousNamespace
class Namespace(PreviousNamespace):
    __metaclass__ = vars(PreviousNamespace)['__metaclass__']
    
    class B:
        def bar(self): return "version_3.bar()"
''')

#======================================================
# test.py
# command: python test.py <#version>

def NamespaceFactory(version):
    return __import__("version_%d" % version).Namespace

print NamespaceFactory(2).B().foo() # "version_2.foo()"
print NamespaceFactory(3).C().bar() # "version_3.bar()"

import sys, inspect
namespace = NamespaceFactory(int(sys.argv[1]))
# print the __mro__ of each 'inner' class
for name,cls in inspect.getmembers(namespace,
                                   inspect.isclass):
#for name, cls in (t for t in namespace.__dict__.items() if isinstance(t[1], type)):
    print cls
    for ancestor in cls.__mro__:
        print "\t", ancestor

#======================================================
---------------------------------------------------------------------------------

Run:

[ 5:33] C:\pywk\clp\sakkis\meta>py24 vermeta.py 3
version_2.foo()
version_3.bar()
<class 'version_3.A'>
        <class 'version_3.A'>
        <class 'version_2.A'>
        <class 'version_1.A'>
        <type 'object'>
<class 'version_3.B'>
        <class 'version_3.B'>
        <class 'version_2.B'>
        <class 'version_1.B'>
        <class 'version_3.A'>
        <class 'version_2.A'>
        <class 'version_1.A'>
        <type 'object'>
<class 'version_3.C'>
        <class 'version_3.C'>
        <class 'version_2.C'>
        <class 'version_1.C'>
        <class 'version_3.B'>
        <class 'version_2.B'>
        <class 'version_1.B'>
        <class 'version_3.A'>
        <class 'version_2.A'>
        <class 'version_1.A'>
        <type 'object'>
<type 'type'>
        <type 'type'>
        <type 'object'>

Regards,
Bengt Richter



More information about the Python-list mailing list