On 29 Apr 2005 11:02:59 -0700, gry@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 -- http://mail.python.org/mailman/listinfo/python-list