1

I have been learning python from no long time ago. But nearly at the beginning I stumbled on simple question: how to set a restriction (limitation) on object value or object's properties without making a difficult class description. There is no problem with python's unstrict typification. I search for the methods to control not only the variable types, but the variable value diapasons also. For example let creating the classical educational class - "Circle". A Circle - object should have three properties: two origin coordinates (some x, y) and a radius, in that (in our human visible world) x and y should be real numerical, a radius should be real positive numerical. I somehow solved this task, correspnding method of "Circle" class below:

def __setattr__(self,name,value):
    def setting_atr(name,value):
        if isinstance(value,(float,int)):
            if name=='radius' and value<0:
                raise ValueError("Attribute 'radius' should be no negative")
            self.__dict__[name]=value
        else:
            raise TypeError("Attribute should be numerical")
    if len(self.__dict__)<3: #number of object attribute
        return setting_atr(name,value)
    elif name not in self.__dict__:
        raise AttributeError("'Circle' object has no such attribute")
    else:
        return setting_atr(name,value)

Nevertheless I utterly displeased with way I have done that. The code is huge for so small conception, and it's almost no reusable (I know about the descriptors - it will give the near same result). I would prefer some declarative stile. When I had been starting search for an alternative my first thought was to add my own operator. But after hard googling I realized that it impossible to make that in python itself (if I'm still wrong - please correst me). Such as I only newbie, and entering in python developers team or forked python ( :)) is no way for me, I gave up for this thoughts. The another possibly path in declarative stile is writing something like:

radius=radius if isinstance(radius,(float,int)) else raise TypeError("Attribute should be numerical")
radius=radius if radius>=0 else raise ValueError("Attribute 'radius' should be no negative")

of course it not work, because we haven't ability to use 'raise' by that. I should be able calling errors object as function to brought it for reality. The simplest way to do that it wrote the error-raised function like this:

def SomeError(exception=Exception, message="Something don't well"):
if isinstance(exception.args,tuple):
    raise exception
else:
    raise exception(message)

So it allow to write:

radius=radius if radius>=0 else SomeError(ValueError("Attribute 'radius' should be no negative"))

I also thought about followed form:

@dfValueError(['isinstance(radius,(float,int))'],"Attribute should be numerical")
@dfTypeError(['radius>=0'],"Attribute 'radius' should be no negative")
def fun_radius(x)
    return x

The small problem with this approach is in the more complex and less informatively evident error outing print. At the first the error message lead to the fake error raised function, the place where "oops" actually occurred only in second lines.

My goal is to make possibility raise exception from lambda functions and code lines like has showed above, to make code more evident. To do this I should create my own exceptions class with call method implemented. Problem: inherited class didn't see itself in own name spase, so method

__call__(self):
    raise self #yes ha-ha

give in result:

TypeError: __call__() missing 1 required positional argument: 'self'

So my question is simple: is it possible to make error object be callable? If yes, what I should read, to do it. And also, if someone solve this small problem, I would be happy. P.S. I know samples (about radius variable declaration) showed above can't be implemented in this "circle" class as is. I give it only as example to show my thoughts track. Please, don't pick on it.

Afterword (by Marat) Thank very much who wrote and voted in this topic. After readed answers, I thought very long, and finaly raised myself to add some augment to this topic (even if it entail some risk to infringe rules). For the first. Some people have wrote that we dont't need care about errors at the object creation state, and even no need to control variable types at all (inasmuch as it no has implemented in python by default). Although I have been no long time in object oriented programming, before that I had (and still have) been work on calculation a technical processes and devices. So I'm very confident about this words: "Possible error should be localized as soon as possible (I didn't say improved)". Many algorithms for calculation a real physical processes use iterations and recursions so if you slip out (for later handle) some occurred mistake it could brought a hard detectable error in final result. So, there is python, from this point of view. Python's "Duck Typing" is great for quick prototyping and simplifying applied programming. But "Duck Typing" is not a religion demand. Nevertheless Python's unstrict typification it isn't a bizarre thing to add a type control, if you think you need that. But the style which Python impose to perform it differ from the style in the static typification languages as C++, Java and etc. In Python we can control the object only in some "control points". This guide us to the "System Oriented Programming" principles (Therm is mine, but I didn't check if it exist in press, so it may has differ meaning from the same of another authors, and what I mean may also has another name). We should (if will) set the "control points" at the inlet and outlet of the elements of (main) system. What is for the system elements? It should be any object who has own name space and can interact with outside as function (by methods also). The simplest version of inlet controlling for function element is:

