0

This recent question raised my interest whether we can simulate private variable behaviour in python. I attempted to do just that with the help of inspect module.

My plan was to check whether the __getattribute__ method is being called from the inside the class, similar to how this answer does it.

From my understanding, I can get the next outer frame object using f_back until I eventually arive at the class from where it is being called. To my surprise, all calls of frame.__self__ resulted in AttributeError until I eventually arrived at None by excessively calling f_back:

import inspect

class MyClass:
    def __init__(self):
        self.variable = 1

    def __getattribute__(self, attr):
        frame = inspect.currentframe()

        try:
            while frame != None:
                try:
                    frame.__self__ # Can I do that?
                    print("Yes I can")
                except AttributeError:
                    print("Nope")

                frame = frame.f_back

        finally:
            del frame

        return super().__getattribute__(attr)

    def get_variable(self):
        return self.variable

A = MyClass()
print(A.get_variable())

Since all I'm getting is "Nope" even though the getter calls __getattribute__ from inside the class (and I would assume going back frame by frame I should arrive at the class from which it's beeing called) I can think of two possibilities why this isn't working.

  1. Something has changed since the answer was posted?
  2. I'm missing a key detail

Since my code is super similar to the code posted in the answer mentioned above, I'll go and assume it has something to do with the versions of python.

So my question is, how can I check from what class is the method beeing called? Is there some other way to do it? But most importantly why isn't this code working?

  • 1
    Why even bother to go through all this rigamarole, I can just do `object.__getattr__(MyClass(), 'variable')`. Might as well just use double-underscores. Or just a *single underscore* as is conventional. – juanpa.arrivillaga Feb 26 '19 at 01:35
  • Why do you expect frame objects to even have a `__self__` attribute? That's not a thing. Frame objects don't have such an attribute. – user2357112 Feb 26 '19 at 01:43
  • That being said, I can't find any documentation stating that frame objects will have a `__self__` attribute. Oh, that was supposed to be `object.__getattribute__(MyClass(), 'variable')` – juanpa.arrivillaga Feb 26 '19 at 01:44
  • Because an upvoted answer did just that. I don't believe code that doesn't work would be upvoted by the community. This code is just a copy of code from an answer and I'm assuming it's producing different output. – Mantas Kandratavičius Feb 26 '19 at 01:46
  • 1
    "I don't believe code that doesn't work would be upvoted by the community." - that's overly optimistic of you. Broken code gets upvoted all the time. – user2357112 Feb 26 '19 at 01:48
  • Even assuming that the answer that I linked was never working to begin with (which I doubt!), and that's not the way to do it, my question remains open - what would be the correct way to check from what class is the method being called. – Mantas Kandratavičius Feb 26 '19 at 01:53
  • It was closer to working in the [original revision](https://stackoverflow.com/revisions/53772947/1), which at least didn't try to use completely made-up nonexistent attributes, but even that revision had a lot of problems, and not just the problems that result naturally from the task being impossible. For example, it uses `==` where it needs `is`, and it's "instance-private" instead of "class-private". – user2357112 Feb 26 '19 at 02:03
  • 1
    There's a lot of upvoted crap on this site. You can't assume an answer is good just because it has upvotes. – user2357112 Feb 26 '19 at 02:04

1 Answers1

1

Despite the task beeing deemed impossible, it seems like the behavior I want can be easily achieved by simply replacing __self__ with f_locals['self'].

import inspect

class MyClass:
    def __init__(self):
        self.variable = 1

    def __getattribute__(self, attr):
        frame = inspect.currentframe()

        try:
            locals = frame.f_back.f_locals

            if locals.get('self', None) is self:
                print("Called from this class!")

            else:
                print("Called from outside of class!")

        finally:
            del frame

        return super().__getattribute__(attr)

    def get_variable(self):
        return self.variable

A = MyClass()
print(A.get_variable())

The current code was not supposed to function since the frame object itself does not have the __self__ attribute and does not return the class of that frame - which I assumed it does from the aforementioned answer.

  • You'll find that unlike proper private variable functionality, this allows access from subclass and superclass methods, but forbids access from staticmethods, classmethods, or instance methods called from a different instance. Also, it does nothing to prevent *setting* attributes from outside the class, and it can easily be bypassed by calling `object.__getattribute__`, among many other ways. Overall, this is strictly worse than just prefixing attributes with `__`, in many ways. – user2357112 Feb 26 '19 at 04:03
  • This question was never about how good this method is, merely about the error that I ran into while attempting to implement it. There could be a million other uses for figuring out from which class a function is being called from, and this was simply one of them. – Mantas Kandratavičius Feb 26 '19 at 04:09
  • Except... it doesn't figure out what class the access happened from. At best, if all goes right, you have the instance whose method the access happened from. That doesn't tell you the class. – user2357112 Feb 26 '19 at 04:10
  • To try to find the class, you could inspect the calling frame's code object and try to figure out what class it was defined inside. This has plenty of failure points of its own, though. – user2357112 Feb 26 '19 at 04:17
  • Well this solution produces the exact output that I want and is so far the only solution. If there are any other ways to achieve the same goal that are superior in other ways, I'll gladly mark the answer as the better answer. – Mantas Kandratavičius Feb 26 '19 at 04:27