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













2












$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









share|improve this question









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, 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















2












$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









share|improve this question









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, 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













2












2








2


1



$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









share|improve this question









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






share|improve this question









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.











share|improve this question









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.









share|improve this question




share|improve this question








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, 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$
    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$
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










1 Answer
1






active

oldest

votes


















1












$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.)






share|improve this answer











$endgroup$








  • 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






  • 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 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$
    @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










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.









draft saved

draft discarded


















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









1












$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.)






share|improve this answer











$endgroup$








  • 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






  • 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 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$
    @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















1












$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.)






share|improve this answer











$endgroup$








  • 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






  • 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 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$
    @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













1












1








1





$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.)






share|improve this answer











$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.)







share|improve this answer














share|improve this answer



share|improve this answer








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 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




    $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$
    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












  • 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






  • 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 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$
    @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







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










Laurențiu Andronache is a new contributor. Be nice, and check out our Code of Conduct.









draft saved

draft discarded


















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.




draft saved


draft discarded














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





















































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







Popular posts from this blog

बाताम इन्हें भी देखें सन्दर्भ दिक्चालन सूची1°05′00″N 104°02′0″E / 1.08333°N 104.03333°E / 1.08333; 104.033331°05′00″N 104°02′0″E / 1.08333°N 104.03333°E / 1.08333; 104.03333

Why is the 'in' operator throwing an error with a string literal instead of logging false?Why can't I use switch statement on a String?Python join: why is it string.join(list) instead of list.join(string)?Multiline String Literal in C#Why does comparing strings using either '==' or 'is' sometimes produce a different result?How to initialize an array's length in javascript?How can I print literal curly-brace characters in python string and also use .format on it?Why does ++[[]][+[]]+[+[]] return the string “10”?Why is char[] preferred over String for passwords?Why does this code using random strings print “hello world”?jQuery.inArray(), how to use it right?

How can we generalize the fact of finite dimensional vector space to an infinte dimensional case?$k[x]$-module and cyclic module over a finite dimensional vector spaceSubspace of a finite dimensional space is finite dimensionalIf V is an infinite-dimensional vector space, and S is an infinite-dimensional subspace of V, must the dimension of V/S be finite? ExplainWhy is an infinite dimensional space so different than a finite dimensional one?base for finite dimensional vector space is not infinite dimensional vector space?Any finite-dimensional vector space is the dual space of anotherHaving Trouble Understanding Meaning Of A Finite-Dimensional Vector SpaceProve that “Every subspaces of a finite-dimensional vector space is finite-dimensional”Ring as a finite dimensional Vector space over a field KQuestion regarding basis and dimension