views:

96

answers:

2

Hi,

Python 2.5.4. Fairly new to Python, brand new to decorators as of last night. If I have a class with multiple boolean attributes:

class Foo(object):
    _bool1 = True
    _bool2 = True
    _bool3 = True
    #et cetera

    def __init__():
        self._bool1 = True
        self._bool2 = False
        self._bool3 = True
        #et cetera

Is there a way to use a single decorator to check that any setting of any of the boolean attributes must be a boolean, and to return the boolean value for any requested one of these variables?

In other words, as opposed to something like this for each attribute?

def bool1():
    def get_boo1():
        return self._bool1
    def set_bool1(self,value):
        if value <> True and value <> False:
            print "bool1 not a boolean value. exiting"
            exit()
        self._bool1=value
    return locals()
bool1 = property(**bool1())

#same thing for bool2, bool3, etc...

I have tried to write it as something like this:

def stuff(obj):
    def boolx():
        def fget(self):
            return obj
        def fset(self, value):
            if value <> True and value <> False:
                print "Non-bool value" #name of object???
                exit()
            obj = value
        return locals()
    return property(**boolx())

bool1 = stuff(_bool1)
bool2 = stuff(_bool2)
bool3 = stuff(_bool3)

which gives me:

File "C:/PQL/PythonCode_TestCode/Tutorials/Decorators.py", line 28, in stuff
    return property(**boolx())
TypeError: 'obj' is an invalid keyword argument for this function

Any pointers on how to do this correctly?

Thanks,

Paul

+2  A: 

Two important things.

First, "class-level" attributes are shared by all instances of the class. Like static in Java. It's not clear from your question if you're really talking about class-level attributes.

Generally, most OO programming is done with instance variables, like this.

class Foo(object):
    def __init__():
        self._bool1 = True
        self._bool2 = False
        self._bool3 = True
        #et cetera

Second point. We don't waste a lot of time validating the types of arguments.

If a mysterious "someone" provides wrong type data, our class will crash and that's pretty much the best possible outcome.

Fussing around with type and domain validation is a lot of work to make your class crash in a different place. Ultimately, the exception (TypeError) is the same, so the extra checking turns out to have little practical value.

Indeed, extra domain checking can (and often does) backfire when someone creates an alternate implementation of bool and your class rejects this perfectly valid class that has all the same features as built-in bool.

Do not conflate human-input range checking with Python type checking. Human input (or stuff you read from files or URI's) must be range checked, but not not type checked. The piece of the application that does the reading of the external data defines the type. No need to check the type. There won't be any mysteries.

The "what if I use the wrong type and my program appears to work but didn't" scenario doesn't actually make any sense. First, find two types that have the same behavior right down the line but produce slightly different results. The only example is int vs. float, and the only time is really matters is around division, and that's taken care of by the two division operators.

If you "accidentally" use a string where a number was required, your program will die. Reliably. Consistently.

S.Lott
Thanks, I had originally tried to write it just using the instance variables in the __init__ statement, but the stuff() method had bombed out saying it didn't know anything about 'self' when I had written something like bool1 = self.stuff(self._bool1)... it had not gotten to the __init__ method yet. So that messed with my head! :-)
TallPaul
The only concern I have is NOT checking attribute values and not writing tight enough code so that an error in data entry or coding is NOT caught and the program completes (incorrectly) without an exception... hence the data validation! :-) To me that can be worse than the program dying... running to completion when it should not have, and having side effects.
TallPaul
+1: Pointing out the conceptual flaws instead of just giving a solution.
nikow
@Tall: Sure, when there is a serious danger of data corruption or incorrect results then you should test, but that is seldom the case. Especially in your example I find it hard to imagine such a problem. So unless you have a VERY specific reason why this is a problem you should keep it simple an embrace duck typing. If you just try to write Java code in Python then you won't get anywhere.
nikow
@TallPaul: This is not C. Things don't "sort-of-work". In Python, if you've got the wrong type of data your program dies. Also, don't conflate user input range checking with ordinary API type checking. You must do human input range checking. Don't waste time on API type checking: if you make a mistake, your program WILL die. Reliably.
S.Lott
@S. Lott: "don't conflate user input range checking with ordinary API type checking. You must do human input range checking." That's really where I am trying to get to: checking input from anything or anyone outside the class, then internally using the underscored attributes ie, self._bool1.
TallPaul
@TallPaul: I'll repeat the admonition. Check numeric ranges. Do not check type. The *anything or anyone outside the class* path is a waste of time and money. Check numeric ranges only. Everything else will fail with an exception, no need to check prior to the failure. There's no "almost worked".
S.Lott
+3  A: 

You can try using a descriptor:

class BooleanDescriptor(object):
    def __init__(self, attr):
        self.attr = attr

    def __get__(self, instance, owner):
      return getattr(instance, self.attr)

    def __set__(self, instance, value):
      if value in (True, False):
        return setattr(instance, self.attr, value)
      else:
        raise TypeError


class Foo(object):
    _bar = False
    bar = BooleanDescriptor('_bar')

EDIT:

As S.Lott mentioned, python favors Duck Typing over type checking.

muhuk
I think I actually meant Descriptor when I typed Decorator! :-) Thanks for the nice example!
TallPaul
@TallPaul: If you meant descriptor, please update your question to say what you mean.
S.Lott
@S.Lott: Edited the title. Thanks!
TallPaul