20

How do I introspect A's instance from within b.func() (i.e. A's instance's self):

class A():
    def go(self):
        b=B()
        b.func()

class B():
    def func(self):
        # Introspect to find the calling A instance here
Jonathan Livni
  • 101,334
  • 104
  • 266
  • 359
  • Note that in Python 2.x, it is best to inherit from `object` rather than nothing so that you are using *new-style classes*. – Mike Graham Sep 01 '11 at 15:18
  • There is no portable way to do this (the only way to do it in CPython is by grabbing the parent frame and inspecting it, but not all Python implementations expose this data) – Nick Bastin Sep 01 '11 at 15:42

4 Answers4

42

In general we don't want that func to have access back to the calling instance of A because this breaks encapsulation. Inside of b.func you should have access to any args and kwargs passed, the state/attributes of the instance b (via self here), and any globals hanging around.

If you want to know about a calling object, the valid ways are:

  1. Pass the calling object in as an argument to the function
  2. Explicitly add a handle to the caller onto b instance sometime before using func, and then access that handle through self.

However, with that disclaimer out of the way, it's still worth knowing that Python's introspection capabilities are powerful enough to access the caller module in some cases. In the CPython implementation, here is how you could access the calling A instance without changing your existing function signatures:

class A:
    def go(self):
        b=B()
        b.func()

class B:
    def func(self):
        import inspect
        print inspect.currentframe().f_back.f_locals["self"]
        
if __name__ == "__main__":
    a = A()
    a.go()

Output:

<__main__.A instance at 0x15bd9e0>

This might be a useful trick to know about for debugging purposes. A similar technique is even used in stdlib logging, here, so that loggers are able to discover the source code/file name/line number/function name without needing to be explicitly passed that context. However, in normal use cases, it would not usually be a sensible design decision to access stack frames in the case that B.func actually needed to use A, because it's cleaner and easier to pass along the information that you need rather than to try and "reach back" to a caller.

wim
  • 338,267
  • 99
  • 616
  • 750
  • 1
    This is fragile (there are plenty of cases we could construct with which it does not work) and non-portable (`inspect.currentframe` is an implementation detail). – Mike Graham Sep 01 '11 at 15:20
  • the OP asked for Introspection. this is the only way i know how to do it without changing their interfaces. – wim Sep 01 '11 at 15:26
  • It turns out the OP didn't need introspection and could and did implement a sane API. The important thing isn't obeying the question as asked; the important thing is helping the person asking. – Mike Graham Sep 01 '11 at 15:28
  • 5
    +1 for answering the question as asked ( regardless of whether it is wise to do it this way ). Where will this not work? I suspect the "disclaimer" about `currentframe()` is alluding to Stackless ( not to Jython or Iron Python )....can anyone confirm this? – Nick Perkins Sep 01 '11 at 15:32
  • @Mike simple example of a situation where this knowledge might be helping the person asking: code crashed in body of `B.func`. user invokes debugger and wants to inspects some attributes of the caller module. it doesn't necessarily have to be about implementing a sane API or not. if it was a n00b maybe I would explain more, but in this case the OP's reputation is higher than my own. – wim Sep 01 '11 at 15:40
  • @wim, in that case the person wants to use pdb, not to rewrite their code to have a half-baked implementation of pdb interspersed in the application. – Mike Graham Sep 01 '11 at 15:45
  • 1
    @Mike you're missing the point, i'm not saying you rewrite you code instead of `pdb`. suppose you are in ipython, and you get an unhandled exception in `B.func`. then you can use the magic `%debug` and you're in the body of `func`. from there you have the full power of the interpreter, and you could type `import inspect` and do whatever introspection might help you. *just because a tool can be abused it doesn't mean it should never be used* – wim Sep 01 '11 at 15:51
  • @wim, I never made a claim that `inspect.currentframe` should never be used. I did claim, and still believe, that this solution *does not help* the original poster. – Mike Graham Sep 01 '11 at 15:59
  • @Mike I am not going to have this argument with you. It's up to the asker whether this knowledge is helpful or not for their particular situation. Perhaps you should read the title of this question again. – wim Sep 01 '11 at 16:06
  • 3
    @wim Actually, it's up to the community whether this knowledge is helpful to more than just the poster. That's why it's getting downvotes. These are a signal that what you posted is a poor solution for the OP to use. – Wilduck Sep 01 '11 at 16:18
  • 1
    @Wilduck you can not say what the 'solution' is when the problem has not been defined. they have simply asked for introspection, the question is even tagged `python` `introspection` - and i can't see any other answer here which actually _does_ introspection. i can only guess the downvotes are due to it not being made clear how/why the function being aware of it's caller is in poor taste, which might lead other people reading the answer to misuse and abuse pythons introspection powers. i'll edit my answer to make that matter more clear and see if any of the downvotes get changed. – wim Sep 01 '11 at 23:34
  • @wim, Clearly this escalated a bit from this narrow discussion. If you'd like to discuss our interactions and ideas of how to make SO the best resource it can be, I started a room at http://chat.stackoverflow.com/rooms/3077/room-for-mike-graham-and-wim – Mike Graham Sep 02 '11 at 00:52
  • 29
    I find this all astonishing. Do people think that Python's ability to do introspection should be kept secret just because it's usually not the right thing to do? Only one answer gave the right facts, and people downvote it? Facts are facts. Do the downvoters object to revealing this "secret knowlege"? You all have no idea why the OP is looking for this, and therefore you can not judge that it is wrong. There are certainly possible situations where this type of introspection would be valuable. So, imho, ok to warn against it, but should not downvote ans for revealing how it can be done. – Nick Perkins Sep 02 '11 at 01:26
  • 1
    Sometimes this is the only way to solve an issue actually and I don't see why people seem to ignore this. What if you want to have a generic function `fatal_error(msg)` for example which shows the caller? I'm pretty sure you don't want to call `fatal_error(obj, msg)` – Pithikos Oct 15 '14 at 12:12
  • @NickPerkins - I completely agree with you. Seems as if too many people are too judgy. I also have differing opinions on coding right-versus-wrong opinions, but Ill eave those out. I personally am building an application that has both command line commandline functionality as well as GUI, and I have replicated "logger" interfaces in a GUI based logger window, and which to use callback tracing to display the Object, and Method names to MY logger without having to have a slew of if statements detecting whether Im using gui or not!!! – trumpetlicks Jan 08 '21 at 12:19
