Torilla
Torilla

Reputation: 403

Property decorator with optional argument

I want to create a class property with a decorator that accepts an optional argument. Normally I would write

def MyProperty(func, optional=None):
    def getter():
        """magic goes here"""
        return func() if not optional else optional(func())
    return property(getter)

class MyClass(object):
    @MyProperty
    def myFunction(foo):
        return foo

MyClass().myFunction(5.)
>>> 5.0

This is all fine, but when I now also pass a function along the decorator like this:

class MyClass(object):
    @MyProperty(int)
    def myFunction(foo):
        return foo

and I now call

MyClass().myFunction(5)
>>> TypeError: 'property' object is not callable 

while I expect to get int(5) as result.

Upvotes: 0

Views: 1332

Answers (1)

khelwood
khelwood

Reputation: 59146

When you write

@MyProperty(int)
def myFunction(foo)
    ...

what that means is that MyProperty(int) is called, and whatever that returns is then called with myFunction as an argument. So MyProperty should be a function that returns a function that accepts a function and returns a function.

So you could write your decorator something like this:

def MyProperty(optional=None):
    def decorator(func):
        def getter(*args, **kwargs):
            """unspecified magic goes here"""
            return func(*args, **kwargs) if not optional else optional(func(*args, **kwargs))
        return getter
    return decorator

So MyProperty(int) returns a function (decorator), and decorator returns whatever you are decorating.

However, when you call it without an argument, you'd still need to call it @MyProperty() instead of @MyProperty, otherwise you miss a stage of unwrapping.

>>> class MyClass:
...    @MyProperty()
...    def f1(foo):
...      return foo
...    @MyProperty(int)
...    def f2(foo):
...      return foo
...
>>> MyClass.f1(1.5)
1.5
>>> MyClass.f2(1.5)
1

I'm not sure about your use of property. Both your functions in the example are just functions inside a class. They don't have a self argument or a cls argument, and you're calling them from the class itself, not from an instance. It's somewhat unclear what you were aiming for.

When I tried this in Python 2 I had to declare the functions as static methods for this to work.

>>> class MyClass(object):
...    @staticmethod
...    @MyProperty()
...    def f1(foo):
...      return foo
...    @staticmethod
...    @MyProperty(int)
...    def f2(foo):
...      return foo
...
>>> MyClass.f1(0.5)
0.5
>>> MyClass.f2(1.5)
1

Upvotes: 2

Related Questions