Use a Descriptor (EDIT: Not a single decorator) fo

2019-06-08 15:19发布

问题:

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

回答1:

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.



回答2:

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.