5

You pass it to b.func() as an argument.

Benjamin Peterson
  • 19,297
  • 6
  • 32
  • 39
  • That was my workaround, however I wanted to find out how to refrain from that – Jonathan Livni Sep 01 '11 at 15:15
  • 7
    That's not a workaround. That's how you give other functions context. – Benjamin Peterson Sep 01 '11 at 15:17
  • I already implemented it your way and carried on, but I'm curios to know... besides I think that in my specific design it might be more elegant – Jonathan Livni Sep 01 '11 at 15:20
  • 2
    What is your specific design? I seriously doubt it, though. "Explicit is better than implicit" – Benjamin Peterson Sep 01 '11 at 15:21
  • 1
    agreed. technically you can get the caller, but `b.func()` really has no business knowing who called it – wim Sep 01 '11 at 15:22
  • It would almost certainly not be "more elegant" more likely it would create some tight coupling, making your code harder to modify in the future. – Wilduck Sep 01 '11 at 16:15
  • Fine, you win :) I'm convinced - Even if I'll know how to do it, I won't change my code! I agree with you 100% (I probably did from the beginning) – Jonathan Livni Sep 01 '11 at 18:17
  • I love these philosophical arguments! One person says explicit (i.e. specific) is better, and another person says explicit is not elegant (and somehow more difficult to modify). I have worked projects on BOTH ends of that spectrum, and the one thing that I have seen (especially when looking at Java and Kotlin) is that elegance does NOT mean easier to read, modify, or understand. Invariably, it has made reading code extremely difficult, because instead of KNOWING what is being passed, sometimes your are just passing something like "instance()" which tells you NOTHING (not even high level). – trumpetlicks Jan 08 '21 at 12:27
  • There is no singular RIGHT way of doing anything. There are times for elegance, and times for bare git-er-done. There are times for using introspection (logging), and times when it should never be used. I don't prescribe to any "rules". Just as I've heard that a code file should never be larger than 1K lines, I disagree. If you are working on a 5 million line OS, then now all you've done is traded out complexity of SLOC per file to number of files (1K lines per file, but you now have 5K files of complexity to sort through for edits). Let's stop prescribing rules, and do what makes sense! – trumpetlicks Jan 08 '21 at 12:32
1

Do this by refactoring your code to work like

class A():
    def go(self):
        b = B(self)
        b.func()

class B():
    def __init__(self, a):
        self.a = a

    def func(self):
        # Use self.a

or

class A():
    def go(self):
        b = B()
        b.func(self)

class B():
    def func(self, a):
        # a
Mike Graham
  • 73,987
  • 14
  • 101
  • 130
  • Actually there are many `A`s that may call `B`'s instance. I wouldn't like to keep them all in `B`'s instance – Jonathan Livni Sep 01 '11 at 15:18
  • @Johnathan, In the example snippet, Bs were made by an A, and it wasn't clear they were kept and used by many different As. It sounds like you may want to pass the right A precisely when you cal B.func, like in my second example. – Mike Graham Sep 01 '11 at 15:23
0

I agree with Benjamin - pass it to b.func() as an argument and don't introspect it!!!!

If your life really depends on it, then I think you can deduce the answer from this answer.

Community
  • 1
  • 1
Jonathan Livni
  • 101,334
  • 104
  • 266
  • 359