Storing value with limits in object

J

Josip

I'm trying to limit a value stored by object (either int or float):

class Limited(object):
def __init__(self, value, min, max):
self.min, self.max = min, max
self.n = value
def set_n(self,value):
if value < self.min: # boundary check
self.n = self.min
if value > self.max:
self.n = self.max
else:
self.n = value
n = property(lambda self : self._value, set_n)

This works, except I would like the class to behave like built-in types, so
I can use it like this:

a = Limited(7, 0, 10)
b = math.sin(a)

So that object itself returns it's value (which is stored in a.n). Is this
possible?
 
D

David

I'm trying to limit a value stored by object (either int or float):

class Limited(object):
def __init__(self, value, min, max):
self.min, self.max = min, max
self.n = value
def set_n(self,value):
if value < self.min: # boundary check
self.n = self.min
if value > self.max:
self.n = self.max
else:
self.n = value
n = property(lambda self : self._value, set_n)

This works, except I would like the class to behave like built-in types, so
I can use it like this:

a = Limited(7, 0, 10)
b = math.sin(a)

So that object itself returns it's value (which is stored in a.n). Is this
possible?

Not with normal vars, because = is a rebinding operator in Python,
rather than assignment.

You can do (close to) the above with object properties.

David.
 
J

Josip

Not with normal vars, because = is a rebinding operator in Python,
rather than assignment.

You can do (close to) the above with object properties.

David.

Yes, but it's done with built-in types like int and float. I suspect I could
subclass from them and implement limits, but I would have to make
seperate class for each type. Can I override conversion methods like int()
and float() within my class?
 
D

David

Yes, but it's done with built-in types like int and float. I suspect I could
subclass from them and implement limits, but I would have to make
seperate class for each type. Can I override conversion methods like int()
and float() within my class?

I think I may have misread your original post.

ints and floats are internal , immutable types, with some class
goodness on top (so you can treat them like objects to a degree,
subclass from them, etc). Python's interpreter has built-in logic
which 'knows' how to use ints and floats variables, without calling
their special "__" methods. Python would be a lot slower if it worked
this way.

To do exactly what you want, you'd need to add a new internal numeric
type to Python.

You can subclass from float, and redefine __float__ and __int__, but
those will only be called when your code actually calls the builtin
float() and int() builtins, eg:

import math

class Float(float):
def __float__(self):
raise NotImplementedError

a = Float(1)
print math.sin(a)

# Outputs 0.841470984808

a = Float(1)
print math.sin(float(a))

# Throws a NotImplementedError exception

There is no way (afaik) for an object to tell Python to call one of
it's methods to get a reference, or 'value' to the object (if there
was, it would make things really complicated). In Python you generally
need to update the logic used during a lookup to get that effect (ie,
in a.b.c, you can customise the a.b lookup, or the a.b.c lookup, but
not the a lookup itself).

In theory you could hack Python's internal locals or globals
dictionary so that it did something unusual while looking up your
object. But in practice this doesn't work, because the returned
objects (when you call globals() or locals()) attributes are readonly.
Probably because those internal lookup dicts are implemented in
optimized C not Python, and the C implementation doesn't let you
redefine it's internals via the Python interface.

David.
 
J

Josip

In theory you could hack Python's internal locals or globals
dictionary so that it did something unusual while looking up your
object. But in practice this doesn't work, because the returned
objects (when you call globals() or locals()) attributes are readonly.
Probably because those internal lookup dicts are implemented in
optimized C not Python, and the C implementation doesn't let you
redefine it's internals via the Python interface.

David.

I'll settle for implementing the __call__() method to return the value as I
have
no intention to mess around with Python's internal mechanisms.

Thanks a lot for your deep insight.
 
G

George Sakkis

I'm trying to limit a value stored by object (either int or float):

class Limited(object):
def __init__(self, value, min, max):
self.min, self.max = min, max
self.n = value
def set_n(self,value):
if value < self.min: # boundary check
self.n = self.min
if value > self.max:
self.n = self.max
else:
self.n = value
n = property(lambda self : self._value, set_n)

This works,

I bet you didn't even try this, unless your definition of "works"
includes a "RuntimeError: maximum recursion depth exceeded". Here's a
a working version:

class Limited(object):
def __init__(self, value, min, max):
self.min, self.max = min, max
self.n = value

