I have 2 existing decorators to function in python, @property and @safe_property. Those decorators can't be changed, and they are part of the code that I have no access.
def safe_property(original_property):
def wrap(self):
try:
return original_property(self)
except AttributeError as e:
pass
return wrap
class MyClass(object):
def __init__(self):
pass
@property
@safe_property
def do_func(self):
print("inside do_func!")
return [2,3,4]
By calling the function:
a = MyClass()
print(a.do_func)
The output is good for me!:
inside do_func!
[2, 3, 4]
Now, another feature has come and I'm trying to filter out some of the return values of do_func according to an (optional) additional argument. That means that some user could continue work as usual and call:
print(a.do_func)
While others could call with a filter:
print(a.do_func(True))
In order to try this, I created another decorator called my_decorator such as:
def my_decorator(*args, **kwargs):
print(args)
print(kwargs)
def wrapper(*args):
print(args)
if args[1] == True:
return
# how do I return filter?
else:
return #without the filter?
return wrapper
class MyClass(object):
def __init__(self):
pass
@my_decorator
@property
@safe_property
def do_func(self):
print("inside do_func!")
return [2,3,4]
The current output of this functionality is:
(<property object at 0x02AF0090>,)
{}
(<__main__.MyClass object at 0x00BCDBB0>, True)
None
How can I filter only the odd number ** for example,** of the return list from: do_func?
Thanks
You are applying your decorator to the output of the @property
decorator. That decorator produces a property()
object, not a function. That's because decorators are applied outward from the function definition; see my answer on decorator execution order; so @safe_property
is applied first, then @property
, then @my_decorator
.
If you wanted to decorate the getter function, place your decorator right above the def
statement, it'll be executed first, and whatever your decorator returns will be passed to the safe_property()
decorator (which adds its own wrapper function):
@property
@safe_property
@my_decorator
def do_func(self):
print("inside do_func!")
return [2,3,4]
or, seeing as @safe_property
also produces a wrapper function that's suitable as a getter function, you can place your decorator in between the @safe_property
and @property
lines to wrap the wrapper function returned the former:
@property
@my_decorator
@safe_property
def do_func(self):
print("inside do_func!")
return [2,3,4]
Either way, your decorator wrapper is passed the callable to decorate, and should return a replacement. Property getters only take self
, your replacement would be called with self
too, and no other arguments:
def my_decorator(func):
def wrapper(self): # a replacement getter function, so only self is passed in
result = func(self) # call the original getter
if self.some_flag: # you can access the instance in the wrapper
# return only odd values from the getter
return [i for i in result if i % 2]
else:
# otherwise return the values unchanged
return result
return wrapper
To place @my_decorator
at the top is to decorate a property()
object, not a function, so you'd need to specifically handle being passed such an object (you can see how a @property
decorator works in an answer I wrote before).
E.g. you could extract the getter from the property().fget
attribute, and then return an appropriate replacement (which would be another property()
object):
def my_decorator(prop):
getter = prop.fget
def wrapper(self): # a replacement getter function, so only self is passed in
result = getter(self) # call the original getter, taken from the property
if self.some_flag: # you can access the instance in the wrapper
# return only odd values from the getter
return [i for i in result if i % 2]
else:
# otherwise return the values unchanged
return result
# return a new property object, with the wrapper as the getter function
# and copying across all other property attributes
return property(wrapper, prop.fset, prop.fdel, prop.doc)
Note that a property getter
function will only ever be passed self
, there are no other arguments for property getters possible.
However, handling a property
object directly doesn't really have any advantages over placing your decorator one line lower, it only complicates matters by having to add the prop.fget
reference and property(...)
return value.