import sys
import warnings
from gc import get_referrers
from weakref import WeakKeyDictionary
from types import FunctionType, CodeType, CellType
 
referrer_types = {
    CodeType: [FunctionType],
    FunctionType: [dict, CellType],
    CellType: [tuple],
    tuple: [FunctionType],
    dict: [type]
}
 
def get_class(obj):
    if next_types := referrer_types.get(type(obj)):
        for referrer in get_referrers(obj):
            if issubclass(referrer_type := type(referrer), type):
                return referrer
            if referrer_type in next_types and (cls := get_class(referrer)):
                return cls
 
def make_protector(action):
    def decorator(func):
        def wrapper(*args, **kwargs):
            func_code = func.__code__
            if func_code not in class_of:
                class_of[func_code] = get_class(func_code)
            caller_code = sys._getframe(1).f_code
            if caller_code not in class_of:
                class_of[caller_code] = get_class(caller_code)
            if not (class_of[caller_code] and
                    class_of[func_code] in class_of[caller_code].mro()):
                action(func.__qualname__)
            return func(*args, **kwargs)
        class_of = WeakKeyDictionary()
        return wrapper
    return decorator
 
@make_protector
def protected(name):
    warnings.warn(f'{name} is protected.', stacklevel=3)
 
@make_protector
def private(name):
    raise Exception(f'{name} is private.')
 
class C:
    def A_factory(self):
        class A:
            @protected
            def protected_func(self):
                print('protected is running')
                self.private_func()
 
            @private
            def private_func(self):
                print('private is running')
 
        return A
 
a = C().A_factory()
 
class B(a):
    def foo(self):
        super().private_func()
 
b = B()
b.foo()  # Runs without complaint because of inheritance
b.protected_func()  # Runs with protected warning
b.private_func()  # Raises Exception