2

Consider the following code sample showing a simple class that allows for method chaining by returning self.

import inspect

class classname(object):
    def __init__(self):
        self.hold = None

    def funcname(self):
        self.hold = 'a'
        return self

    def method(self, c):
        self.hold = self.hold * c
        return self


c = classname().funcname().method(10)

What I am trying to do is inspect c in a manner so that i can see that funcname and method was called on this class.

securisec
  • 3,435
  • 6
  • 36
  • 63
  • 1
    You cannot, the object retains no memory of this, you will somehow have to keep track of it yourself. This sounds like a fundamental problem with your design if this is something you have to know. – juanpa.arrivillaga Nov 11 '19 at 23:13
  • Thanks for design feedback @juanpa.arrivillaga. But you are making assumptions about my use case for such a post. If it cant be done, I understand, but there has to be a way to get this information. – securisec Nov 11 '19 at 23:21
  • I didn't say it was impossible, just that you have to keep track of this yourself. You must manually keep track of what is being called using the internal state of your object, e.g. a list of the calls being made, you could write a decotrator to factor out some of the biolerplate, and to be more explicit. – juanpa.arrivillaga Nov 11 '19 at 23:27

2 Answers2

1

It can be done, but not through inspect, since the instance itself doesn't remember that history. You have to add code to maintain the calls manually.

class classname(object):
    def __init__(self):
        self.hold = None
        self.calls = []

    def funcname(self):
        self.hold = 'a'
        return self

    def method(self, c):
        self.hold = self.hold * c
        return self

    def __getattribute__(self, name):
        self_calls = object.__getattribute__(self, "__dict__")["calls"]
        self_calls.append(name)
        return object.__getattribute__(self, name)


c = classname().funcname().method(10)
print(c.calls)
wim
  • 338,267
  • 99
  • 616
  • 750
0

I have written a library for this sort of thing, which I've just put on GitHub in case it is useful to anybody.

It works by wrapping functions, classes or objects with a proxy which logs every interaction. This includes not just the names of the methods, but also their arguments and return values, and which methods called which other methods (in a tree).

Usage:

from tracer import tracer

@tracer
class classname(object):
    def __init__(self):
        self.hold = None

    def funcname(self):
        self.hold = 'a'
        return self

    def method(self, c):
        self.hold = self.hold * c
        return self

    def recursive_method(self, n):
        if n > 0:
            self.recursive_method(n - 1)
            self.recursive_method(n - 2)

c = classname().funcname().method(10)
c.recursive_method(3)

c.print_call_tree()

Output:

* +-> __init__()
  |
  +-> funcname() +-> <__main__.classname object at 0x0000000002A9EBA8>
  |
  +-> method(10) +-> <__main__.classname object at 0x0000000002AE2160>
  |
  +-> recursive_method(3) +-> recursive_method(2) +-> recursive_method(1) +-> recursive_method(0)
                          |                       |                       |
                          |                       |                       +-> recursive_method(-1)
                          |                       |
                          |                       +-> recursive_method(0)
                          |
                          +-> recursive_method(1) +-> recursive_method(0)
                                                  |
                                                  +-> recursive_method(-1)

Fair warning: it is a bit brittle, for example you can see the log shows different ids for the object returned by funcname and method, even though these should be the same object with the same id.

kaya3
  • 47,440
  • 4
  • 68
  • 97