0

Assume a class like this, where attribute x has to be either an integer or a float:

class foo(object):
    def __init__(self,x):
        if not isinstance(x,float) and not isinstance(x,int):
            raise TypeError('x has to be a float or integer')
        else:
            self.x = x

Assigning a non-integer and non-float to x will return an error when instantiating the class:

>>> f = foo(x = 't')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in __init__
TypeError: x has to be a float or integer

But the direct assignment of x does not return any errors:

>>> f = foo(x = 3)
>>> f.x = 't'
>>> 

How can I make python raise an error in the latter case?

user3076813
  • 499
  • 2
  • 6
  • 13
  • 1
    What does "I cannot think of a clever way of doing it" mean? Why must the solution be "clever"? – Bryan Oakley Oct 26 '15 at 17:29
  • OK. Now, I see that the solution was straightforward. I was initially thinking of requiring the user to use a function set_x in order to set the value of x, instead of a direct assignment, i.e., f.set_x(x = 't') instead of f.x = 't'. This way all the checks on x are done within set_x. But I wasn't sure how check for a direct assignment in __setattr_. – user3076813 Oct 26 '15 at 17:59

2 Answers2

0

You could use the Descriptor protocol, although the syntax is a little bit more complicated:

from types import IntType, LongType, FloatType
AllowedTypes = IntType, LongType, FloatType


class NumberDescriptor(object):
    def __init__(self, name):
        self._name = name

    def __set__(self, instance, value):
        if not isinstance(value, AllowedTypes):
            raise TypeError("%s must be an int/float" % self._name)
        instance.__dict__[self._name] = value

class A(object):
    x = NumberDescriptor("x")
    def __init__(self, x):
        self.x = x

if __name__ == "__main__":
    a1 = A(1)
    print a1.x
    a2 = A(1.4)
    print a2.x
    #a2 = A("1")
    a2.x = 4
    print a2.x
    a1.x = "2"
    print a1.x
CristiFati
  • 38,250
  • 9
  • 50
  • 87
-1

Use a property:

class Foo(object):

    def __init__(self, x):
        self.x = x

    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, x):
        if not isinstance(x,float) and not isinstance(x,int):
            raise TypeError('x has to be a float or integer')
        self._x = x

If you find yourself needing to do this a lot you might want to look into Traits or Traitlets.

Bi Rico
  • 25,283
  • 3
  • 52
  • 75
  • 1
    A problem with all these methods is that they work fine if x is a single value, however, if they may fail if x is supposed to be a "list" of integers and floats. For example, if f = foo(x = [1,2,3]), then f.x = ['1',2,3] will return an error while f.x[0] = '1' will not return any errors. – user3076813 Dec 18 '15 at 06:59