Following this question I have an idea how to check whether my function was decorated or not.
Only that I need further information, namely the decorators that were actually applied onto the function (or called when the function was called if it suits better).
For being safe from the danger mentioned in this answer, I am using functools.wraps. This way I don't have to be careful for any naming redefinition of the wrapper used.
This is what I have so far:
from functools import wraps
def decorator_wraps(function):
#wraps(function)
def _wrapper(*a, **kw): ...
return _wrapper
def is_decorated(func):
return hasattr(func, '__wrapped__')
#decorator_wraps
def foo(x, y): ...
print(is_decorated(foo)) # True
But what I need:
from functools import wraps
def decorator_wraps_1(function):
#wraps(function)
def _wrapper(*a, **kw): ...
return _wrapper
def decorator_wraps_2(function):
#wraps(function)
def _wrapper(*a, **kw): ...
return _wrapper
def decorators(func):
# returns list of decorators on `func`
# OR
def is_decorated_by(func, decorator):
# returns True if `func` is decorated by `decorator`
#decorator_wraps_1
#decorator_wraps_2
def foo(x, y): ...
print(decorators(foo)) # [decorator_wraps_1, decorator_wraps_2]
print(is_decorated_by(foo, decorator_wraps_1)) # True
TLDR
I want to decide if my function was decorated and I need the names of these decorator functions as well.
Any idea how to achieve this?
TL;DR
Roll your own #wraps.
import functools
def update_wrapper(wrapper, wrapped, decorator, **kwargs):
wrapper = functools.update_wrapper(wrapper, wrapped, **kwargs)
if decorator is not None:
__decorators__ = getattr(wrapper, "__decorators__", [])
setattr(wrapper, "__decorators__", __decorators__ + [decorator])
return wrapper
def wraps(wrapped, decorator, **kwargs):
return functools.partial(
update_wrapper, wrapped=wrapped, decorator=decorator, **kwargs
)
def get_decorators(func):
return getattr(func, "__decorators__", [])
def is_decorated_by(func, decorator):
return decorator in get_decorators(func)
Usage:
def test_decorator_1(function):
#wraps(function, test_decorator_1)
def wrapper(*args, **kwargs):
return function(*args, **kwargs)
return wrapper
def test_decorator_2(function):
#wraps(function, test_decorator_2)
def wrapper(*args, **kwargs):
return function(*args, **kwargs)
return wrapper
#test_decorator_1
#test_decorator_2
def foo(x: str, y: int) -> None:
print(x, y)
assert get_decorators(foo) == [test_decorator_2, test_decorator_1]
assert is_decorated_by(foo, test_decorator_1)
Custom #wraps
Concept
There is no built-in way for this as far as I know. All it takes to create a (functional) decorator is to define a function that takes another function as argument and returns a function. No information about that "outer" function is magically imprinted onto the returned function by virtue of decoration.
However we can lean on the functools.wraps approach and simply roll our own variation of it. We can define it in such a way that it takes not just a reference to the wrapped function as argument, but also a reference to the outer decorator.
The same way that functools.update_wrapper defines the additional __wrapped__ attribute on the wrapper it outputs, we can define our own custom __decorators__ attribute, which will be simply a list of all the decorators in the order of application (the reverse order of notation).
Code
The proper type annotations are a bit tricky, but here is a full working example:
import functools
from collections.abc import Callable
from typing import Any, ParamSpec, TypeAlias, TypeVar
P = ParamSpec("P")
T = TypeVar("T")
AnyFunc: TypeAlias = Callable[..., Any]
def update_wrapper(
wrapper: Callable[P, T],
wrapped: AnyFunc,
decorator: AnyFunc | None = None,
assigned: tuple[str, ...] = functools.WRAPPER_ASSIGNMENTS,
updated: tuple[str, ...] = functools.WRAPPER_UPDATES,
) -> Callable[P, T]:
"""
Same as `functools.update_wrapper`, but can also add `__decorators__`.
If provided a `decorator` argument, it is appended to the the
`__decorators__` attribute of `wrapper` before returning it.
If `wrapper` has no `__decorators__` attribute, a list with just
`decorator` in it is created and set as that attribute on `wrapper`.
"""
wrapper = functools.update_wrapper(
wrapper,
wrapped,
assigned=assigned,
updated=updated,
)
if decorator is not None:
__decorators__ = getattr(wrapper, "__decorators__", [])
setattr(wrapper, "__decorators__", __decorators__ + [decorator])
return wrapper
def wraps(
wrapped: AnyFunc,
decorator: AnyFunc | None,
assigned: tuple[str, ...] = functools.WRAPPER_ASSIGNMENTS,
updated: tuple[str, ...] = functools.WRAPPER_UPDATES
) -> Callable[[Callable[P, T]], Callable[P, T]]:
"""Same as `functools.wraps`, but uses custom `update_wrapper` inside."""
return functools.partial(
update_wrapper, # type: ignore[arg-type]
wrapped=wrapped,
decorator=decorator,
assigned=assigned,
updated=updated,
)
def get_decorators(func: AnyFunc) -> list[AnyFunc]:
return getattr(func, "__decorators__", [])
def is_decorated_by(func: AnyFunc, decorator: AnyFunc) -> bool:
return decorator in get_decorators(func)
def test() -> None:
def test_decorator_1(function: Callable[P, T]) -> Callable[P, T]:
#wraps(function, test_decorator_1)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
print(f"Called wrapper from {test_decorator_1.__name__}")
return function(*args, **kwargs)
return wrapper
def test_decorator_2(function: Callable[P, T]) -> Callable[P, T]:
#wraps(function, test_decorator_2)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
print(f"Called wrapper from {test_decorator_2.__name__}")
return function(*args, **kwargs)
return wrapper
#test_decorator_1
#test_decorator_2
def foo(x: str, y: int) -> None:
print(x, y)
assert get_decorators(foo) == [test_decorator_2, test_decorator_1]
assert is_decorated_by(foo, test_decorator_1)
assert hasattr(foo, "__wrapped__")
foo("a", 1)
if __name__ == '__main__':
test()
The output is of course:
Called wrapper from test_decorator_1
Called wrapper from test_decorator_2
a 1
Details and caveats
With this approach, none of the original functionality of functools.wraps should be lost. Just like the original, this #wraps decorator obviously relies on you passing the correct arguments for the entire affair to make sense in the end. If you pass a nonsense argument to #wraps, it will add nonsense information to your wrapper.
The difference is you now have to provide two function references instead of one, namely the function being wrapped (as before) and the outer decorator (or None if you want to suppress that information for some reason). So you would typically use it as #wraps(function, decorator).
If you don't like that the decorator argument is mandatory, you could have it default to None. But I thought it was better this way, since the whole point is to have a consistent way of tracking who decorated whom, so omitting the decorator reference should be a conscious choice.
Note that I chose to implement __decorators__ in that order because while they are written in the reverse order, they are applied in that order. So in this example foo is decorated with #test_decorator_2 first and then the wrapper that comes out of that is decorated with #test_decorator_1. It made more sense to me for our list to reflect that order.
Static type checks
With the given type annotations mypy --strict is happy as well and any IDE should still provide the auto-suggestions as expected. The only thing that threw me off, was that mypy complained at my usage of update_wrapper as argument for functools.partial. I could not figure out, why that was, so I added a # type: ignore there.
NOTE: If you are on Python <3.10, you'll probably need to adjust the imports and take for example ParamSpec from typing_extensions instead. Also instead of T | None, you'll need to use typing.Optional[T] instead. Or upgrade your Python version. 🙂
Related
I'm struggling to get this code to properly type check with mypy:
from typing import Any
class Decorator:
def __init__(self, func) -> None:
self.func = func
def __call__(self, *args: Any, **kwargs: Any) -> Any:
return self.func(*args, **kwargs)
class Foo:
#property
#Decorator
def foo(self) -> int:
return 42
f = Foo()
# reveal_type(f.foo)
a: int = f.foo
print(a)
This gives me this error, even though running it correctly prints 42 (note I've uncommented the reveal_type when running mypy):
$ mypy test.py
test.py:17: note: Revealed type is "mypy_test.test.Decorator"
test.py:18: error: Incompatible types in assignment (expression has type "Decorator", variable has type "int") [assignment]
Found 1 error in 1 file (checked 1 source file)
This code properly type checks with mypy:
from typing import Any
class Decorator:
def __init__(self, func) -> None:
self.func = func
def __call__(self, *args: Any, **kwargs: Any) -> Any:
return self.func(*args, **kwargs)
class Foo:
#property
#Decorator
def foo(self) -> int:
return 42
f = Foo()
# reveal_type(f.foo)
a: int = f.foo()
print(a)
But this obviously won't run correctly:
$ python3 test.py
Traceback (most recent call last):
File "/home/gsgx/code/tmp/mypy_test/test.py", line 18, in <module>
a: int = f.foo()
TypeError: 'int' object is not callable
Note that if instead of using a class decorator I use a function decorator, everything works fine (runs correctly and no typecheck errors, reveal_type now shows f.foo is Any):
def Decorator(f):
return f
How can I get this to work using a class decorator? I'm using Python 3.10.6 and mypy 0.991.
Decorator.__call__ hides the type hints that you provided for foo, and replaces them with uselessly general hints.
You need to use typing.ParamSpec and typing.TypeVar to capture the hints of the decorated function to reuse for the new one. I'm not entirely sure how to do that for your class, but here's an example using ordinary functions:
from typing import ParamSpec, TypeVar
P = ParamSpec('P')
RV = TypeVar('R')
def Decorator(f: Callable[P, RV]) -> Callable[P, RV]:
def _(*args: P.args, **kwargs: P.kwargs) -> RV:
return f(*args, **kwargs)
return _
typing.ParamSpec is available starting in Python 3.11, though you can get it from the 3rd-party typing-extensions package as well.
The problem with the class is that you seem to need to make Decorator generic in order to associate the return value of the argument to __init__ with the return value of __call__:
from typing import ParamSpec, TypeVar, Generic
P = ParamSpec('P')
RV = TypeVar('R')
class Decorator(Generic[RV]):
def __init__(self, func: Callable[P, RV]) -> None:
self.func = func
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> RV:
return self.func(*args, **kwargs)
This "works", but requires you to use typing.cast with
a: int = cast(int, f.foo)
to avoid
tmp.py:27: error: Incompatible types in assignment (expression has type "Decorator[int]", variable has type "int") [assignment]
Perhaps there's a simpler solution for the class itself that I'm overlooking. It seems to me, though, that mypy simply isn't recognizing that f.foo and Foo.foo should have different types.
On the other hand, a seemingly trivial change
class Foo:
#Decorator
def foo2(self) -> int:
return 42
foo = property(foo2)
changes the revealed type of f.foo to Any, so this again may be a problem with how mypy handles property. property itself may not be correctly typed to handle the more precise typing in Decorator, and only when property is used "statically" as a decorator itself do the hints on foo carry through.
You can implement a very easy hack by doing this:
import typing as t
import collections.abc as cx
if t.TYPE_CHECKING:
F = t.TypeVar("F")
Decorator: cx.Callable[[F], F]
else:
class Decorator:
"""Your runtime implementation here"""
If you want to do this properly, it's a bit more verbose.
property is generally special-cased across all the type checker implementations. For mypy, you need to write anything that interacts with property as if #property was not even there. This means re-implementing property's descriptor protocol, if only for static type-checking purposes:
import collections.abc as cx
import typing as t
Self = t.TypeVar("Self")
P = t.ParamSpec("P")
R = t.TypeVar("R", covariant=True)
class Decorator(t.Generic[P, R]):
func: cx.Callable[P, R]
def __init__(self, func: cx.Callable[P, R]) -> None:
self.func = func
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
return self.func(*args, **kwargs)
if t.TYPE_CHECKING:
# Type-check-only descriptor protocol access. No runtime implementation.
#t.overload # type: ignore[no-overload-impl]
def __get__(self, instance: None, type_: type) -> property:
"""Descriptor access from the class (instance is `None`)"""
#t.overload
def __get__(self, instance: t.Any, type_: type) -> R:
"""Descriptor access from the instance"""
class Foo:
#property
#Decorator
def foo(self) -> int:
return 42
f = Foo()
# reveal_type(f.foo) # mypy: Revealed type is "builtins.int"
# reveal_type(Foo.foo) # mypy: Revealed type is "builtins.property"
a: int = f.foo
print(a)
However, if I were you, I would write class Decorator to be completely independent of needing property. This means providing a runtime implementation to the descriptor protocol's __get__:
from __future__ import annotations
import collections.abc as cx
import typing as t
ObjT = t.TypeVar("ObjT")
P = t.ParamSpec("P")
R = t.TypeVar("R", covariant=True)
class Decorator(t.Generic[P, R]):
func: cx.Callable[P, R]
def __init__(self, func: cx.Callable[P, R]) -> None:
self.func = func
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
return self.func(*args, **kwargs)
#t.overload
def __get__(self, instance: None, type_: type[ObjT]) -> Decorator[[ObjT], R]:
...
#t.overload
def __get__(self, instance: ObjT, type_: type[ObjT]) -> R:
...
def __get__(
self: Decorator[[ObjT], R], instance: ObjT | None, type_: type[ObjT]
) -> Decorator[[ObjT], R] | R:
if instance is None:
return self
return self.func(instance)
class Foo:
#Decorator
def foo(self) -> int:
return 42
f = Foo()
# reveal_type(f.foo) # mypy: Revealed type is "builtins.int"
# reveal_type(Foo.foo) # mypy: Revealed type is "Decorator[[call.Foo], builtins.int]"
a: int = f.foo
print(a)
Note that any descriptor-like access through the instance can only pass the instance object to Decorator.func; you can't have arbitrary arguments passed through Foo().foo, as Python's syntax doesn't allow you to do that.
The Generic[P, R] and __call__ on Decorator[...] still allows you to use #Decorator over free functions (not methods in class bodies) which don't need property-like implementations, whatever your use case for that may be.
I'm trying to type a decorator factory and it seems to require appending something to a ParamSpec rather than prepending it:
P = ParamSpec('P')
R = TypeVar('R')
def patch_clients(*targets: str):
def decorator(func: Callable[Concatenate[P, ClientsMock], R]) -> Callable[P, R]:
clients_mock = ClientsMock()
for target in targets:
func = mock.patch(target, clients_mock)(func)
#functools.wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
return func(*args, clients_mock, **kwargs)
return wrapper
return decorator
Usage:
class MyTestCase(unittest.TestCase):
#patch_client('some.module.import')
def test_foo(self, clients_mock):
pass
This doesn't work as the last argument to Concatenate needs to be P. However, I can't pass clients_mock as the first argument to func as that would mess with self at the use site (test_foo()) of the decorator.
Is there a way to achieve the type of test_foo() that I want?
How do class decorators for methods in classes work? Here is a sample of what I've done through some experimenting:
from functools import wraps
class PrintLog(object):
def __call__(self, func):
#wraps(func)
def wrapped(*args):
print('I am a log')
return func(*args)
return wrapped
class foo(object):
def __init__(self, rs: str) -> None:
self.ter = rs
#PrintLog()
def baz(self) -> None:
print('inside baz')
bar = foo('2')
print('running bar.baz()')
bar.baz()
And this works perfectly fine. However, I was under the impression that decorators do not need to be called with (), but when I remove the brackets from #PrintLog(), I get this error:
def baz(self) -> None:
TypeError: PrintLog() takes no arguments
Is there something I am missing/do not understand? I've also tried passing in a throwaway arg with __init__(), and it works.
class PrintLog(object):
def __init__(self, useless):
print(useless)
def __call__(self, func):
#wraps(func)
def wrapped(*args):
print('I am a log')
return func(*args)
return wrapped
class foo(object):
def __init__(self, rs: str) -> None:
self.ter = rs
#PrintLog("useless arg that I'm passing to __init__")
def baz(self) -> None:
print('inside baz')
Again, this works, but I don't want to pass any argument to the decorator.
tl;dr: This question in python 3.x.
Help appreciated!
Class decorators accept the function as a subject within the __init__ method (hence the log message), so your decorator code should look like:
class PrintLog(object):
def __init__(self, function):
self.function = function
def __call__(self):
#wraps(self.function)
def wrapped(*args):
print('I am a log')
return self.function(*args)
return wrapped
Sorry if this doesn’t work, I’m answering on my mobile device.
EDIT:
Okay so this is probably not what you want, but this is the way to do it:
from functools import update_wrapper, partial, wraps
class PrintLog(object):
def __init__(self, func):
update_wrapper(self, func)
self.func = func
def __get__(self, obj, objtype):
"""Support instance methods."""
return partial(self.__call__, obj)
def __call__(self, obj, *args, **kwargs):
#wraps(self.func)
def wrapped(*args):
print('I am a log')
return self.func(*args)
return wrapped(obj, *args)
class foo(object):
def __init__(self, rs: str) -> None:
self.ter = rs
#PrintLog
def baz(self) -> None:
print('inside baz')
bar = foo('2')
print('running bar.baz()')
bar.baz()
The decorator has to have the __get__ method defined because you're applying the decorator to an instance method. How would a descriptor have the context of the foo instance?
Ref: Decorating Python class methods - how do I pass the instance to the decorator?
There is a big picture you're missing.
#decorator
def foo(...):
function_definition
is almost identical (except for some internal mangling) to
temp = foo
foo = decorator(temp)
It doesn't matter what the decorator is, as long as it can act like a function.
Your example is equivalent to:
baz = PrintLog("useless thing")(<saved defn of baz>)
Since PrintLog is a class, PrintLog(...) creates an instance of PrintLog. That instance has a __call__ method, so it can act like a function.
Some decorators are designed to take arguments. Some decorators are designed not to take arguments. Some, like #lru_cache, are pieces of Python magic which look to see if the "argument" is a function (so the decorator is being used directly) or a number/None, so that it returns a function that then becomes the decorator.
I am developing an API through which I am passing to the user list of functionalities of a module with the documentations of each function. In order to access the documentation I used to do:
def foo(*args, **kwargs):
"""
Foo documentation is here!
"""
return None
print(foo.__doc__)
# Foo documentation is here!
Now that I added a decorator for some of those functions, the __doc__ returns None since the decorator function doesn't have any documentation.
def decor_func(func):
def wrap(*args, **kwargs):
return func(*args, **kwargs)
return wrap
#decor_func
def foo(*args, **kwargs):
"""
Foo documentation is here!
"""
return None
print(foo.__doc__)
# None
Is there any way that I can have access to decorated function's documentation?
You can update the __doc__ attribute of the wrap function:
def decor_func(func):
def wrap(*args, **kwargs):
return func(*args, **kwargs)
# Set the decorated function `__doc__` attribute
wrap.__doc__ = func.__doc__
return wrap
#decor_func
def foo(*args, **kwargs):
"""
Foo documentation is here!
"""
return None
print(foo.__doc__)
# Foo documentation is here!
However, the best approach is to use functools.wraps, as allows you to also copy additional attributes such as the original name, module and annotations:
import functools
def decor_func(func):
#functools.wraps(func)
def wrap(*args, **kwargs):
return func(*args, **kwargs)
return wrap
#decor_func
def foo(*args, **kwargs):
"""
Foo documentation is here!
"""
return None
print(foo.__doc__)
# Foo documentation is here!
Note, as others have pointed out, you should use functools.wraps so that your wrapper "looks" like the function it is wrapping, and adds the wrapped fucntion to a __wrapped__ attribute. However, note, you can always introspect the wrapper's closure to retrieve a reference to the original function, since it is a free variable in the wrapper and thus will be stored in the closure:
>>> def decor_func(func):
... def wrap(*args, **kwargs):
... return func(*args, **kwargs)
... return wrap
...
>>> #decor_func
... def foo(*args, **kwargs):
... """
... Foo documentation is here!
... """
... return None
...
>>> foo.__closure__
(<cell at 0x10e69da90: function object at 0x10e83a700>,)
So,
>>> foo.__closure__[0].cell_contents.__doc__
'\n Foo documentation is here!\n '
But again, you should use functools.wraps to begin with. The above might help if you have no control over the decorator though.
I want to be able to call a method according to some standard format:
outputs = obj.meth(in_0, in_1, ...)
, where outputs is a tuple of arrays, and each input is an array.
However, in most instances, I only return one array, and don't want to be forced to return a tuple of length 1 just for the sake of the standard format. (My actual formatting problem is more complicated but lets stick with this explanation for now.)
I want to be able to define a class like:
class _SomeClass(object):
def __init__(self):
self._amount_to_add = 1
#single_return_format
def add_one(self, x):
return x+self._amount_to_add
And then be able to call it as follows:
obj = _SomeClass()
assert obj.add_one(3) == 4
assert obj.add_one.standard_format(3)==(4, )
Question is: how do I define the decorator to allow this behaviour?
I tried:
def single_return_format(fcn):
fcn.standard_format = lambda *args: (fcn(*args), )
return fcn
, but it fails on the line with the second assert with:
TypeError: add_one() takes exactly 2 arguments (1 given)
Because the add_one requires "self" as an argument, and the the object has not even been created yet at the time the decorator modifies the function.
So Stack, how can I do this?
Notes:
1) I know I could do this with base-classes and inheritance instead, but that becomes a problem when you have more than one method in the class that you want to decorate this way.
2) The actual problem comes from using theano - the standard format is outputs, updates = fcn(*inputs), but most functions don't return any updates, so you want to be able to define those functions in a natural way, but still have the option of calling them according to this standard interface.
That's indeed a problem, because the way the "bound" method is retrieved from the function doesn't consider this way.
I see two ways:
You could just wrap the function:
def single_return_format(fcn):
# TODO Do some functools.wraps here...
return lambda *args, **kwargs: (fcn(*args, **kwargs), )
No fooling around with .standard_format, but a mere replacement of the function. So the function can define itself as returning one value, but can only be called as returning the tuple.
If this is not what you want, you can define a class for decorating methods which overrides __get__ and does the wrapping in a "live fashion". Of course, it can as well redefine __call__ so that it is usable for (standalone, non-method) functions as well.
To get exactly what you want you'd have to write a non-data descriptor and a set of wrapper classes for your functions. The reason for this is that the process of getting functions from objects as methods is highly optimised and it's not possible to hijack this mechanism. Instead you have to write your own classes that simulate this mechanism -- which will slow down your code if you are making lots of small method calls.
The very best way I can think to get the desired functionality is not to use any of the methods that you describe, but rather write a wrapper function that you use when needed to call a normal function in the standard format. eg.
def vectorise(method, *args, **kwargs):
return tuple(method(arg, **kwargs) for arg in args)
obj = _SomeClass()
result = vectorise(obj.add_one, 1, 2, 3)
Indeed, this is how numpy takes functions that operate on one argument and turns them into a function that works on arrays.
import numpy
def add_one(x):
return x + 1
arr = numpy.vectorize(add_one)([1, 2, 3])
If you really, really want to use non-data descriptors then following will work. Be warned these method calls are considerably slower. On my computer a normal method call takes 188 nanoseconds versus 1.53 microseconds for a "simple" method call -- a ten-fold difference. And vectorise call takes half the time a standard_form call does. The vast majority of that time is the lookup of the methods. The actual method calls are quite fast.
class simple_form:
"""Allows a simple function to be called in a standard way."""
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
if instance is None:
return self.func
return SimpleFormMethod(self.func, instance)
class MethodBase:
"""Provides support for getting the string representation of methods."""
def __init__(self, func, instance):
self.func = func
self.instance = instance
def _format(self):
return "<bound {method_class} {obj_class}.{func} of {obj}>".format(
method_class=self.__class__.__name__,
obj_class=self.instance.__class__.__name__,
func=self.func.__name__,
obj=self.instance)
def __str__(self):
return self._format()
def __repr__(self):
return self._format()
class SimpleFormMethod(MethodBase):
def __call__(self, *args, **kwargs):
return self.func(self.instance, *args, **kwargs)
#property
def standard_form(self):
return StandardFormMethod(self.func, self.instance)
class StandardFormMethod(MethodBase):
def __call__(self, *args, **kwargs):
return tuple(self.func(self.instance, arg, **kwargs) for arg in args)
class Number(object):
def __init__(self, value):
self.value = value
def add_to(self, *values):
return tuple(val + self.value for val in values)
#simple_form
def divide_into(self, value):
return value / self.value
num = Number(2)
print("normal method access:", num.add_to, sep="\n")
print("simple form method access:", num.divide_into, sep="\n")
print("standard form method access:", num.divide_into.standard_form, sep="\n")
print("access to underlying function:", Number.divide_into, sep="\n")
print("simple example usage:", num.divide_into(3))
print("standard example usage:", num.divide_into.standard_form(*range(3)))
Dunes gave the correct answer. I've stripped it down to bare bones so that it solves the problem in the question. The stripped-down code is here:
class single_return_format(object):
def __init__(self, func):
self._func = func
def __get__(self, instance, owner):
return SimpleFormMethod(instance, self._func)
class SimpleFormMethod(object):
def __init__(self, instance, func):
self._instance = instance
self._func = func
def __call__(self, *args, **kwargs):
return self._func(self._instance, *args, **kwargs)
#property
def standard_format(self):
return lambda *args, **kwargs: (self._func(self._instance, *args, **kwargs), )
class _SomeClass(object):
def __init__(self):
self._amount_to_add = 1
#single_return_format
def add_one(self, x):
return x+self._amount_to_add
obj = _SomeClass()
assert obj.add_one(3) == 4
assert obj.add_one.standard_format(3) == (4, )