def func(a,b,c):
    #just like this
    a=int(a)
    b=str(b)
    c=list(c)

More comlexity and interesting approach shown here: Type-checking decorator for Python 2

Separately, about method that was suggested by my co-author. "Property" decorator use the same methods as descriptor, so it emulate object attributes by class methods. This mode should be used carefully because in python the objects of same class are shared the class name space. I let myself rewrote the same programm that have wrote by @Izkata with __setattr__ (again):

filter_function_for_radius=lambda value: value if value>0 else SomeError(ValueError, 'Radius cannot be negative')
class Circle(object):
    def __init__(self, x=0, y=0, radius=1):
        self.x = x
        self.y = y
        self.radius =radius
    def __setattr__(self,name,value):
        if name=='radius':
            value=filter_function_for_radius(value)
        self.__dict__[name]=value

...and if you need some another (perhaps complex or negative) radius for some hipothetical circle you don't need to inherit or redefine the class, just "change filter". But that advantage of function style may be obtain only by using __setattr__ or by creating own metaclass. And once more thank you, for answering.

6
  • Why you would like to prevent radius being negative in the first place? I think that a negative radius should generate an error when used, not when set. Also checking values to be float or int is quite ugly... why you care about that? Dec 29, 2013 at 8:57
  • 3
    About your actual question, I don't understand it. Why are you saying "I should be able calling errors object as function"? Dec 29, 2013 at 9:00
  • 2
    "I think that a negative radius should generate an error when used, not when set.": I disagree: an error condition should be caught as soon as possible.
    – Giorgio
    Dec 29, 2013 at 9:05
  • Isn't it simplify control of objects if we prevent the one absurd at creation step, at using phase it may be hard to find and improve a mistake 2) "I should be able calling errors object as function" because in current stage I can't use error "raising" in lambda funtion and declarative stile programming like above
    – Marat
    Dec 29, 2013 at 9:33
  • 3
    If you want type-safety, better use a type-safe language. In a weakly typed language like Python it will always need additional code to implement type checkings by yourself.
    – Doc Brown
    Dec 29, 2013 at 12:11

4 Answers 4

0

Here is some code of mine without the TypeError: __call__() missing 1 required positional argument: 'self'. How do you create this error?

>>> class X(Exception):
    def __call__(self):
        raise self


>>> x = X()
>>> x()

Traceback (most recent call last):
  File "<pyshell#5>", line 1, in <module>
    x()
  File "<pyshell#3>", line 3, in __call__
    raise self
X
>>> class Other(X):
    pass

>>> o = Other()
>>> o()

Traceback (most recent call last):
  File "<pyshell#12>", line 1, in <module>
    o()
  File "<pyshell#3>", line 3, in __call__
    raise self
Other
4

It looks like you're trying to reinvent python's property decorator. There's a good question on StackOverflow about what use they are, and the top answer has exactly what you're trying to do here: Real world example about how to use property feature in python?

Lets go back to the beginning, and use properties on your Circle instead:

A Circle - object should have three properties: two origin coordinates (some x, y) and a radius, in that (in our human visible world) x and y should be real numerical, a radius should be real positive numerical.

Initially, a basic Circle with no restrictions might look something like this:

class Circle(object):
   def __init__(self, x=0, y=0, radius=1):
      self.x = x
      self.y = y
      self.radius = radius

Now checking that something "is numeric" goes against python's philosophy of duck typing, and as shown in the other answers, you'll probably get it wrong. And besides, the instant you try to use them in a math equation, python is going to raise a TypeError anyway.

So this is how you might use property to keep radius from being negative, without having to futz around with attribute names inside __setattr__:

class Circle(object):
   def __init__(self, x=0, y=0, radius=1):
      self.x = x
      self.y = y
      self.radius = radius

   @property
   def radius(self):
      return self._radius

   @radius.setter
   def radius(self, radius):
      if radius < 0:
         raise ValueError('Radius cannot be negative')

      self._radius = radius
1

Generally, type checking in Python is going to be ugly and require lots of extra code because Python isn't meant to be type checked. The general mantra with Python is (I have found): trust but verify.

What this means is that you should code mostly without type checking and let the user put in whatever values they want. If I want to make a circle that holds the value "hello world" as its radius, why not?

I should expect, however, that whenever I try to, let's say, scale this circle up the circle should throw an error. Probably a typerror because somewhere your code tried to use my str as an int or float. However, this is on the user.

