views:

101

answers:

3

I'm not clear how to pose this question. If I did, I'd probably be a lot closer to a solution.. I need some insight into inheritance.

I want to make a custom subtype of float. But I want the instance of this subtype to re-evaluate it's value before performing any of the normal float methods (__add__,__mul__, etc..). In this example it should multiply it's value by the global FACTOR:

class FactorFloat(float):
    # I dont think I want to do this:
##    def __new__(self, value):
##        return float.__new__(self, value)
    def __init__(self, value):
        float.__init__(self, value)
    # something important is missing..
    # want to do something with global FACTOR 
    # when any float method is called

f = FactorFloat(3.)
FACTOR = 10.
print f   # 30.0
print f-1 # 29.0
FACTOR = 2.
print f   # 6.0
print f-1 # 5.0

This is a just a sanitized example that I think gets my point across. I'll post a more complex "real" problem if necessary.

+1  A: 

What you're wanting to do is actually very hard. I do not know of any python software that subclasses a type such as float or int and then does mathematics with it. I think that perhaps there is a better way to accomplish what you're trying to achieve without using a subclass of float. You should investigate alternatives.

Jerub
+1  A: 

Here are a couple of methods to get your testcases passing

def __str__(self):
    return str(FACTOR*self)
def __sub__(self, other):
    return self*FACTOR-other

obviously you have to also implement __add__, __mul__, etc...

Having said that - what is your use case? This seems like a weird thing to want to do

gnibbler
My use case is a multi-axis interpolation using some global lookup values. I want to define, for example, air density as a simple float-like variable that is dependent upon global pressure and temperature. However, I would also like to interchangeably use actual floats for densities that are temperature and/or pressure independent (for example steel). I was hoping to avoid implementing __add__, __mul__, etc.., but it seems unavoidable. ..and probably easier than posting this question..
bpowah
+5  A: 
class FactorFloat(float):
    def _factor_scale(f):
        def wrapper(self, *args, **kwargs):
            scaled = float.__mul__(self, FACTOR)
            result = f(scaled, *args, **kwargs)
            # if you want to return FactorFloats when possible:
            if isinstance(result, float):
                result = type(self)(result/FACTOR)
            return result
        return wrapper

    def __repr__(self):
        return '%s(%s)' % (type(self).__name__, float.__repr__(self))

    __str__ = _factor_scale(float.__str__)
    __mul__ = _factor_scale(float.__mul__)
    __div__ = _factor_scale(float.__div__)
    __add__ = _factor_scale(float.__add__)
    __sub__ = _factor_scale(float.__sub__)


f = FactorFloat(3.)
FACTOR = 10.
print f   # 30.0
print f-1 # 29.0
FACTOR = 2.
print f   # 6.0
print f-1 # 5.0
print repr(f)

for:

30.0
29.0
6.0
5.0
FactorFloat(3.0)

EDIT:

In response to the question in the comment; making things slightly more general and automated, using a class decorator. I would not loop over dir(baseclass), but instead would explicitly list the methods I wished to wrap. In the example below, I list them in the class variable _scale_methods.

def wrap_scale_methods(cls):
    Base = cls.__base__
    def factor_scale(f):
        def wrapper(self, *args, **kwargs):
            scaled = Base.__mul__(self, FACTOR)
            result = f(scaled, *args, **kwargs)
            if isinstance(result, Base):
                result = type(self)(result/FACTOR)
            return result
        return wrapper
    for methodname in cls._scale_methods:
        setattr(cls, methodname, factor_scale(getattr(Base, methodname)))
    return cls

@wrap_scale_methods
class FactorComplex(complex):
    _scale_methods = '__str__ __mul__ __div__ __add__ __sub__'.split()
    def __repr__(self):
        return '%s(%s)' % (type(self).__name__, complex.__repr__(self)[1:-1])
Matt Anderson
Thanks. Just curious, is there a way to loop over dir(float) instead of defining `__mul__`, `__div__`, etc. individually? I ask because I might like to try this out on a numpy array instead of a float.
bpowah
See the edit to my answer.
Matt Anderson