Can an object check the name of the variable to which it is assigned?

In Python, is there a way to instantiate an object to see the name of the variable to which it is assigned? Take for example the following:

class MyObject(object):
    pass

x = MyObject()

Is it possible that MyObject sees that it is assigned the name of the variable x at any point? How is this __init__ method?

+8
source share
5 answers

No. Objects and names live in different dimensions. A single object can have many names during its lifetime, and it is impossible to determine which one can be the one you want. Even here:

class Foo(object):
    def __init__(self): pass

x = Foo()

two names designate the same object ( selfwhen __init__executed, xin the global area).

+4
source

Yes it is possible*. However, the problem is more complicated than it seems at first glance:

  • .
  • .
  • .

, , , - :

import gc, inspect

def find_names(obj):
    frame = inspect.currentframe()
    for frame in iter(lambda: frame.f_back, None):
        frame.f_locals
    obj_names = []
    for referrer in gc.get_referrers(obj):
        if isinstance(referrer, dict):
            for k, v in referrer.items():
                if v is obj:
                    obj_names.append(k)
    return obj_names

- , , / . , .

* Cpython

+8

, . , jsbueno's, .

, , , Python (. ). , - ( ). Python 3.4+ dis.get_instructions(), . - .

import inspect
import dis

def take1(iterator):
    try:
        return next(iterator)
    except StopIteration:
        raise Exception("missing bytecode instruction") from None

def take(iterator, count):
    for x in range(count):
        yield take1(iterator)

def get_assigned_name(frame):
    """Takes a frame and returns a description of the name(s) to which the
    currently executing CALL_FUNCTION instruction value will be assigned.

    fn()                    => None
    a = fn()                => "a"
    a, b = fn()             => ("a", "b")
    a.a2.a3, b, c* = fn()   => ("a.a2.a3", "b", Ellipsis)
    """

    iterator = iter(dis.get_instructions(frame.f_code))
    for instr in iterator:
        if instr.offset == frame.f_lasti:
            break
    else:
        assert False, "bytecode instruction missing"
    assert instr.opname.startswith('CALL_')
    instr = take1(iterator)
    if instr.opname == 'POP_TOP':
        raise ValueError("not assigned to variable")
    return instr_dispatch(instr, iterator)

def instr_dispatch(instr, iterator):
    opname = instr.opname
    if (opname == 'STORE_FAST'              # (co_varnames)
            or opname == 'STORE_GLOBAL'     # (co_names)
            or opname == 'STORE_NAME'       # (co_names)
            or opname == 'STORE_DEREF'):    # (co_cellvars++co_freevars)
        return instr.argval
    if opname == 'UNPACK_SEQUENCE':
        return tuple(instr_dispatch(instr, iterator)
                     for instr in take(iterator, instr.arg))
    if opname == 'UNPACK_EX':
        return (*tuple(instr_dispatch(instr, iterator)
                     for instr in take(iterator, instr.arg)),
                Ellipsis)
    # Note: 'STORE_SUBSCR' and 'STORE_ATTR' should not be possible here.
    # `lhs = rhs` in Python will evaluate `lhs` after `rhs`.
    # Thus `x.attr = rhs` will first evalute `rhs` then load `a` and finally
    # `STORE_ATTR` with `attr` as instruction argument. `a` can be any 
    # complex expression, so full support for understanding what a
    # `STORE_ATTR` will target requires decoding the full range of expression-
    # related bytecode instructions. Even figuring out which `STORE_ATTR`
    # will use our return value requires non-trivial understanding of all
    # expression-related bytecode instructions.
    # Thus we limit ourselfs to loading a simply variable (of any kind)
    # and a arbitary number of LOAD_ATTR calls before the final STORE_ATTR.
    # We will represents simply a string like `my_var.loaded.loaded.assigned`
    if opname in {'LOAD_CONST', 'LOAD_DEREF', 'LOAD_FAST',
                    'LOAD_GLOBAL', 'LOAD_NAME'}:
        return instr.argval + "." + ".".join(
            instr_dispatch_for_load(instr, iterator))
    raise NotImplementedError("assignment could not be parsed: "
                              "instruction {} not understood"
                              .format(instr))

def instr_dispatch_for_load(instr, iterator):
    instr = take1(iterator)
    opname = instr.opname
    if opname == 'LOAD_ATTR':
        yield instr.argval
        yield from instr_dispatch_for_load(instr, iterator)
    elif opname == 'STORE_ATTR':
        yield instr.argval
    else:
        raise NotImplementedError("assignment could not be parsed: "
                                  "instruction {} not understood"
                                  .format(instr))

. C- Python , , script. . Python f(), a = g(). g() C- b = f2(). f2() , a b, script C. ( , , : P)

:

class MyItem():
    def __init__(self):
        self.name = get_assigned_name(inspect.currentframe().f_back)

abc = MyItem()
assert abc.name == "abc"
+2

, , . ".py", , - , , , " ".

, , - inspect.currentframe - "f_lineno", , ( __init__). inspect.filename .

, "=", , , .

from inspect import currentframe, getfile

class A(object):
    def __init__(self):
        f = currentframe(1)
        filename = getfile(f)
        code_line = open(filename).readlines()[f.f_lineno - 1] 
        assigned_variable = code_line.split("=")[0].strip()
        print assigned_variable

my_name = A()
other_name = A()

, , assignemtn, , , for, , -   , .

Botton line: , - - , , , collections.namedtuple

" ", , - , , :

class A(object):
  def __init__(self, name):
      self.name = name

x = A("x")

, , - . - Python , "=", , . , statemtns, Python, for, with, def class. , , specfically , , "" .

Focus on the instructions def. It usually creates a function. But with the help of a decorator, you can use "def" to create any object - and the name used for the function available to the constructor:

class MyObject(object):
   def __new__(cls, func):
       # Calls the superclass constructor and actually instantiates the object:
       self = object.__new__(cls)
       #retrieve the function name:
       self.name = func.func_name
       #returns an instance of this class, instead of a decorated function:
       return self
   def __init__(self, func):
       print "My name is ", self.name

#and the catch is that you can't use "=" to create this object, you have to do:

@MyObject
def my_name(): pass

(This last way to do this can be used in production code, unlike the one that resorts to reading the source file)

+1
source

Here is a simple function to achieve what you want, provided that you want to extract the name of the variable to which the instance is assigned from the method call :

import inspect

def get_instance_var_name(method_frame, instance):
    parent_frame = method_frame.f_back
    matches = {k: v for k,v in parent_frame.f_globals.items() if v is instance}
    assert len(matches) < 2
return list(matches.keys())[0] if matches else None

Here is a usage example:

class Bar:
    def foo(self):
        print(get_instance_var_name(inspect.currentframe(), self))

bar = Bar()
bar.foo()  # prints 'bar'

def nested():
    bar.foo()
nested()  # prints 'bar'

Bar().foo()  # prints None
+1
source

All Articles