How do I do this without class methods ?

Alex Martelli aleaxit at yahoo.com
Wed Apr 25 05:00:06 EDT 2001


"Jacek Generowicz" <jmg at ecs.soton.ac.uk> wrote in message
news:g0zod5quh4.fsf at scumbag.ecs.soton.ac.uk...
>
> I have a class hierarchy. The classes in the hierarchy differ in
> (amongst other things) the value of a class variable. Furthermore, I
> want to be able to select the value of said class variable from a
> range of allowed values for each of the classes. I've cooked up a toy
> hierarchy to illustrate the point, below.
>
> My problem is that it seems simple enough to do with class methods,
> but I don't seem to be able to find a way around their absence. What's
> the pythonic way to do this ?

I think a regular function taking the class object as an argument
is simplest.


> class woderwick:
>     bwian = [ 'zero', 'one', 'two', 'many' ]
>     wodger = bwian[0] # default value
>     def __init__ ( self ):
>         print self.wodger
>     # This only affects the particular instance; no use
>     def welease_bwian ( self, N ):
>         self.wodger = self.bwian[N]

So recode it outside the class body instead:

def welease_bwian(klass, N):
    klass.wodger = klass.bwian[N]


> class rodrigo(woderwick):
>     bwian = [ 'cero', 'uno', 'dos', 'demasiados' ]
>
> class roderich(woderwick):
>     bwian = [ 'gar nichts', 'eins', 'zwei', 'viele' ]
>
> class roderik(roderich): # :-)
>     bwian = [ 'geen bal', 'een', 'twee', 'te veel' ]
>
> class rafal(woderwick):
>     bwian = [ 'figa z makiem', 'raz', 'dwa', 'kupa' ]
>
> # And so on ad tedium; ie I REALLY want to write welease_bwian only
> # once for the whole hierarchy. Not too much to ask, is it? as it
> # performs exactly the same function in all cases.

Makes sense to me.


> # Now I want to be able to say
> a = woderwick() # wnat zero, get zero
> b = rodrigo() # want cero, get zero

If you want each self.wodger of an instance to be
initialized depending on its class you may need to
code __init__ differently.  For example,
    def __init__(self):
        self.wodger = self.bwian[0]
or something -- I'm not too sure what you want
to accomplish here.  This sets the _instance_
attribute, not the _class_ one.


> # Then I want to be able to set wodger to a given element of the
> # corresponding bwian, for the whole class.
>
> # This only works, for the base class (no surprise)
> def welease_bwian( N ):
>     woderwick.wodger = woderwick.bwian[N]

And this will work for every class:

def welease_bwian(klass, N):
    klass.wodger = klass.bwian[N]

but it sets the _class_ attribute, not the _instance_
one if any.

If attributes with the same name are set on both
instance and class, and you access that name through
an instance, the class attribute will not be fetched
(unless you explicitly ask for it, self.__class__.wodger).

So maybe what you want for initialization is also
some test or manipulation on self.__class__...?


> welease_bwian( 1 )
>
> a = woderwick() # Gives one, as required
> b = rodrigo() # Gves one, rather than uno
>
> # This only affects the temporary instance, hence useless
> rodrigo().welease_bwian( 2 )

If you want the call on an instance to affect the
whole class, you can (delegate to the global
function welease_bwian passing as argument
the self.__class__) but it violates the principle
of least astonishment to many readers.  Maybe
    welease_bwian(rodrigo, 2)
would be preferable.

> a = woderwick() # get one, want two
> b = rodrigo()   # get one, want dos
>
> Suggestions welcome.

Clarifying the specs might help.  If as it seems
you're always accessing attribute .wodger from
instances, never classes, and only ever wants to
set it on a per-class basis instead (so all the
instances extant get affected) maybe what you
need is a different approach:

class woderwick:
    bwian = [ 'zero', 'one', 'two', 'many' ]
    _wodger_index = 0 # default index
    def __getattr__(self, name):
        if name=='wodger':
            return self.bwian[self._wodger_index]
        raise AttributeError, name
    def __init__ ( self ):
        print self.wodger
    # Affect class through instance...
    def welease_bwian ( self, N ):
        self.__class__._wodger_index = N

But if you want an instance's .wodger to be
"fixed" at the time it's created, NOT following
further gyrations of its class's settings, then
you DO want to set self.wodger at __init__
time.  Very different semantics.  So:

class woderwick:
    bwian = [ 'zero', 'one', 'two', 'many' ]
    _wodger_index = 0 # default index
    def __init__ ( self ):
        self.wodger = self.bwian[self._wodger_index]
        print self.wodger
    # Affect class through instance...
    def welease_bwian ( self, N ):
        self.__class__._wodger_index = N

In either case, I think a function outside
of the class would be a better design for
welease_bwian, rather than affecting the
whole class (maybe only for instances to
be created in the future, in the second
approach) through a call on an instance.
But there might well be cases where such
state-sharing WOULD be exactly right.


Alex






More information about the Python-list mailing list