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.