I'm writing an incredibly hackish piece of not-quite-production code in Python, and I need some way of detecting whether a _XYZ__foo
attribute access was called from a method defined on a class called /_*XYZ/
. It's not quite as easy as that, though, since I need to detect the original method access in case anything has overridden __getattribute__
and called super()
.
I'm bad at explaining, so... the rules are similar to Java's private
, except I want to prevent cheating. (Yes, I know that this is counter to the Python philosophy; bear with me here.)
My current plan of attack is:
re.compile('_(?P<class>.*?)__(?P<name>.*)')
to detect the name of the class (with preceding _
s stripped).super
chain with sys._getframe(n)
to find out where the attribute access was.I might be able to do this by emulating super
's walking of the MRO, but I'd much rather rely on detection because checking what's been called by super
and what's been called by user functions is hard.
So, to my actual question. Given a frame, how can I detect which class a method is associated with? If I had access to the function object I could do f.__qualname__[:-1-len(f.__name__)]
, but I don't (or, at least, I don't think I do). As is, I have no idea how to do this!
Here's a simple example that demonstrates what I want to do:
import sys
import re
import itertools
import builtins
from builtins import __build_class__
def build_class(func, name, *bases, metaclass=None, **kwds):
if bases[-1] is object:
bases = bases[:-1]
bases += HackishClass, object
if metaclass is None:
return __build_class__(func, name, *bases, **kwds)
return __build_class__(func, name, *bases, metaclass=metaclass, **kwds)
private_regex = re.compile('_(?P<class>.*?)__(?P<name>.*)')
class HackishClass:
__slots__ = ()
def __getattribute__(self, key):
match = private_regex.match(key)
if match is not None:
for depth in itertools.count(1):
frame = sys._getframe(depth)
if ...: # snip
# Check for the original attribute access here.
break
class_name = ... # HERE! MAGIC GOES HERE!
if class_name != match['class']:
raise AttributeError("This is private! Keep out.")
return super().__getattribute__(key)
builtins.__build_class__ = build_class
As far as I know, there's no way to obtain the method where the attribute access occurred directly from a frame object. We can, however, obtain that method's code object. We can then search the object's MRO until we find the method to which that code object belongs.
private_regex = re.compile('_(?P<class>.*?)__(?P<name>.*)')
class HackishClass:
__slots__ = ()
def __getattribute__(self, key):
match = private_regex.match(key)
if match is None:
# not a private attribute, no problem
return super().__getattribute__(key)
# obtain the code object of the calling function
calling_codeobj = inspect.currentframe().f_back.f_code
# iterate the MRO until we find a class with the name from `key`
classname = match.group('class')
for cls in type(self).mro():
if cls.__name__ != classname:
continue
# check if the code object belongs to a method defined in this class
for thing in vars(cls).values():
if getattr(thing, '__code__', None) is calling_codeobj:
# found it! allow the attribute access
return super().__getattribute__(key)
raise AttributeError("This is private! Keep out.")
A small demonstration:
class Foo:
def __init__(self):
self.__foo = 5
print(self.__foo)
f = Foo() # prints 5
print(f._Foo__foo) # throws AttributeError