Mimic C#'s nameof() in Python
I'm not sure why you'd want to do this, however your code is really hard to understand.


You merge your functions together better.

name_of finds the parent frame, and then also goes on to handle creating and caching scope, along with finding the next name. This is defiantly breaking single responsibility.

I'd also merge all your one/two line functions into _cache. This is so we can tell what your code is actually doing.

What I'd do use:


the class for _Scopes,
a _scope function to build the scope,
a _get_frame function to get the current frame, and
the name_of function, to use _get_frame, and then call _Scopes.

To create _get_frame I'd move the try into it's own function.
To create _scope I'd:


Merge most of your code into _cache.
I manually perform the dropwhile and takewhile loops.
Move the code from _is_ident and _all_attr into the function.
Merge _all_attr with the takewhile loop. This is as you raise if i.opname in _LOAD. Unless it's LOAD_ATTR.
Remove the instructions list. It's not needed with the above, and you only want the last value anyway.
Don't mutate _Scopes from here.
Make this a generator of names, don't mutate anything.

To create _Scopes I'd:


Create a class, that inherits from collections.UserDict.
Move the rest of your name_of code into a next_name method.
You also can change the code so that it's better too.


Get the scope by using self.get. This means if it's not None then you can return without a second look-up.
Try to get the next(scope, None) if it's none, then you can implicitly remove the scope, by falling through to the creating code. Otherwise you can return the name.
If you wrap the call to _scope in a try, then you can perform the _Scopes.clear() function in the except clause.




And so I'd change your code to (untested):

import dis as _dis
from collections import UserDict

__all__ = ['name_of']

_ATTR = 'LOAD_ATTR'
_LOAD = set(
    'LOAD_CONST',
    'LOAD_NAME',
    'LOAD_GLOBAL',
    'LOAD_FAST',
    'LOAD_CLOSURE',
    'LOAD_DEREF',
    'LOAD_CLASSDEREF'
)


def _scope(frame):
    bytecode = iter(_dis.Bytecode(frame.f_code))
    while True:
        for instruction in bytecode:
            if instruction == 'name_of':
                break
        else:
            break

        name = None
        for instruction in bytecode:
            if instruction.opname != _ATTR:
                if instruction.opname not in _LOAD:
                    break
                raise SyntaxError('Invalid: {!r}'.format(name))
            name = instruction

        if name is None or not name.argrepr.isidentifier():
            raise SyntaxError('Invalid: {!r}'.format(name))
        yield name.argrepr


class _Scopes(UserDict):
    def next_name(self, frame):
        frame_id = id(frame)
        scope = self.get(frame_id, None)
        if scope is not None:
            name = next(scope, None)
            if name is not None:
                return name
        try:
            scope = self.data[frame_id] = _scope(frame)
        except SyntaxError:
            self.clear()
            raise
        return next(scope)


_scopes = _Scopes()


def _get_frame():
    try:
        raise Exception
    except Exception as e:
        return exc.__traceback__.tb_frame.f_back


def name_of(_):
    frame = _get_frame().f_back
    return _scopes.next_name(frame)