[Chicago] Prevent access to method as attribute

Ken Schutte kenschutte at gmail.com
Wed Oct 17 13:41:12 CEST 2012


Along similar lines, you could try this:


class notAttribute(object):
    def __init__(self, func):
        self.func = func

    def __get__(self, obj, type = None):
        return self.__class__(self.func.__get__(obj, type))

    def __nonzero__(self):
        raise Exception("Don't check me as an attribute!  Call me first!")

    def __call__(self,*a,**kw):
        return self.func(*a,**kw)


Usage:

class A(object):

    @notAttribute
    def isValid(self):
        return False


a = A()

try:
    if a.isValid:
        print "(1) valid"
    else:
        print "(1) not valid"
except Exception, e:
    print "ERROR:", e

if a.isValid():
    print "(2) valid"
else:
    print "(2) not valid"


The magic bit is the __get__ which intercepts the attribute lookup and
creates a new object whose function is bound to the A instance.  So,
the isValid function can use 'self' in any way it would normally.  (it
could probably also use types.MethodType()).

This solution doesn't really prevent you from using isValid as an
attribute.  It prevents you of using that result in a boolean test
(which calls __nonzero__).  If you want to say an attribute can only
be used as a simple function call... that is maybe only possible with
real code introspection because attribute lookup and function call are
two separate steps.

Ken


On Tue, Oct 16, 2012 at 10:31 PM, Adam Forsyth <agforsyth at gmail.com> wrote:
> Since this seems like it would mostly be useful for beginners, I'd focus on
> the most common case -- forgetting to call an instance method in a boolean
> context (like an "if" statement).
>
>
>
> from types import MethodType
>
> class UnBoolableCallable(object):
>     def __init__(self, call):
>         self.call = call
>     def __call__(self, *args, **kwargs):
>         return self.call(*args, **kwargs)
>     def __len__(self): # why doesn't __bool__ work? check on this.
>         raise Exception("You forgot to call me!")
>
> # we could do this in __new__ on the class
> # since we're only customizing instance creation, not class creation
> class AngryObject(type):
>     def __call__(cls, *args, **kwargs):
>         # create the instance
>         instance = super(AngryObject, cls).__call__(*args, **kwargs)
>         # for each attribute in the class dict
>         for attr in vars(cls):
>             # get the object from the instance (so as to bind methods)
>             value = getattr(instance, attr)
>             # if it's an instance method
>             if isinstance(value, MethodType):
>                 # wrap it in an un-boolable type
>                 setattr(instance, attr, UnBoolableCallable(value))
>         return instance
>
> class A(object):
>     __metaclass__ = AngryObject
>     def isValid(self):
>         return False
>
> # first two work, third raises
> for result in A().isValid(), A.isValid, A().isValid:
>     print result, type(result), bool(result)
>
>
>
>
> P.S. If you really want this behavior use Ruby.
>
>
>
>
> On Tue, Oct 16, 2012 at 11:58 AM, Brian Ray <brianhray at gmail.com> wrote:
>>
>> Question came up from a colleague:
>>
>> I wonder, if there is a simple way in Python to add a hook to class that
>> makes sure methods don't not get called as attributes.
>>
>> class A:
>>     def isValid(self):
>>            return False
>>
>>
>> if A().isValid:
>>     print 'Always True'
>>
>>
>>
>> I had some wacky and wild thoughts about subclassing something to
>> check each and every call and using inspect module to see how it was
>> called. Generally, I know it is the callers responsibility to know
>> what they are doing. We are all responsible adults here, correct?
>>
>>
>>
>> --
>> Brian Ray
>> @brianray
>> (773) 669-7717
>> _______________________________________________
>> Chicago mailing list
>> Chicago at python.org
>> http://mail.python.org/mailman/listinfo/chicago
>
>
>
> _______________________________________________
> Chicago mailing list
> Chicago at python.org
> http://mail.python.org/mailman/listinfo/chicago
>


More information about the Chicago mailing list