4

Is there a way to know within a function if the function has been called by itself or assigned to a variable with = ?

I would like to do something like this:

def func():
    if 'assigned with equal':
        return 5
    else:
        print 'not assigned'

that would give those outputs:

func()
-> 'not assigned'
a = func()
a
-> 5
Seb
  • 1,765
  • 9
  • 23
  • 10
    It's not possible. – Łukasz Rogalski Apr 19 '17 at 14:30
  • 1
    I would not try to do this. Have 1 version that returns, then print the return value if necessary at the call site. In general, you should try to have functions consistently return the same type, or at the very least consistently either return a usable value, or not. – Carcigenicate Apr 19 '17 at 14:30
  • 1
    in case of class you can do it, by assigning a class variable. – Thameem Apr 19 '17 at 14:33
  • 1
    there's no real reason that a function on its own should need to know that. – chatton Apr 19 '17 at 14:35
  • @chatton: For optimization reasons. If calculating return value is non-trivial, you might want to skip it altogether if the value is never used anyway. –  Apr 19 '17 at 14:46
  • 3
    @doublep I would think that that would be the responsibility of an object who cared about the function, a function itself doesn't need to know what it's being used for or when it should be used, the object using it should worry about that. – chatton Apr 19 '17 at 14:51
  • @chatton: So, because you'd do it differently, it doesn't classify as a "real reason"? –  Apr 19 '17 at 14:57
  • _For optimization reasons. If calculating return value is non-trivial, you might want to skip it altogether if the value is never used anyway._ Seems like you need two different functions. Likely your function do too much. – Łukasz Rogalski Apr 19 '17 at 16:58
  • I've just realized that the question doesn't match the provided code. So what are you _really_ asking here? If the return value is assigned to a name or the function called via the name it was defined with and not any other way? – BlackJack Apr 20 '17 at 18:47
  • @BlackJack I am not sure how to formulate the title and sentence in the question better. I wanted to know if it was possible, in any way, to write a function that could behave like in the specific example I gave. – Seb Apr 20 '17 at 20:40

2 Answers2

2

Yes, there is a way to do this, though getting it right will be tricky. You can use the inspect module to access the call stack. This allows you to see what the code looks like that called the function.

The stack looks something like this:

[(<frame object at 0x107de1d38>, 'path/to/function/file.py', 6, 'func', ['\tstack = inspect.stack()\n'], 0), (<frame object at 0x107d34050>, 'path/to/calling/file.py', 17, '<module>', ['func()\n'], 0)]

Notice the second to last entry: ['func()\n']. This is showing the code that calls your function. Even though the name of the function shows up elsewhere in the stack, it always shows the actual name of the function no matter how it is called. So you have to do a little work on your own to determine whether or not the call was made directly or through an assigned variable.

This is the only way to get the function name. Python does not have a feature to retrieve the function name from within the function itself.

To make it clear that this will be more difficult than just if func_name in stack, I've fleshed out the function a little bit to show a couple of real world examples of how a function might be called and what it would look like. Because the function can't determine its own name, it is hardcoded at the top of the function.

import inspect


def func(var=None, default=''):
    my_name = 'func'
    stack = inspect.stack()
    func_call = stack[-1][4][0]
    print func_call.rstrip()  # `func_call` contains a trailing newline

    # logic to determine if the name matches
    # ...
    # ...


x = func
func()
return_value = x(var=1)
print func()
if x():
    pass

This prints:

func()
return_value = x(var=1)
print func()
None  # This prints from the call, because `func()` doesn't return anything
if x():
Community
  • 1
  • 1
Darrick Herwehe
  • 3,553
  • 1
  • 21
  • 30
  • How can this be the accepted answer if it doesn't answer the question? To get the name the function was called by and the source line is definitely not enough to determine if the result will be assigned to a name or not. – BlackJack Apr 20 '17 at 13:11
  • @BlackJack It is enough information, you just have to write your own logic for it. The call is there, the name is there. How you determine if the name is in the call is up to you. – Darrick Herwehe Apr 20 '17 at 13:38
  • @BlackJack I didn't require that the function be failproof, this answer gave the elements to produce exactly the result I described in my question. The fact that the function can't be used in other cases is irrelevant to my question but worth being noted. – Seb Apr 20 '17 at 14:49
  • @DarrickHerwehe But the question wasn't if the function name is in the source line from the calling stack frame but if the result of the function call is assigned to a name or not. This assignment doesn't even have to be in that source line. Heck, the source line doesn't even have to be available at all. This is _very_ fragile. – BlackJack Apr 20 '17 at 15:24
  • @BlackJack I don't disagree with what you're saying, but the question is actually `how can I _determine_ if the function is called directly or as a variable`. This is a hacky workaround for something that the language doesn't provide a method to accomplish. Yes, it's fragile, and I don't even recommend its usage, but it's a devil's advocate position against the commenters saying it's not possible. – Darrick Herwehe Apr 20 '17 at 17:34
  • @DarrickHerwehe Okay sorry, I've made the mistake to look at the code in the question which has nothing to do with the _actual_ question. My apologies. – BlackJack Apr 20 '17 at 18:43
0

Here is a running example I wrote based on the accepted answer. We can't assign a new name to the function but it does what I wanted to do

import inspect

# generator function to get all the strings in an iterable object
def descend_strings(obj):
    if hasattr(obj,'__iter__'):
        if type(obj)==dict:
            for key in obj:
                for result in descend_strings(obj[key]):
                    yield result
        else:
            for item in obj:
                for result in descend_strings(item):
                    yield result
    if type(obj)==str:
        yield obj

def func():
    stack = inspect.stack()

    joined_flat_stack_str = ''.join(list(descend_strings(stack)))

    if ('= func()' in joined_flat_stack_str) or ('print func()' in joined_flat_stack_str):
        return 5
    else:
        print 'not assigned'


func()       # 'not assigned'
a = func()   
print a      # 5
print func() # 5

x = func
x()          # 'not assigned'
a = x()      # 'not assigned'
print a      # None
Seb
  • 1,765
  • 9
  • 23
  • It's quite limited. `a, b = spam(), func()` or `x = spam(func())` are cases where the return value is assigned/used but not recognised by your inspection. That `func()` has a really weird and surprising API. – BlackJack Apr 20 '17 at 13:15