module exports a property instead of a class -- Evil?

Bengt Richter bokr at oz.net
Sat Apr 30 03:02:30 EDT 2005


On 29 Apr 2005 11:02:59 -0700, gry at ll.mit.edu wrote:

>I often find myself wanting an instance attribute that can take on only
                                ^^^^^^^^
without checking deeply, are you not sharing state among all instance?
See following for an alternative way, allowing initialization by a
first assignment of a name sequence, followed by normal operation on
a per instance basis.

>a few fixed symbolic values. (This is less functionality than an enum,
>since there are no *numbers* associated with the values).  I do want
>the thing to fiercely object to assignments or comparisons with
>inappropriate values.  My implementation below gets me:
>
>.import mode
>.class C(object):
>.   status = mode.Mode('started', 'done', 'on-hold')
>.
>.c=C()
>.c.status = 'started'
>.c.status = 'stated': #Exception raised
>.if c.status == 'done': something
>.if c.status == 'stated': #Exception raised
>.if c.status.done: something  #simpler and clearer than string compare
>.if c.status < 'done': something # Mode arg strings define ordering
>
>I would appreciate comments on the overall scheme, as well as about the
>somewhat sneaky (I think) exporting of a property-factory instead of a
>class.  My intent is to provide a simple clear interface to the client
>class ("C" above), but I don't want to do something *too* fragile or
>confusing...
>(I'd also welcome a better name than "Mode"...)
>
>-------------------------- mode.py ----------------------
>class _Mode:  #internal use only, not exported.
>    def __init__(self, *vals):
>        if [v for v in vals if not isinstance(v, str)]:
>            raise ValueError, 'Mode values must be strings'
>        else:
>            self.values = list(vals)
>
>    def set(self, val):
>        if val not in self.values:
>            raise ValueError, 'bad value for Mode: "%s"' % val
>        else:
>            self.state = val
>
>    def __cmp__(self, other):
>        if other in self.values:
>            return cmp(self.values.index(self.state),
>self.values.index(other))
>        else:
>            raise ValueError, 'bad value for Mode comparison'
>
>    def __getattr__(self, name):
>        if name in self.values:
>            return self.state == name
>        else:
>            raise AttributeError, 'no such attribute: "%s"' % name
>
>
>def Mode(*vals): # *function* returning a *property*, not a class.
>    m = _Mode(*vals)
>    def _insert_mode_get(self):
>        return m
>    def _insert_mode_set(self, val):
>        m.set(val)
>    return property(_insert_mode_get, _insert_mode_set)
>-----------------------------------------------------------
>
Not tested beyond what you see ;-)

----< state.py >----------------------------------------------------------------------------------------------
# set up to validate state name strings
namerefcode = compile('a','','eval').co_code
non_name_chars = []
for c in (chr(i) for i in xrange(256)):
    try:
        if compile(c, '', 'eval').co_code != namerefcode:
            non_name_chars.append(c)
    except (SyntaxError, TypeError):
        non_name_chars.append(c)
non_name_chars = ''.join(non_name_chars)
idem = ''.join([chr(i) for i in xrange(256)])    
        
class Status(object):
    def __get__(self, inst, cls=None):
        if inst is None: return self
        if not '_state' in inst.__dict__:
            raise ValueError, 'Uninitialized instance state names'
        return inst._state
    def __set__(self, inst, value):
        if not hasattr(inst, '_state'): inst._state = self.State(*value)
        else: inst._state._setv(value)
    
    class State(object):
        def __init__(self, *names):
            for s in names:
               if s[:1].isdigit() or s!= s.translate(idem, non_name_chars):
                    raise ValueError, '%r is not a valid name'%s
            self._names = list(names)
            self._value = names[0]
        def _name_ck(self, name):
            if name not in self._names:
                raise AttributeError(
                    'Legal names are: %s -- not %r' % (', '.join(map(repr, self._names)), name)) 
        def __getattr__(self, attr):
            if attr.startswith('_'):
                return object.__getattribute__(self, attr)
            self._name_ck(attr)
            return self._value == attr
        def _setv(self, value):
            self._name_ck(value)
            self._value = value
        def __cmp__(self, other):
            self._name_ck(other)
            return cmp(self._names.index(self._value), self._names.index(other))
        def __str__(self):
            return self._value

def test():
    class C(object):
        status = Status()
    instances = [C(), C(), C()]
    nameslist = map(str.split, ['started done on_hold', 'one two three', 'UNK running stopped'])
    for i, (inst, names) in enumerate(zip(instances, nameslist)):
        inst.status = names
        inst.status = names[i]
        print 'Instance %s names: %r' %(i, inst.status._names)
    for i, inst in enumerate(instances):
        print i, 'names:',inst.status._names
        print i, 'current:', inst.status
        print i, '     '.join([' .%s? -> %s'% (name, getattr(inst.status, name)) for name in inst.status._names])
        print i, '     '.join(['==%s? -> %s'% (name, inst.status == name) for name in inst.status._names])
        print i, '     '.join([' >%s? -> %s'% (name, inst.status  > name) for name in inst.status._names])
        print i, '     '.join([' <%s? -> %s'% (name, inst.status  < name) for name in inst.status._names])
        try: inst.status = 'fini'
        except Exception, e:
            print 'Exception %s: %s' %(e.__class__.__name__, e)
    class D(object):
        hownow = Status()
    dinst = D()
    for bogus in ['this$', 'an#d', 'with space', '4digitstart', 'bad\x00']:
       try: dinst.hownow = ('verily', bogus, 'will', 'fail')
       except Exception, e:
           print 'Exception %s: %s' %(e.__class__.__name__, e)
    
if __name__ == '__main__': test()
--------------------------------------------------------------------------------------------------------------
Result:

[ 0:00] C:\pywk\clp>py24 state.py
Instance 0 names: ['started', 'done', 'on_hold']
Instance 1 names: ['one', 'two', 'three']
Instance 2 names: ['UNK', 'running', 'stopped']
0 names: ['started', 'done', 'on_hold']
0 current: started
0  .started? -> True      .done? -> False      .on_hold? -> False
0 ==started? -> True     ==done? -> False     ==on_hold? -> False
0  >started? -> False      >done? -> False      >on_hold? -> False
0  <started? -> False      <done? -> True      <on_hold? -> True
Exception AttributeError: Legal names are: 'started', 'done', 'on_hold' -- not 'fini'
1 names: ['one', 'two', 'three']
1 current: two
1  .one? -> False      .two? -> True      .three? -> False
1 ==one? -> False     ==two? -> True     ==three? -> False
1  >one? -> True      >two? -> False      >three? -> False
1  <one? -> False      <two? -> False      <three? -> True
Exception AttributeError: Legal names are: 'one', 'two', 'three' -- not 'fini'
2 names: ['UNK', 'running', 'stopped']
2 current: stopped
2  .UNK? -> False      .running? -> False      .stopped? -> True
2 ==UNK? -> False     ==running? -> False     ==stopped? -> True
2  >UNK? -> True      >running? -> True      >stopped? -> False
2  <UNK? -> False      <running? -> False      <stopped? -> False
Exception AttributeError: Legal names are: 'UNK', 'running', 'stopped' -- not 'fini'
Exception ValueError: 'this$' is not a valid name
Exception ValueError: 'an#d' is not a valid name
Exception ValueError: 'with space' is not a valid name
Exception ValueError: '4digitstart' is not a valid name
Exception ValueError: 'bad\x00' is not a valid name

Regards,
Bengt Richter



More information about the Python-list mailing list