3

I am (not so) new to python, so I was wondering, is this a valid way to check if a function or callable is "empty"?

def _empty_impl():
    pass

def is_empty_impl(fn, pfn = None):
    empty = None
    pfn = pfn or []
    if isinstance(fn, type):
        fn = fn.__init__
        if fn == object.__init__:
            # We don't really know, though
            empty = True
    if not empty:
        if hasattr(fn, '__code__'):
            # Covers real functions.
            empty = _empty_impl.__code__.co_code == fn.__code__.co_code
        # keep searching for a concrete function
        elif hasattr(fn, '__call__'):
            # Prevents stack overflows when passing some builtins
            # or if someone has made some funny callable implementation
            if fn in pfn:
                raise Exception("loop detected")
            fn = fn.__call__
            pfn.append(fn)
            empty = is_empty_impl(fn, pfn)
    if empty is None:
        raise Exception("object does not look like a function")
    return empty

By "empty" I mean any simple pass-only function implementation, with as many arguments as needed and with no statement or expression in its body, exactly like in:

def empty_function():
    pass

Some tests I did using Python 2.7.11 and 3.5.1 (REPL-optimized!). Everything seems to meet my expectations so far:

def _test(x):
    import sys
    try:
        return x()
    except:
        return "Exception: %s" % str(sys.exc_info()[1])

class A(object):
    def __call__(self): pass
    def empty(self): pass
    def not_empty(self): print("hello world")

class B(object):
    def __init__(self, x = 1): self._x = x
    def __call__(self): print("hello world")

class C(object):
    def __init__(self, *args, **kwargs): pass

_test(lambda: is_empty_impl(A))             # True
_test(lambda: is_empty_impl(A.empty))       # True
_test(lambda: is_empty_impl(A.not_empty))   # False
_test(lambda: is_empty_impl(A.__call__))    # True
_test(lambda: is_empty_impl(A()))           # True
_test(lambda: is_empty_impl(A().empty))     # True
_test(lambda: is_empty_impl(A().not_empty)) # False
_test(lambda: is_empty_impl(A().__call__))  # True
_test(lambda: is_empty_impl(B()))           # False
_test(lambda: is_empty_impl(B()))           # False
_test(lambda: is_empty_impl(C))             # True
_test(lambda: is_empty_impl(C()))           # 'Exception: object does not look like a function'
_test(lambda: is_empty_impl(int))           # True
_test(lambda: is_empty_impl(str))           # True
_test(lambda: is_empty_impl(tuple))         # True
_test(lambda: is_empty_impl(list))          # 'Exception: loop detected'
_test(lambda: is_empty_impl(dict))          # 'Exception: loop detected'

I know how hacky this is, but is there anything obvious I might be missing (e.g. stability of the __code__'s interface), or is there a better (pythonic?) way to do this?

Edit

By @pvg's suggestion, I simplified the implementation. Here it is:

def _empty_impl():
    pass

def _is_empty_impl(fn, pfn = None):
    return _empty_impl.__code__.co_code == fn.__code__.co_code

For my use case, that is enough. Here is how I'm using it.

Flávio Lisbôa
  • 651
  • 5
  • 19
  • 1
    Is there some reason you're not using the `inspect` module? It seems some of the checks have a standard interface there. – pvg May 30 '16 at 03:43
  • @pvg I'm not sure how I would use `inspect` and take care of all the corner cases. I just want to take a callable and reach the concrete function that implements it, so that I can compare its bytecode with that of an empty-bodied function. There are a whole lot of callables in Python... – Flávio Lisbôa May 30 '16 at 04:05
  • I haven't thought of it in as much detail as you have - what cases does your implementation cover that aren't covered by - a check with `inspect.isfunction`, followed by, if needed, a check with `callable` that does the rest of the digging? – pvg May 30 '16 at 04:43
  • @pvg Well, I think it would be more effective to give an example than trying to explain: https://ideone.com/BPjeaQ – Flávio Lisbôa May 30 '16 at 07:44
  • Ok, beside the fact this looks a little crazy, why do you need to deal with generic callables at all? It seems for your use case, methods are enough. Also, now that I've looked it it, your current implementation is obviously wrong by your own test definition. Every single test starting with C is inaccurate. – pvg May 30 '16 at 07:52
  • @pvg I edited the pastebin to include a snippet of code that looks more like the intended usage. – Flávio Lisbôa May 30 '16 at 07:58
  • Still not clear to me why you'd need to handle any kind of callable when you're specifically dealing with methods. More importantly, this reads like you're trying to replicate functionality that either already exists or can be implemented with different tools - property, get/setattr, etc. Perhaps you should write a question describing what you're trying to do and see if you can get help. As it is, your function doesn't really do what your say it should do - as I said, all of the test starting with C and down fail. – pvg May 30 '16 at 08:07
  • @pvg Now that I look into the problem again, I think you are absolutely right. It was an oversight on my part, but I still wonder if simply comparing `function.__code__.__co_code__` is fine (as in https://gist.github.com/flisboac/bfd81543f553c154ff90bb0b9aab4c1e#file-pyattr-py-L10, there is a small usage example at the end of the gist). – Flávio Lisbôa May 30 '16 at 09:03
  • It's sort of fine although it's a CPython implementation detail. Given that in python you have a pretty full range dynamic capabilities and metaprogramming, it seems completely unnecessary. You can programmatically define new methods, decorate methods, define properties, intercept property lookups and method dispatch, further, you can easily annotate methods with your own properties, it seems completely unnecessary to try to detect an empty method for what you are trying to accomplish, at least as I understand it. – pvg May 30 '16 at 09:22
  • @pvg Yes, you're right. I just wanted to avoid creating trivial one-line implementations, but considering that an entirely trivial property would potentially be a violation of encapsulation principles, it would be a better practice to define at least one of the methods and "trivialize" the other. Unless you have a read-only, or write-only, property. – Flávio Lisbôa May 30 '16 at 09:31
  • duplicate of: https://stackoverflow.com/questions/13620542/detecting-empty-function-definitions-in-python/58973125 – Ente Nov 21 '19 at 11:21
  • Possible duplicate of [Detecting empty function definitions in python](https://stackoverflow.com/questions/13620542/detecting-empty-function-definitions-in-python) – Ente Nov 22 '19 at 06:48

0 Answers0