n = property(lambda self : self._value,
lambda self,value:
self.__dict__.__setitem__('_value',
max(self.min, min(value,
self.max))))

def __int__(self): return int(self._value)
def __float__(self): return float(self._value)


a = Limited(11, 0, 9)
print float(a)
import math
print math.sqrt(a)
except I would like the class to behave like built-in types, so
I can use it like this:

a = Limited(7, 0, 10)
b = math.sin(a)

So that object itself returns it's value (which is stored in a.n). Is this
possible?

For (most) math.* functions it suffices to define __float__, so the
above works. For making it behave (almost) like a regular number,
you'd have to write many more special methods: http://docs.python.org/ref/numeric-types.html.
Here's a possible start:

import operator

class Limited(object):
def __init__(self, value, min, max):
self.min, self.max = min, max
self.n = value

n = property(lambda self : self._value,
lambda self,value:
self.__dict__.__setitem__('_value',
max(self.min, min(value,
self.max))))

def __str__(self): return str(self.n)
def __repr__(self): return 'Limited(%r, min=%r, max=%r)' %
(self.n, self.min, self.max)

def __int__(self): return int(self._value)
def __float__(self): return float(self._value)

def __add__(self, other): return self._apply(operator.add, self,
other)
def __sub__(self, other): return self._apply(operator.sub, self,
other)
# a few dozens more methods follow ...

def __radd__(self, other): return self._apply(operator.add, other,
self)
def __rsub__(self, other): return self._apply(operator.sub, other,
self)
# a few dozens more methods follow ...

@classmethod
def _apply(cls, op, first, second):
minmax = None
if isinstance(first, cls):
minmax = first.min,first.max
first = first._value
if isinstance(second, cls):
if minmax is None:
minmax = second.min,second.max
second = second._value
return cls(op(first,second), *minmax)


a = Limited(11, 0, 9)
print a+1
print 1+a
print a-1
print 1-a


Needless to say, this is almost two orders of magnitude slower than
the builtin numbers, so you'd better not use it for any serious number
crunching.

HTH,
George
 
J

Josip

Why not make it a function?
function assignLimited(value, vmin, vmax):
value = max(vmin, value)
value = min(vmax, value)
return value


a = assignLimited(7, 0, 10)


Seems like it solves your problem relatively cleanly.
Note: I also removed min/max variables because they would mask the
built-in min/max functions.

-Larry

Yes, the simple solution is often the best. Still, I'm going for object
oriented solution because I want the value and it's limits to be kept
together as I'll have many such values with different limits. Storing all
the limits in caller namespace is not really an option.
 
J

Josip

Why not make it a function?
function assignLimited(value, vmin, vmax):
value = max(vmin, value)
value = min(vmax, value)
return value


a = assignLimited(7, 0, 10)


Seems like it solves your problem relatively cleanly.
Note: I also removed min/max variables because they would mask the
built-in min/max functions.

-Larry

Yes, the simple solution is often the best. Still, I'm going for object
oriented solution because I want the value and it's limits to be kept
together as I'll have many such values with different limits. Storing all
the limits in caller namespace is not really an option.
 
J

Josip

I bet you didn't even try this, unless your definition of "works"
includes a "RuntimeError: maximum recursion depth exceeded". Here's a
a working version:

Actually, the version I'm using is somewhat bigger. I removed docstrings and
recklessly stripped away some methods to make it shorter concise and
incorrect.
For (most) math.* functions it suffices to define __float__, so the
above works. For making it behave (almost) like a regular number,
you'd have to write many more special methods:
http://docs.python.org/ref/numeric-types.html.
Here's a possible start:
(...)

Yes, this is very close to what I was looking for. It implements all the
functionality except asssigning values. And speed is not an issue for my
application.
Thanks.
 
L

Lie

Still, I'm going for object
oriented solution because I want the value and it's limits to be kept
together as I'll have many such values with different limits.

In that case, "overriding assignment" makes no sense since if an
operation is done on two values that have two different limits, what
would happen? Take the first's or second's limits, or interpolate the
new limit by some magic function? A much better solution would be for
operation to always return plain vanilla int, and we set limits
explicitly.
Storing all
the limits in caller namespace is not really an option.

You want to store limits in the object too?

