When does Callable lack __module__?
Asked Answered
I

1

10

I am making a logging decorator for functions in python:

import logging
from typing import Callable
from functools import wraps


def function_logging(fn: Callable) -> Callable:
    fn_logger = logging.getLogger(fn.__module__ + '.' + fn.__name__)

    @wraps(fn)
    def wrapper(*args, **kwargs):
        fn_logger.info("Args: {}".format(args))
        fn_logger.info("Kwargs: {}".format(kwargs))
        result = fn(*args, **kwargs)
        fn_logger.info("Return: {}".format(result))
        return result

    return wrapper

PyCharm's static analysis is telling me that I cannot expect a Callable to have the attribute __module__. So far I haven't encountered cases where this fails. Does anyone know under what circumstances one might encounter a Callable without the __module__ attribute?

Inclination answered 9/5, 2019 at 14:58 Comment(5)
As far as I can tell, in general the only things with no __module__ attribute are instances of builtin types. However, none of those are callable. I'm not sure if a metaclass can prevent the addition of a __module__ on a class, its instances or its methods (and even if it can I'm not sure if it's safe). I was able to find a sort of fringe case. Method descriptors for builtin types, like range.count (which is callable) do not have __module__. On instances e.g. range(0).count the __module__ is None, except for special methods e.g. range(0).__str__ where it does not exist.Dictaphone
@jdehesa that could very well be it. Follow up: is there another type than Callable that would be appropriate to exclude these fringe cases?Inclination
I'm not sure there is anything more precise than Callable for that. I'm not even sure if those cases are supposed to behave like that and that's the reason for PyCharm to complain or if there's just no precise answer to the question "what things should or should not have __module__" in general. In any case, you can always do something like (getattr(fn, '__module__', None) or '(none)').Dictaphone
I will note that mypy has no problem with this.Recessional
@Recessional mypy is generally a bit more well behaved than whatever PyCharm is using under the hood.Inclination
V
4

Here's an example:

list.__add__

That's a callable with no __module__. It's the unbound method for list concatenation.

In general, there's no requirement that callables have a __module__. It just has to be possible to call them. There's also no requirement that callables have a __name__, and for most callable types, it doesn't make sense to use functools.wraps with them.

Your decorator requires an instance of the ordinary Python function type; it cannot accept arbitrary callables. If you're going to give it type hints, you should hint it with that type:

def function_logging(fn: types.FunctionType) -> types.FunctionType:
    ...
Varlet answered 6/5, 2020 at 10:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.