You might ask why anyone would ever do this. Here's a nice example.

In a statictly typed language, like Haskell, we'd do something like this:

data Point = Point { x :: Int, y :: Int }
data Circle = Circle { c :: Point, r :: Int }

Really, if we wanted full support we could replace the Int with a Num a => a but that requires RankNTypes and I don't want to get in to all that.

Now Point and Circle are datatypes that hold precisely the information we want and don't allow us to do anything silly. We can still make r negative, but we can't really defend against that easily.

If you want to fake something similar in Python you can do:

NUMBER = (float, int, long)

def check_type(value, types, error):
   if isinstance(value, types):
       return True
   raise TypeError(error)

class Point(object):
    __slots__ = ['x', 'y']
    def __init__(self, x, y):
        if check_type(x, NUMBER, "x should be a number."): 
            self.x = x
        if check_type(y, NUMBER, "y should be a number"):
            self.y = y

 class Circle(object):
     __slots__ = ['c', 'r']
     def __init__(self, c, r):
         if check_type(c, Point, "c must be a Point"):
             self.c = c
         if check_type(x, NUMBER, "r must be a number"):
             self.r = r

This is the cleanest way I can think to do it, and it's not very clean at all. Basically, what you're doing is abstracting away the type checking code into a function and supplying it with all the information. If you're looking for safety, this probably isn't the way to go anyway, because now the burden is just on the developer to provide all the right types.

For instance, in your original code you only checked for instances of int and float. However:

>>> x = 100000000000000000000000000000000000000000000
>>> isinstance(x, (float, int))
False
>>> type(x)
<type 'long'>

Since you failed to accommodate this small idiosyncrasy, your end user could end up with unfixable bugs in their code because you provided the wrong types for checking. In a more type-oriented language, like Haskell from earlier, you can solve this with declarations like Num a => a. However, you can't do that in Python because Python was never meant for this kind of work.

There's even a bug in my code! What if the user wanted to have a circle in the complex plane instead of the real plane? I failed to add complex to my NUMBER tuple, so that would fail type checking.

Consider the alternative (mentioned in @Paddy3118's answer):

Point = namedtuple('Point', 'x', 'y')
Circle = namedtuple('Circle', 'c', 'r')

Or, if you'd rather not have a separate Point type:

Circle = namedtuple('Circle', 'x', 'y', 'r')

This code is short, concise, and provides exactly the functionality you need. If you need to add more methods you can do:

import math

class Circle(namedtuple('Circle', 'x', 'y', 'r'):
   __slots__ = ()
   def scale(scale_factor):
       self._replace(r = r * scale_factor)

   @property
   def area(self):
       return (math.pi ** 2) * (self.r)

   @property
   def circumference(self):
       return math.pi * 2 * self.r

That last option is probably your best bet. It gives you the flexibility of Python's dynamic typing while still being a little bit conservative in what you can put in it.

1
  • I think we just are using different versions of python. In my 3.3 I have: a=111111111111111111111111111111111111111111111111111111111111111111.111111 >>> a.__class__ <class 'float'> >>> a=1000000000000000000000000000000000000000000000000000000000000000000000 >>> a.__class__ <class 'int'> But you are nevertheless right. I should was use abstact numerical class numbers.Number (from numbers module)
    – Marat
    Jan 25, 2014 at 6:16
0

THe trick is to try to not code too defensively. Try coding the minimum at first, maybe something like:

from collections import namedtuple
Circle = namedtuple('Circle', 'x, y, r')
c1 = Circle(0.0, 0.0, 1.0)

Leave it up to users of Circle to provide reasonable values - it usually works.

The docstring of class Circle above is fixed and pretty terse as it just says:

'Circle(x, y, r)'

You can make things clearer by, for example using more descriptive names:

Circle = namedtuple('Circle', 'center_x, center_y, radius')

Or by sub-classing and adding a more extensive docstring:

class Circle(namedtuple('Circle', 'x, y, r')):
    '''\
    Circle(x, y, r): Circle centered at point x,y of radius r
    '''

You need to ask yourself if you really need extensive type checking. It is normally not the first thing done when writing Python, and can seem counter-intuitive to programmers used to other programming languages.

1
  • You suggest very interesting approach, thank. Most amazing in it "p=Point(2,1)"; "p.__dict__" return "OrderedDict([('x', 2), ('y', 1)])" and still work as instance from class havin slot-attribute
    – Marat
    Jan 26, 2014 at 13:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.