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
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.