Try this: (_LimitedInt inherits from int, so operator overloading is
unnecessary)

###
#!/usr/bin/env python

class _LimitedInt(int):
class InvalidLimitsError(Exception): pass

def __init__(self, value, base = 10):
int.__init__(value, base)

def setlimits(self, lim):
''' Set the limits and if value is not within limit,
raise ValueError

The lim argument accepts:
- A _LimitedInt instance, from which to copy the limits
- A two-tuple, which specifies the limits i.e. (min, max)

If lim isn't those or lim[0] > lim[1], raise
InvalidLimitsError

Accepting _LimitedInt instance is just for convenience
'''

if isinstance(lim, _LimitedInt):
lim = lim.limits

try:
self.min, self.max = [int(x) for x in lim]
if self.min > self.max: raise ValueError
except (ValueError, TypeError):
raise self.InvalidLimitsError, ('limit = %s' % str(lim))

if not (self.min < self < self.max):
raise ValueError, \
('val = %s, min = %s, max = %s' % \
(self, self.min, self.max))

def getlimits(self):
return (self.min, self.max)

limits = property(getlimits, setlimits)

def lint(value, limits, base = 10):
if base != 10:
ret = _LimitedInt(value, base)
else:
ret = _LimitedInt(value)

ret.limits = limits
return ret

### END OF REAL CODE ###
### THE REST ARE JUST TESTING CODES ###

if __name__ == '__main__':
print 'Instantiating lint...'
a = lint(50, (0, 200)) # Limit explicitly specified
b = lint(150, a) # Copy the limits of a
c = lint(a, (0, 1000)) # Value = a, Limit = (0, 1000)
d = lint(a, c) # Value = a, Limit = c
print

print 'Printing the value and the limits...'
print a, a.limits
print b, b.limits
print c, c.limits
print d, d.limits
print

print 'Changing limits'
print 'Note: lint is partially immutable,'
print ' its limits is mutable,'
print ' while its value is immutable'
a.limits = (0, 300)
b.limits = a
print a, a.limits
print b, b.limits
print

print '"Changing" values'
a = lint(b, a)
print a, a.limits
print

print 'Operations...'
e = lint(a + b - c * d + 100, (-10000, 1000))
f = a + b - c * d ## Operation returns plain integer
g = lint(f, (-100000, 100000)) ## Always recast result of operator
print e, e.limits, type(e) ## This is an int, not lint
print f, type(f) ## BEWARE: f is integer, it has no
limits
print g, g.limits, type(g)

## INVALIDS
# a = lint(100, (1000, 100000))
# a = lint(100, 'ab')
# a = lint(100, (10000, 0))
# a = lint(100, 10)
# a = lint(100, (10, 1000, 10000))###
 
L

Lie

Still, I'm going for object
oriented solution because I want the value and it's limits to be kept
together as I'll have many such values with different limits.

In that case, "overriding assignment" makes no sense since if an
operation is done on two values that have two different limits, what
would happen? Take the first's or second's limits, or interpolate the
new limit by some magic function? A much better solution would be for
operation to always return plain vanilla int, and we set limits
explicitly.
Storing all
the limits in caller namespace is not really an option.

You want to store limits in the object too?

Try this: (_LimitedInt inherits from int, so operator overloading is
unnecessary)

###
#!/usr/bin/env python

class _LimitedInt(int):
class InvalidLimitsError(Exception): pass

def __init__(self, value, base = 10):
int.__init__(value, base)

def setlimits(self, lim):
''' Set the limits and if value is not within limit,
raise ValueError

The lim argument accepts:
- A _LimitedInt instance, from which to copy the limits
- A two-tuple, which specifies the limits i.e. (min, max)

If lim isn't those or lim[0] > lim[1], raise
InvalidLimitsError

Accepting _LimitedInt instance is just for convenience
'''

if isinstance(lim, _LimitedInt):
lim = lim.limits

try:
self.min, self.max = [int(x) for x in lim]
if self.min > self.max: raise ValueError
except (ValueError, TypeError):
raise self.InvalidLimitsError, ('limit = %s' % str(lim))

if not (self.min < self < self.max):
raise ValueError, \
('val = %s, min = %s, max = %s' % \
(self, self.min, self.max))

def getlimits(self):
return (self.min, self.max)

