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