views:

722

answers:

3

Is there anyway to do something like this:

class A:
    def foo(self):
        if isinstance(caller, B):
           print "B can't call methods in A"
        else:
           print "Foobar"
class B:
    def foo(self, ref): ref.foo()

class C:
    def foo(self, ref): ref.foo()


a = A();
B().foo(a)    # Outputs "B can't call methods in A"
C().foo(a)    # Outputs "Foobar"

Where caller in A uses some form of introspection to determine the class of the calling method's object?

In the end, I put this together based on some of the suggestions (not sure if it's appropriate or good etiquette to add this):

import inspect
...
def check_caller(self, klass):
    frame = inspect.currentframe()
    current = lambda : frame.f_locals.get('self')
    while not current() is None:
        if isinstance(current(), klass): return True
        frame = frame.f_back
    return False

It's not perfect for all the reasons supplied, but thanks for the responses: they were a big help.

+4  A: 

The caller is always an instance of A. The fact that you're calling it inside a B method doesn't change that. In other words: Insiode B.foo, ref is an instance of A, so calling ref.foo() is a call on A, B is not involved on that call (it could happen top-level).

The only sane way is to pass a reference to self so A can check if it is B or not.

class A(object):
    def foo(self, caller=None):
        if isinstance(caller, B):
           print "B can't call methods in A"
        else:
           print "Foobar"

class B(object):
    def foo(self, ref): ref.foo(self)

class C(object):
    def foo(self, ref): ref.foo(self)

a = A();
B().foo(a)    # Outputs "B can't call methods in A"
C().foo(a)    # Outputs "Foobar"
a.foo()       # Outputs "Foobar"
nosklo
Looking to do this transparently. It's unmanageable when the number of methods increases significantly.
blakef
@blakef: I've edited my question to why this is a bad idea. The call could happen top-level. Trying to detect where the call happened signals misdesign. You should instead share **why on earth** you want to prevent methods of one class to call methods on another.
nosklo
The question became academic in the end. Your solution is the better approach, but I was interested in how it could be done without involving the caller's code.
blakef
+4  A: 

Assuming the caller is a method, then yes you can, by looking in the previous frame, and picking out self from the locals.

class Reciever:
    def themethod(self):
        frame = sys._getframe(1)
        arguments = frame.f_code.co_argcount
        if arguments == 0:
            print "Not called from a method"
            return
        caller_calls_self = frame.f_code.co_varnames[0]
        thecaller = frame.f_locals[caller_calls_self]
        print "Called from a", thecaller.__class__.__name__, "instance"

Üglŷ as heck, but it works. Now why you would want to do this is another question altogether, I suspect that there is a better way. The whole concept of A isn't allowed to call B is likely to be a mistake.

Lennart Regebro
fails in a lot of situations: calling the method top-level, calling the method in a free function, using another name for `self` (use of `self` is just a convention), etc
nosklo
Yup. I agree that passing the caller in is a better solution.
Lennart Regebro
You can see whether caller has arguments from `frame.f_code.co_argcount` and see the name of the first argument in `frame.f_code.co_varnames[0]`.
Denis Otkidach
Good idea! I updated the method to do that. It got even uglier. :) I still think passing the caller is a better solution though. In the ZCA similar things to this is done all the time between different components, and you always get the adapted component as context. Works fine.
Lennart Regebro
This is what I was looking for. It's not beautiful, but interesting.
blakef
A: 

Something like this may meet your needs better:

class A(object):
    def foo(self):
        # do stuff

class B(A):
    def foo(self):
        raise NotImplementedError

class C(A):
    pass

...but it's difficult to say without knowing exactly what you're trying to do.

Jason Baker