limits = property(getlimits, setlimits)

def lint(value, limits, base = 10):
if base != 10:
ret = _LimitedInt(value, base)
else:
ret = _LimitedInt(value)

ret.limits = limits
return ret

### END OF REAL CODE ###
### THE REST ARE JUST TESTING CODES ###

if __name__ == '__main__':
print 'Instantiating lint...'
a = lint(50, (0, 200)) # Limit explicitly specified
b = lint(150, a) # Copy the limits of a
c = lint(a, (0, 1000)) # Value = a, Limit = (0, 1000)
d = lint(a, c) # Value = a, Limit = c
print

print 'Printing the value and the limits...'
print a, a.limits
print b, b.limits
print c, c.limits
print d, d.limits
print

print 'Changing limits'
print 'Note: lint is partially immutable,'
print ' its limits is mutable,'
print ' while its value is immutable'
a.limits = (0, 300)
b.limits = a
print a, a.limits
print b, b.limits
print

print '"Changing" values'
a = lint(b, a)
print a, a.limits
print

print 'Operations...'
e = lint(a + b - c * d + 100, (-10000, 1000))
f = a + b - c * d ## Operation returns plain integer
g = lint(f, (-100000, 100000)) ## Always recast result of operator
print e, e.limits, type(e) ## This is an int, not lint
print f, type(f) ## BEWARE: f is integer, it has no
limits
print g, g.limits, type(g)

## INVALIDS
# a = lint(100, (1000, 100000))
# a = lint(100, 'ab')
# a = lint(100, (10000, 0))
# a = lint(100, 10)
# a = lint(100, (10, 1000, 10000))###
 
L

Lie

#!/usr/bin/env python

## VERSION 2
##
## changelog:
## - Uses inheritance from _Limited
## - Added _LimitedLong and llong
## - limit choose between int, long, and float

class _Limited(object):
def setlimits(self, lim):
''' Set the limits and if value is not within limit,
raise ValueError

The lim argument accepts:
- An instance of _Limited, from which to copy the limits
- A two-tuple, which specifies the limits i.e. (min, max)

If lim isn't those or lim[0] > lim[1], raise
InvalidLimitsError

Accepting _Limited instance is just for convenience
'''

if isinstance(lim, _Limited):
self.min, self.max = lim.limits
else:
try:
self.min, self.max = lim
if self.min > self.max: raise ValueError
except (ValueError, TypeError):
raise self.InvalidLimitsError, ('limit = %s' %
str(lim))

if not (self.min < self < self.max):
raise ValueError, \
('val = %s, min = %s, max = %s' % \
(self, self.min, self.max))

def getlimits(self):
return (self.min, self.max)

limits = property(getlimits, setlimits)

class _LimitedInt(int, _Limited):
def __init__(self, value, base = 10):
int.__init__(value, base)

class _LimitedLong(long, _Limited):
def __init__(self, value, base = 10):
long.__init__(value, base)

class _LimitedFloat(float, _Limited):
def __init__(self, value):
float.__init__(value)


def lint(value, limits, base = None):
''' Always Creates _LimitedInt instance, allows the use of base
'''
if base:
ret = _LimitedInt(value, base)
else:
ret = _LimitedInt(value)

ret.limits = limits
return ret

def llong(value, limits, base = None):
''' Always Creates _LimitedLong instance, allows the use of base
'''
if base:
ret = _LimitedLong(value, base)
else:
ret = _LimitedLong(value)

ret.limits = limits
return ret

def lfloat(value, limits):
''' Always Creates _LimitedFloat instance
'''
ret = _LimitedFloat(value)

ret.limits = limits
return ret

def limit(value, limits):
''' Automatically choose between _LimitedInt, _LimitedLong,
or _LimitedFloat. Cannot use _LimitedInt's/Long's base
'''
if isinstance(value, (int, long)):
try:
ret = _LimitedInt(value)
except OverflowError:
ret = _LimitedLong(value)
elif isinstance(value, float):
ret = _LimitedFloat(value)

ret.limits = limits
return ret
 
J

Josip

Thanks alot. I'm going to use this with few modifications to tailor it to my
needs.
Thumbs up!
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,794
Messages
2,569,641
Members
45,354
Latest member
OrenKrause

Latest Threads

Top