A simple decorator written as a class, which counts how many times a function has been calledCount how many times values has appeared in list so farCount how many times a value is greater than or equal to the median of previous D elementsSimple game of hangman which counts wins and lossesWrite a class for Team which should have function to prints players detailsFinding the duration of how long a search has been popular
how to write formula in word in latex
Did Ender ever learn that he killed Stilson and/or Bonzo?
Who is flying the vertibirds?
Why doesn't the EU now just force the UK to choose between referendum and no-deal?
Sailing the cryptic seas
How to use deus ex machina safely?
If I can solve Sudoku can I solve Travelling Salesman Problem(TSP)? If yes, how?
What is this large pipe coming out of my roof?
Brexit - No Deal Rejection
Use of undefined constant bloginfo
Are there verbs that are neither telic, or atelic?
Welcoming 2019 Pi day: How to draw the letter π?
Recruiter wants very extensive technical details about all of my previous work
Employee lack of ownership
Should we release the security issues we found in our product as CVE or we can just update those on weekly release notes?
Hacking a Safe Lock after 3 tries
Look at your watch and tell me what time is it. vs Look at your watch and tell me what time it is
What exactly is this small puffer fish doing and how did it manage to accomplish such a feat?
Why one should not leave fingerprints on bulbs and plugs?
What's causing this power spike in STM32 low power mode
AG Cluster db upgrade by vendor
What approach do we need to follow for projects without a test environment?
Life insurance that covers only simultaneous/dual deaths
Why did it take so long to abandon sail after steamships were demonstrated?
A simple decorator written as a class, which counts how many times a function has been called
Count how many times values has appeared in list so farCount how many times a value is greater than or equal to the median of previous D elementsSimple game of hangman which counts wins and lossesWrite a class for Team which should have function to prints players detailsFinding the duration of how long a search has been popular
$begingroup$
Can I improve its typing? Is there any other improvement or pythonic change that you would do?
F = TypeVar('F', bound=Callable[..., Any])
# This is mostly so that I practice using a class as a decorator.
class CountCalls:
"""Logs to DEBUG how many times a function gets called, saves the result in a newly created attribute `num_calls`."""
def __init__(self, func: F) -> None:
functools.update_wrapper(self, func)
self.func = func
self.num_calls: int = 0
self._logger = logging.getLogger(__name__ + '.' + self.func.__name__)
self.last_return_value = None
def __call__(self, *args: Any, **kwargs: Any) -> Any:
self.num_calls += 1
self._logger.debug(' called %s times', self.num_calls)
self.last_return_value = self.func(*args, **kwargs)
return self.last_return_value
Here's the decorator in action:
>>> @CountCalls
... def asdf(var: str):
... print(var)
... return len(var)
...
>>> asdf('Laur')
Laur
4
DEBUG:__main__.asdf: called 1 times
>>> asdf('python 3')
DEBUG:__main__.asdf: called 2 times
python 3
8
>>> asdf(3)
DEBUG:__main__.asdf: called 3 times
3
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "C:/Projects/Python/he/src/he/decorators.py", line 156, in __call__
self.last_return_value = self.func(*args, **kwargs)
File "<input>", line 4, in asdf
TypeError: object of type 'int' has no len()
>>> asdf.num_calls
3
python python-3.x meta-programming
New contributor
Laurențiu Andronache is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
$endgroup$
add a comment |
$begingroup$
Can I improve its typing? Is there any other improvement or pythonic change that you would do?
F = TypeVar('F', bound=Callable[..., Any])
# This is mostly so that I practice using a class as a decorator.
class CountCalls:
"""Logs to DEBUG how many times a function gets called, saves the result in a newly created attribute `num_calls`."""
def __init__(self, func: F) -> None:
functools.update_wrapper(self, func)
self.func = func
self.num_calls: int = 0
self._logger = logging.getLogger(__name__ + '.' + self.func.__name__)
self.last_return_value = None
def __call__(self, *args: Any, **kwargs: Any) -> Any:
self.num_calls += 1
self._logger.debug(' called %s times', self.num_calls)
self.last_return_value = self.func(*args, **kwargs)
return self.last_return_value
Here's the decorator in action:
>>> @CountCalls
... def asdf(var: str):
... print(var)
... return len(var)
...
>>> asdf('Laur')
Laur
4
DEBUG:__main__.asdf: called 1 times
>>> asdf('python 3')
DEBUG:__main__.asdf: called 2 times
python 3
8
>>> asdf(3)
DEBUG:__main__.asdf: called 3 times
3
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "C:/Projects/Python/he/src/he/decorators.py", line 156, in __call__
self.last_return_value = self.func(*args, **kwargs)
File "<input>", line 4, in asdf
TypeError: object of type 'int' has no len()
>>> asdf.num_calls
3
python python-3.x meta-programming
New contributor
Laurențiu Andronache is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
$endgroup$
$begingroup$
FWIW, theunittest.mockmodule may be of use for this sort of thing. Note that theMockclasses are currently not typed due to issues withmypy. You can see the previous, typed code in the link and I take a full look once I get to a computer.
$endgroup$
– Solomon Ucko
Mar 10 at 15:29
$begingroup$
I updated the "decorator in action" part to make it more readable. I don't know what you're saying there about mock typing though.
$endgroup$
– Laurențiu Andronache
Mar 10 at 15:39
add a comment |
$begingroup$
Can I improve its typing? Is there any other improvement or pythonic change that you would do?
F = TypeVar('F', bound=Callable[..., Any])
# This is mostly so that I practice using a class as a decorator.
class CountCalls:
"""Logs to DEBUG how many times a function gets called, saves the result in a newly created attribute `num_calls`."""
def __init__(self, func: F) -> None:
functools.update_wrapper(self, func)
self.func = func
self.num_calls: int = 0
self._logger = logging.getLogger(__name__ + '.' + self.func.__name__)
self.last_return_value = None
def __call__(self, *args: Any, **kwargs: Any) -> Any:
self.num_calls += 1
self._logger.debug(' called %s times', self.num_calls)
self.last_return_value = self.func(*args, **kwargs)
return self.last_return_value
Here's the decorator in action:
>>> @CountCalls
... def asdf(var: str):
... print(var)
... return len(var)
...
>>> asdf('Laur')
Laur
4
DEBUG:__main__.asdf: called 1 times
>>> asdf('python 3')
DEBUG:__main__.asdf: called 2 times
python 3
8
>>> asdf(3)
DEBUG:__main__.asdf: called 3 times
3
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "C:/Projects/Python/he/src/he/decorators.py", line 156, in __call__
self.last_return_value = self.func(*args, **kwargs)
File "<input>", line 4, in asdf
TypeError: object of type 'int' has no len()
>>> asdf.num_calls
3
python python-3.x meta-programming
New contributor
Laurențiu Andronache is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
$endgroup$
Can I improve its typing? Is there any other improvement or pythonic change that you would do?
F = TypeVar('F', bound=Callable[..., Any])
# This is mostly so that I practice using a class as a decorator.
class CountCalls:
"""Logs to DEBUG how many times a function gets called, saves the result in a newly created attribute `num_calls`."""
def __init__(self, func: F) -> None:
functools.update_wrapper(self, func)
self.func = func
self.num_calls: int = 0
self._logger = logging.getLogger(__name__ + '.' + self.func.__name__)
self.last_return_value = None
def __call__(self, *args: Any, **kwargs: Any) -> Any:
self.num_calls += 1
self._logger.debug(' called %s times', self.num_calls)
self.last_return_value = self.func(*args, **kwargs)
return self.last_return_value
Here's the decorator in action:
>>> @CountCalls
... def asdf(var: str):
... print(var)
... return len(var)
...
>>> asdf('Laur')
Laur
4
DEBUG:__main__.asdf: called 1 times
>>> asdf('python 3')
DEBUG:__main__.asdf: called 2 times
python 3
8
>>> asdf(3)
DEBUG:__main__.asdf: called 3 times
3
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "C:/Projects/Python/he/src/he/decorators.py", line 156, in __call__
self.last_return_value = self.func(*args, **kwargs)
File "<input>", line 4, in asdf
TypeError: object of type 'int' has no len()
>>> asdf.num_calls
3
python python-3.x meta-programming
python python-3.x meta-programming
New contributor
Laurențiu Andronache is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
New contributor
Laurențiu Andronache is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
edited Mar 10 at 19:01
200_success
130k17153419
130k17153419
New contributor
Laurențiu Andronache is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
asked Mar 10 at 12:03
Laurențiu AndronacheLaurențiu Andronache
92
92
New contributor
Laurențiu Andronache is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
New contributor
Laurențiu Andronache is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
Laurențiu Andronache is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
$begingroup$
FWIW, theunittest.mockmodule may be of use for this sort of thing. Note that theMockclasses are currently not typed due to issues withmypy. You can see the previous, typed code in the link and I take a full look once I get to a computer.
$endgroup$
– Solomon Ucko
Mar 10 at 15:29
$begingroup$
I updated the "decorator in action" part to make it more readable. I don't know what you're saying there about mock typing though.
$endgroup$
– Laurențiu Andronache
Mar 10 at 15:39
add a comment |
$begingroup$
FWIW, theunittest.mockmodule may be of use for this sort of thing. Note that theMockclasses are currently not typed due to issues withmypy. You can see the previous, typed code in the link and I take a full look once I get to a computer.
$endgroup$
– Solomon Ucko
Mar 10 at 15:29
$begingroup$
I updated the "decorator in action" part to make it more readable. I don't know what you're saying there about mock typing though.
$endgroup$
– Laurențiu Andronache
Mar 10 at 15:39
$begingroup$
FWIW, the
unittest.mock module may be of use for this sort of thing. Note that the Mock classes are currently not typed due to issues with mypy. You can see the previous, typed code in the link and I take a full look once I get to a computer.$endgroup$
– Solomon Ucko
Mar 10 at 15:29
$begingroup$
FWIW, the
unittest.mock module may be of use for this sort of thing. Note that the Mock classes are currently not typed due to issues with mypy. You can see the previous, typed code in the link and I take a full look once I get to a computer.$endgroup$
– Solomon Ucko
Mar 10 at 15:29
$begingroup$
I updated the "decorator in action" part to make it more readable. I don't know what you're saying there about mock typing though.
$endgroup$
– Laurențiu Andronache
Mar 10 at 15:39
$begingroup$
I updated the "decorator in action" part to make it more readable. I don't know what you're saying there about mock typing though.
$endgroup$
– Laurențiu Andronache
Mar 10 at 15:39
add a comment |
1 Answer
1
active
oldest
votes
$begingroup$
One thing you could try to improve the typing would be to type the method itself (although I'm not sure how well tools support it). Also, leading/trailing whitespace should be up to the logger, not the code using it.
F = TypeVar('F', bound=Callable[..., Any])
# This is mostly so that I practice using a class as a decorator.
class CountCalls:
"""Logs to DEBUG how many times a function gets called, saves the result in a newly created attribute `num_calls`."""
def __init__(self, func: F) -> None:
functools.update_wrapper(self, func)
self.func = func
self.num_calls: int = 0
self._logger = logging.getLogger(__name__ + '.' + self.func.__name__)
self.last_return_value = None
__call__: F
def __call__(self, *args: Any, **kwargs: Any) -> Any:
self.num_calls += 1
self._logger.debug(f'called %s times', self.num_calls)
self.last_return_value = self.func(*args, **kwargs)
return self.last_return_value
As for the code itself, you could make a callback-based API.
F = TypeVar('F', bound=Callable[..., Any])
# This is mostly so that I practice using a class as a decorator.
class CountCalls:
"""Logs to DEBUG how many times a function gets called, saves the result in a newly created attribute `num_calls`."""
def __init__(self, func: F, callback: Optional[Callable[[int, Tuple[Any], Dict[str, Any]], Any]] = None) -> None:
if callback is None:
logger = logging.getLogger(__name__ + '.' + self.func.__name__)
def callback(num_calls: int, args: Tuple[Any], kwargs: Dict[str, Any]):
self._logger.debug(f'called %s times', self.num_calls)
functools.update_wrapper(self, func)
self.func = func
self.callback = callback
self.num_calls: int = 0
self.last_return_value = None
__call__: F
def __call__(self, *args: Any, **kwargs: Any) -> Any:
self.num_calls += 1
self.callback(self.num_calls, args, kwargs)
self.last_return_value = self.func(*args, **kwargs)
return self.last_return_value
Or with the number of calls tracked in the callback (for increased flexibility):
F = TypeVar('F', bound=Callable[..., Any])
# This is mostly so that I practice using a class as a decorator.
class CountCalls:
"""Logs to DEBUG how many times a function gets called, saves the result in a newly created attribute `num_calls`."""
def __init__(self, func: F, callback: Optional[Callable[[int, Tuple[Any], Dict[str, Any]], Any]] = None) -> None:
if callback is None:
logger = logging.getLogger(__name__ + '.' + self.func.__name__)
num_calls: int = 0
def callback(args: Tuple[Any], kwargs: Dict[str, Any]):
nonlocal num_calls # Not sure if this is necessary or not
num_calls += 1
self._logger.debug(f'called %s times', self.num_calls)
functools.update_wrapper(self, func)
self.func = func
self.callback = callback
self.last_return_value = None
__call__: F
def __call__(self, *args: Any, **kwargs: Any) -> Any:
self.callback(self.num_calls, args, kwargs)
self.last_return_value = self.func(*args, **kwargs)
return self.last_return_value
(Note: this code has not been tested.)
$endgroup$
1
$begingroup$
Using%-formatting is the default way for theloggingmodule (and it is not going to change due to backward compatibility). It is a bit special in that regard. One can change it to use parts of thestr.formatsyntax, but not with keyword arguments. Usingf-strings here is actually a potentially bad idea, because normally the string formatting is only performed if the message is of the right logging level, which means a lot when not having to do it for debug messages.
$endgroup$
– Graipher
Mar 10 at 19:45
1
$begingroup$
Typing the__call__method itself is genius. Regarding f-strings in logging, I have to agree with @Graipher that string interpolation is better suited for the logging module - that's why I used it in the first place. I'm starting to understand now what's with the callback part that you proposed, and that's genius as well; if I understand correctly, it allows changing the functionality of the decorator by supplying an optional function... I'll go into test mode for that part.
$endgroup$
– Laurențiu Andronache
Mar 10 at 21:58
$begingroup$
@LaurențiuAndronache It would be nice if Python supported additional types ofTypeVars for when tricks like this don't work (such as with typing the callback), but oh well... Also, with the callback API, I'll add a version where the number of calls is tracked by the callback for increased flexibility.
$endgroup$
– Solomon Ucko
Mar 10 at 22:05
$begingroup$
note that in both examples with callbacks, you can't actually supply a callback to the decorator.TypeError: b() takes 0 positional arguments but 1 was given
$endgroup$
– Laurențiu Andronache
30 mins ago
$begingroup$
@LaurențiuAndronache Interesting.... What isb? The decorated function or the callback? What's its signature? Does the error occur when defining the decorated function or when calling it? How are you calling the decorator? I thinkfunctools.partialwith a keyword argument (callback) is necessary.
$endgroup$
– Solomon Ucko
23 mins ago
add a comment |
Your Answer
StackExchange.ifUsing("editor", function ()
return StackExchange.using("mathjaxEditing", function ()
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
);
);
, "mathjax-editing");
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Laurențiu Andronache is a new contributor. Be nice, and check out our Code of Conduct.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f215135%2fa-simple-decorator-written-as-a-class-which-counts-how-many-times-a-function-ha%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
$begingroup$
One thing you could try to improve the typing would be to type the method itself (although I'm not sure how well tools support it). Also, leading/trailing whitespace should be up to the logger, not the code using it.
F = TypeVar('F', bound=Callable[..., Any])
# This is mostly so that I practice using a class as a decorator.
class CountCalls:
"""Logs to DEBUG how many times a function gets called, saves the result in a newly created attribute `num_calls`."""
def __init__(self, func: F) -> None:
functools.update_wrapper(self, func)
self.func = func
self.num_calls: int = 0
self._logger = logging.getLogger(__name__ + '.' + self.func.__name__)
self.last_return_value = None
__call__: F
def __call__(self, *args: Any, **kwargs: Any) -> Any:
self.num_calls += 1
self._logger.debug(f'called %s times', self.num_calls)
self.last_return_value = self.func(*args, **kwargs)
return self.last_return_value
As for the code itself, you could make a callback-based API.
F = TypeVar('F', bound=Callable[..., Any])
# This is mostly so that I practice using a class as a decorator.
class CountCalls:
"""Logs to DEBUG how many times a function gets called, saves the result in a newly created attribute `num_calls`."""
def __init__(self, func: F, callback: Optional[Callable[[int, Tuple[Any], Dict[str, Any]], Any]] = None) -> None:
if callback is None:
logger = logging.getLogger(__name__ + '.' + self.func.__name__)
def callback(num_calls: int, args: Tuple[Any], kwargs: Dict[str, Any]):
self._logger.debug(f'called %s times', self.num_calls)
functools.update_wrapper(self, func)
self.func = func
self.callback = callback
self.num_calls: int = 0
self.last_return_value = None
__call__: F
def __call__(self, *args: Any, **kwargs: Any) -> Any:
self.num_calls += 1
self.callback(self.num_calls, args, kwargs)
self.last_return_value = self.func(*args, **kwargs)
return self.last_return_value
Or with the number of calls tracked in the callback (for increased flexibility):
F = TypeVar('F', bound=Callable[..., Any])
# This is mostly so that I practice using a class as a decorator.
class CountCalls:
"""Logs to DEBUG how many times a function gets called, saves the result in a newly created attribute `num_calls`."""
def __init__(self, func: F, callback: Optional[Callable[[int, Tuple[Any], Dict[str, Any]], Any]] = None) -> None:
if callback is None:
logger = logging.getLogger(__name__ + '.' + self.func.__name__)
num_calls: int = 0
def callback(args: Tuple[Any], kwargs: Dict[str, Any]):
nonlocal num_calls # Not sure if this is necessary or not
num_calls += 1
self._logger.debug(f'called %s times', self.num_calls)
functools.update_wrapper(self, func)
self.func = func
self.callback = callback
self.last_return_value = None
__call__: F
def __call__(self, *args: Any, **kwargs: Any) -> Any:
self.callback(self.num_calls, args, kwargs)
self.last_return_value = self.func(*args, **kwargs)
return self.last_return_value
(Note: this code has not been tested.)
$endgroup$
1
$begingroup$
Using%-formatting is the default way for theloggingmodule (and it is not going to change due to backward compatibility). It is a bit special in that regard. One can change it to use parts of thestr.formatsyntax, but not with keyword arguments. Usingf-strings here is actually a potentially bad idea, because normally the string formatting is only performed if the message is of the right logging level, which means a lot when not having to do it for debug messages.
$endgroup$
– Graipher
Mar 10 at 19:45
1
$begingroup$
Typing the__call__method itself is genius. Regarding f-strings in logging, I have to agree with @Graipher that string interpolation is better suited for the logging module - that's why I used it in the first place. I'm starting to understand now what's with the callback part that you proposed, and that's genius as well; if I understand correctly, it allows changing the functionality of the decorator by supplying an optional function... I'll go into test mode for that part.
$endgroup$
– Laurențiu Andronache
Mar 10 at 21:58
$begingroup$
@LaurențiuAndronache It would be nice if Python supported additional types ofTypeVars for when tricks like this don't work (such as with typing the callback), but oh well... Also, with the callback API, I'll add a version where the number of calls is tracked by the callback for increased flexibility.
$endgroup$
– Solomon Ucko
Mar 10 at 22:05
$begingroup$
note that in both examples with callbacks, you can't actually supply a callback to the decorator.TypeError: b() takes 0 positional arguments but 1 was given
$endgroup$
– Laurențiu Andronache
30 mins ago
$begingroup$
@LaurențiuAndronache Interesting.... What isb? The decorated function or the callback? What's its signature? Does the error occur when defining the decorated function or when calling it? How are you calling the decorator? I thinkfunctools.partialwith a keyword argument (callback) is necessary.
$endgroup$
– Solomon Ucko
23 mins ago
add a comment |
$begingroup$
One thing you could try to improve the typing would be to type the method itself (although I'm not sure how well tools support it). Also, leading/trailing whitespace should be up to the logger, not the code using it.
F = TypeVar('F', bound=Callable[..., Any])
# This is mostly so that I practice using a class as a decorator.
class CountCalls:
"""Logs to DEBUG how many times a function gets called, saves the result in a newly created attribute `num_calls`."""
def __init__(self, func: F) -> None:
functools.update_wrapper(self, func)
self.func = func
self.num_calls: int = 0
self._logger = logging.getLogger(__name__ + '.' + self.func.__name__)
self.last_return_value = None
__call__: F
def __call__(self, *args: Any, **kwargs: Any) -> Any:
self.num_calls += 1
self._logger.debug(f'called %s times', self.num_calls)
self.last_return_value = self.func(*args, **kwargs)
return self.last_return_value
As for the code itself, you could make a callback-based API.
F = TypeVar('F', bound=Callable[..., Any])
# This is mostly so that I practice using a class as a decorator.
class CountCalls:
"""Logs to DEBUG how many times a function gets called, saves the result in a newly created attribute `num_calls`."""
def __init__(self, func: F, callback: Optional[Callable[[int, Tuple[Any], Dict[str, Any]], Any]] = None) -> None:
if callback is None:
logger = logging.getLogger(__name__ + '.' + self.func.__name__)
def callback(num_calls: int, args: Tuple[Any], kwargs: Dict[str, Any]):
self._logger.debug(f'called %s times', self.num_calls)
functools.update_wrapper(self, func)
self.func = func
self.callback = callback
self.num_calls: int = 0
self.last_return_value = None
__call__: F
def __call__(self, *args: Any, **kwargs: Any) -> Any:
self.num_calls += 1
self.callback(self.num_calls, args, kwargs)
self.last_return_value = self.func(*args, **kwargs)
return self.last_return_value
Or with the number of calls tracked in the callback (for increased flexibility):
F = TypeVar('F', bound=Callable[..., Any])
# This is mostly so that I practice using a class as a decorator.
class CountCalls:
"""Logs to DEBUG how many times a function gets called, saves the result in a newly created attribute `num_calls`."""
def __init__(self, func: F, callback: Optional[Callable[[int, Tuple[Any], Dict[str, Any]], Any]] = None) -> None:
if callback is None:
logger = logging.getLogger(__name__ + '.' + self.func.__name__)
num_calls: int = 0
def callback(args: Tuple[Any], kwargs: Dict[str, Any]):
nonlocal num_calls # Not sure if this is necessary or not
num_calls += 1
self._logger.debug(f'called %s times', self.num_calls)
functools.update_wrapper(self, func)
self.func = func
self.callback = callback
self.last_return_value = None
__call__: F
def __call__(self, *args: Any, **kwargs: Any) -> Any:
self.callback(self.num_calls, args, kwargs)
self.last_return_value = self.func(*args, **kwargs)
return self.last_return_value
(Note: this code has not been tested.)
$endgroup$
1
$begingroup$
Using%-formatting is the default way for theloggingmodule (and it is not going to change due to backward compatibility). It is a bit special in that regard. One can change it to use parts of thestr.formatsyntax, but not with keyword arguments. Usingf-strings here is actually a potentially bad idea, because normally the string formatting is only performed if the message is of the right logging level, which means a lot when not having to do it for debug messages.
$endgroup$
– Graipher
Mar 10 at 19:45
1
$begingroup$
Typing the__call__method itself is genius. Regarding f-strings in logging, I have to agree with @Graipher that string interpolation is better suited for the logging module - that's why I used it in the first place. I'm starting to understand now what's with the callback part that you proposed, and that's genius as well; if I understand correctly, it allows changing the functionality of the decorator by supplying an optional function... I'll go into test mode for that part.
$endgroup$
– Laurențiu Andronache
Mar 10 at 21:58
$begingroup$
@LaurențiuAndronache It would be nice if Python supported additional types ofTypeVars for when tricks like this don't work (such as with typing the callback), but oh well... Also, with the callback API, I'll add a version where the number of calls is tracked by the callback for increased flexibility.
$endgroup$
– Solomon Ucko
Mar 10 at 22:05
$begingroup$
note that in both examples with callbacks, you can't actually supply a callback to the decorator.TypeError: b() takes 0 positional arguments but 1 was given
$endgroup$
– Laurențiu Andronache
30 mins ago
$begingroup$
@LaurențiuAndronache Interesting.... What isb? The decorated function or the callback? What's its signature? Does the error occur when defining the decorated function or when calling it? How are you calling the decorator? I thinkfunctools.partialwith a keyword argument (callback) is necessary.
$endgroup$
– Solomon Ucko
23 mins ago
add a comment |
$begingroup$
One thing you could try to improve the typing would be to type the method itself (although I'm not sure how well tools support it). Also, leading/trailing whitespace should be up to the logger, not the code using it.
F = TypeVar('F', bound=Callable[..., Any])
# This is mostly so that I practice using a class as a decorator.
class CountCalls:
"""Logs to DEBUG how many times a function gets called, saves the result in a newly created attribute `num_calls`."""
def __init__(self, func: F) -> None:
functools.update_wrapper(self, func)
self.func = func
self.num_calls: int = 0
self._logger = logging.getLogger(__name__ + '.' + self.func.__name__)
self.last_return_value = None
__call__: F
def __call__(self, *args: Any, **kwargs: Any) -> Any:
self.num_calls += 1
self._logger.debug(f'called %s times', self.num_calls)
self.last_return_value = self.func(*args, **kwargs)
return self.last_return_value
As for the code itself, you could make a callback-based API.
F = TypeVar('F', bound=Callable[..., Any])
# This is mostly so that I practice using a class as a decorator.
class CountCalls:
"""Logs to DEBUG how many times a function gets called, saves the result in a newly created attribute `num_calls`."""
def __init__(self, func: F, callback: Optional[Callable[[int, Tuple[Any], Dict[str, Any]], Any]] = None) -> None:
if callback is None:
logger = logging.getLogger(__name__ + '.' + self.func.__name__)
def callback(num_calls: int, args: Tuple[Any], kwargs: Dict[str, Any]):
self._logger.debug(f'called %s times', self.num_calls)
functools.update_wrapper(self, func)
self.func = func
self.callback = callback
self.num_calls: int = 0
self.last_return_value = None
__call__: F
def __call__(self, *args: Any, **kwargs: Any) -> Any:
self.num_calls += 1
self.callback(self.num_calls, args, kwargs)
self.last_return_value = self.func(*args, **kwargs)
return self.last_return_value
Or with the number of calls tracked in the callback (for increased flexibility):
F = TypeVar('F', bound=Callable[..., Any])
# This is mostly so that I practice using a class as a decorator.
class CountCalls:
"""Logs to DEBUG how many times a function gets called, saves the result in a newly created attribute `num_calls`."""
def __init__(self, func: F, callback: Optional[Callable[[int, Tuple[Any], Dict[str, Any]], Any]] = None) -> None:
if callback is None:
logger = logging.getLogger(__name__ + '.' + self.func.__name__)
num_calls: int = 0
def callback(args: Tuple[Any], kwargs: Dict[str, Any]):
nonlocal num_calls # Not sure if this is necessary or not
num_calls += 1
self._logger.debug(f'called %s times', self.num_calls)
functools.update_wrapper(self, func)
self.func = func
self.callback = callback
self.last_return_value = None
__call__: F
def __call__(self, *args: Any, **kwargs: Any) -> Any:
self.callback(self.num_calls, args, kwargs)
self.last_return_value = self.func(*args, **kwargs)
return self.last_return_value
(Note: this code has not been tested.)
$endgroup$
One thing you could try to improve the typing would be to type the method itself (although I'm not sure how well tools support it). Also, leading/trailing whitespace should be up to the logger, not the code using it.
F = TypeVar('F', bound=Callable[..., Any])
# This is mostly so that I practice using a class as a decorator.
class CountCalls:
"""Logs to DEBUG how many times a function gets called, saves the result in a newly created attribute `num_calls`."""
def __init__(self, func: F) -> None:
functools.update_wrapper(self, func)
self.func = func
self.num_calls: int = 0
self._logger = logging.getLogger(__name__ + '.' + self.func.__name__)
self.last_return_value = None
__call__: F
def __call__(self, *args: Any, **kwargs: Any) -> Any:
self.num_calls += 1
self._logger.debug(f'called %s times', self.num_calls)
self.last_return_value = self.func(*args, **kwargs)
return self.last_return_value
As for the code itself, you could make a callback-based API.
F = TypeVar('F', bound=Callable[..., Any])
# This is mostly so that I practice using a class as a decorator.
class CountCalls:
"""Logs to DEBUG how many times a function gets called, saves the result in a newly created attribute `num_calls`."""
def __init__(self, func: F, callback: Optional[Callable[[int, Tuple[Any], Dict[str, Any]], Any]] = None) -> None:
if callback is None:
logger = logging.getLogger(__name__ + '.' + self.func.__name__)
def callback(num_calls: int, args: Tuple[Any], kwargs: Dict[str, Any]):
self._logger.debug(f'called %s times', self.num_calls)
functools.update_wrapper(self, func)
self.func = func
self.callback = callback
self.num_calls: int = 0
self.last_return_value = None
__call__: F
def __call__(self, *args: Any, **kwargs: Any) -> Any:
self.num_calls += 1
self.callback(self.num_calls, args, kwargs)
self.last_return_value = self.func(*args, **kwargs)
return self.last_return_value
Or with the number of calls tracked in the callback (for increased flexibility):
F = TypeVar('F', bound=Callable[..., Any])
# This is mostly so that I practice using a class as a decorator.
class CountCalls:
"""Logs to DEBUG how many times a function gets called, saves the result in a newly created attribute `num_calls`."""
def __init__(self, func: F, callback: Optional[Callable[[int, Tuple[Any], Dict[str, Any]], Any]] = None) -> None:
if callback is None:
logger = logging.getLogger(__name__ + '.' + self.func.__name__)
num_calls: int = 0
def callback(args: Tuple[Any], kwargs: Dict[str, Any]):
nonlocal num_calls # Not sure if this is necessary or not
num_calls += 1
self._logger.debug(f'called %s times', self.num_calls)
functools.update_wrapper(self, func)
self.func = func
self.callback = callback
self.last_return_value = None
__call__: F
def __call__(self, *args: Any, **kwargs: Any) -> Any:
self.callback(self.num_calls, args, kwargs)
self.last_return_value = self.func(*args, **kwargs)
return self.last_return_value
(Note: this code has not been tested.)
edited 13 mins ago
answered Mar 10 at 16:57
Solomon UckoSolomon Ucko
1,1741415
1,1741415
1
$begingroup$
Using%-formatting is the default way for theloggingmodule (and it is not going to change due to backward compatibility). It is a bit special in that regard. One can change it to use parts of thestr.formatsyntax, but not with keyword arguments. Usingf-strings here is actually a potentially bad idea, because normally the string formatting is only performed if the message is of the right logging level, which means a lot when not having to do it for debug messages.
$endgroup$
– Graipher
Mar 10 at 19:45
1
$begingroup$
Typing the__call__method itself is genius. Regarding f-strings in logging, I have to agree with @Graipher that string interpolation is better suited for the logging module - that's why I used it in the first place. I'm starting to understand now what's with the callback part that you proposed, and that's genius as well; if I understand correctly, it allows changing the functionality of the decorator by supplying an optional function... I'll go into test mode for that part.
$endgroup$
– Laurențiu Andronache
Mar 10 at 21:58
$begingroup$
@LaurențiuAndronache It would be nice if Python supported additional types ofTypeVars for when tricks like this don't work (such as with typing the callback), but oh well... Also, with the callback API, I'll add a version where the number of calls is tracked by the callback for increased flexibility.
$endgroup$
– Solomon Ucko
Mar 10 at 22:05
$begingroup$
note that in both examples with callbacks, you can't actually supply a callback to the decorator.TypeError: b() takes 0 positional arguments but 1 was given
$endgroup$
– Laurențiu Andronache
30 mins ago
$begingroup$
@LaurențiuAndronache Interesting.... What isb? The decorated function or the callback? What's its signature? Does the error occur when defining the decorated function or when calling it? How are you calling the decorator? I thinkfunctools.partialwith a keyword argument (callback) is necessary.
$endgroup$
– Solomon Ucko
23 mins ago
add a comment |
1
$begingroup$
Using%-formatting is the default way for theloggingmodule (and it is not going to change due to backward compatibility). It is a bit special in that regard. One can change it to use parts of thestr.formatsyntax, but not with keyword arguments. Usingf-strings here is actually a potentially bad idea, because normally the string formatting is only performed if the message is of the right logging level, which means a lot when not having to do it for debug messages.
$endgroup$
– Graipher
Mar 10 at 19:45
1
$begingroup$
Typing the__call__method itself is genius. Regarding f-strings in logging, I have to agree with @Graipher that string interpolation is better suited for the logging module - that's why I used it in the first place. I'm starting to understand now what's with the callback part that you proposed, and that's genius as well; if I understand correctly, it allows changing the functionality of the decorator by supplying an optional function... I'll go into test mode for that part.
$endgroup$
– Laurențiu Andronache
Mar 10 at 21:58
$begingroup$
@LaurențiuAndronache It would be nice if Python supported additional types ofTypeVars for when tricks like this don't work (such as with typing the callback), but oh well... Also, with the callback API, I'll add a version where the number of calls is tracked by the callback for increased flexibility.
$endgroup$
– Solomon Ucko
Mar 10 at 22:05
$begingroup$
note that in both examples with callbacks, you can't actually supply a callback to the decorator.TypeError: b() takes 0 positional arguments but 1 was given
$endgroup$
– Laurențiu Andronache
30 mins ago
$begingroup$
@LaurențiuAndronache Interesting.... What isb? The decorated function or the callback? What's its signature? Does the error occur when defining the decorated function or when calling it? How are you calling the decorator? I thinkfunctools.partialwith a keyword argument (callback) is necessary.
$endgroup$
– Solomon Ucko
23 mins ago
1
1
$begingroup$
Using
%-formatting is the default way for the logging module (and it is not going to change due to backward compatibility). It is a bit special in that regard. One can change it to use parts of the str.format syntax, but not with keyword arguments. Using f-strings here is actually a potentially bad idea, because normally the string formatting is only performed if the message is of the right logging level, which means a lot when not having to do it for debug messages.$endgroup$
– Graipher
Mar 10 at 19:45
$begingroup$
Using
%-formatting is the default way for the logging module (and it is not going to change due to backward compatibility). It is a bit special in that regard. One can change it to use parts of the str.format syntax, but not with keyword arguments. Using f-strings here is actually a potentially bad idea, because normally the string formatting is only performed if the message is of the right logging level, which means a lot when not having to do it for debug messages.$endgroup$
– Graipher
Mar 10 at 19:45
1
1
$begingroup$
Typing the
__call__ method itself is genius. Regarding f-strings in logging, I have to agree with @Graipher that string interpolation is better suited for the logging module - that's why I used it in the first place. I'm starting to understand now what's with the callback part that you proposed, and that's genius as well; if I understand correctly, it allows changing the functionality of the decorator by supplying an optional function... I'll go into test mode for that part.$endgroup$
– Laurențiu Andronache
Mar 10 at 21:58
$begingroup$
Typing the
__call__ method itself is genius. Regarding f-strings in logging, I have to agree with @Graipher that string interpolation is better suited for the logging module - that's why I used it in the first place. I'm starting to understand now what's with the callback part that you proposed, and that's genius as well; if I understand correctly, it allows changing the functionality of the decorator by supplying an optional function... I'll go into test mode for that part.$endgroup$
– Laurențiu Andronache
Mar 10 at 21:58
$begingroup$
@LaurențiuAndronache It would be nice if Python supported additional types of
TypeVars for when tricks like this don't work (such as with typing the callback), but oh well... Also, with the callback API, I'll add a version where the number of calls is tracked by the callback for increased flexibility.$endgroup$
– Solomon Ucko
Mar 10 at 22:05
$begingroup$
@LaurențiuAndronache It would be nice if Python supported additional types of
TypeVars for when tricks like this don't work (such as with typing the callback), but oh well... Also, with the callback API, I'll add a version where the number of calls is tracked by the callback for increased flexibility.$endgroup$
– Solomon Ucko
Mar 10 at 22:05
$begingroup$
note that in both examples with callbacks, you can't actually supply a callback to the decorator.
TypeError: b() takes 0 positional arguments but 1 was given$endgroup$
– Laurențiu Andronache
30 mins ago
$begingroup$
note that in both examples with callbacks, you can't actually supply a callback to the decorator.
TypeError: b() takes 0 positional arguments but 1 was given$endgroup$
– Laurențiu Andronache
30 mins ago
$begingroup$
@LaurențiuAndronache Interesting.... What is
b? The decorated function or the callback? What's its signature? Does the error occur when defining the decorated function or when calling it? How are you calling the decorator? I think functools.partial with a keyword argument (callback) is necessary.$endgroup$
– Solomon Ucko
23 mins ago
$begingroup$
@LaurențiuAndronache Interesting.... What is
b? The decorated function or the callback? What's its signature? Does the error occur when defining the decorated function or when calling it? How are you calling the decorator? I think functools.partial with a keyword argument (callback) is necessary.$endgroup$
– Solomon Ucko
23 mins ago
add a comment |
Laurențiu Andronache is a new contributor. Be nice, and check out our Code of Conduct.
Laurențiu Andronache is a new contributor. Be nice, and check out our Code of Conduct.
Laurențiu Andronache is a new contributor. Be nice, and check out our Code of Conduct.
Laurențiu Andronache is a new contributor. Be nice, and check out our Code of Conduct.
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f215135%2fa-simple-decorator-written-as-a-class-which-counts-how-many-times-a-function-ha%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
$begingroup$
FWIW, the
unittest.mockmodule may be of use for this sort of thing. Note that theMockclasses are currently not typed due to issues withmypy. You can see the previous, typed code in the link and I take a full look once I get to a computer.$endgroup$
– Solomon Ucko
Mar 10 at 15:29
$begingroup$
I updated the "decorator in action" part to make it more readable. I don't know what you're saying there about mock typing though.
$endgroup$
– Laurențiu Andronache
Mar 10 at 15:39