对象是否可以检查已分配给它的变量的名称?

时间:2012-01-16 02:30:02

标签: python

在Python中,是否有一种方法可以让对象实例看到它所分配的变量名?以下面的例子为例:

class MyObject(object):
    pass

x = MyObject()

MyObject是否可以在任何时候看到它被分配给变量名x?就像它的__init__方法一样?

7 个答案:

答案 0 :(得分:7)

是的,有可能*。然而,问题比初看起来更难:

  • 可能会为同一个对象分配多个名称
  • 可能有没有名字

无论如何,知道如何查找对象的名称有时可用于调试目的 - 以下是如何执行此操作:

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中

答案 1 :(得分:4)

没有。对象和名称位于不同的维度中。一个对象在其生命周期中可以有许多名称,并且无法确定哪个可能是您想要的名称。即使在这里:

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

x = Foo()

两个名称表示同一个对象(self运行时__init__,全局范围内x)。

答案 2 :(得分:2)

正如许多其他人所说,它无法正常完成。不过jsbueno的灵感,我有一个替代他的解决方案。

与他的解决方案一样,我检查调用者堆栈框架,这意味着它只适用于Python实现的调用者(请参阅下面的注释)。与他不同,我直接检查调用者的字节码(而不是加载和解析源代码)。使用Python 3.4 +的dis.get_instructions()这可以通过最小兼容性的希望来完成。虽然这仍然是一些hacky代码。

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's 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堆栈帧,因此对此脚本是隐藏的。这将导致误报。考虑调用f()的Python函数a = g()g()是C实现的,并调用b = f2()。当f2()尝试查找指定的名称时,它将获得a而不是b,因为该脚本无视C函数。 (至少这是我猜它会起作用的方式:P)

用法示例:

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

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

答案 3 :(得分:1)

通常不能这样做,尽管这可以通过使用内省和用于调试程序的工具来实现。代码必须从“.py”文件运行,而不是从编译的字节码或压缩模块内部运行 - 因为它依赖于文件源代码的读取,从应该找到的方法中“它在哪里”运行”。

诀窍是访问初始化对象的执行框架 - 使用inspect.currentframe - 框架对象具有“f_lineno”值,该值表示调用object方法的行号(在本例中为{ {1}})已被调用。函数inspect.filename允许检索文件的源代码,并获取适当的行号。

一个天真的解析然后偷看那个先写“=”符号的部分,并假设它是包含该对象的变量。

__init__

这不适用于多个受理人,在创建assignemtn之前与对象组成的表达式,将对象附加到列表或添加到词典或集合,在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() 循环的初始化中对象实例化,并且天知道更多情况 -  并且请记住,在第一次归因之后,对象也可以被任何其他变量引用。

Botton line:它可能,但作为玩具 - 它不能用于我的生产代码 - 只需在对象初始化期间将varibal名称作为字符串传递,就像在创建for

时必须要做的那样

如果您需要名称,“正确的方法”是将名称显式传递给对象初始化,作为字符串参数,如:

collections.namedtuple

但是,如果绝对需要只输入一次对象的名称,还有另一种方法 - 继续阅读。 由于Python的语法,一些特殊的赋值,不使用“=”运算符确实允许对象知道它被赋予了名称。因此,在Python中执行assignents的其他statemtns是for,with,def和class关键字 - 可以滥用它,因为特别是类创建和函数定义是赋值语句,它们创建“知道”其名称的对象。 / p>

让我们关注class A(object): def __init__(self, name): self.name = name x = A("x") 声明。它通常创建一个函数。但是使用装饰器可以使用“def”创建任何类型的对象 - 并且具有用于构造函数可用的函数的名称:

def

(这种做法的最后一种方法可以用于生产代码,不像用于阅读源文件的那种方式)

答案 4 :(得分:0)

这是一个实现您想要的功能的简单函数,假设您希望从方法调用中检索实例分配的变量名称:

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 matches.keys()[0] if matches else None

以下是一个用法示例:

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

答案 5 :(得分:0)

假设:

class MyObject(object):
    pass

x = MyObject()

然后,您可以通过对象的ID在环境中进行搜索,并在匹配时返回键。

keys = list(globals().keys())  # list all variable names
target = id(x)  # find the id of your object

for k in keys:
    value_memory_address = id(globals()[k])  # fetch id of every object
    if value_memory_address == target:
        print(globals()[k], k)  # if there is a variable assigned to that id, then it is a variable that points to your object

答案 6 :(得分:0)

我一直在独立从事这项工作,并掌握以下内容。它不像driax的回答那样全面,但是有效地涵盖了所描述的情况,并且不依赖于在全局变量中搜索对象的ID或解析源代码...

import sys
import dis

class MyObject:
    def __init__(self):
        # uses bytecode magic to find the name of the assigned variable
        f = sys._getframe(1) # get stack frame of caller (depth=1)
        # next op should be STORE_NAME (current op calls the constructor)
        opname = dis.opname[f.f_code.co_code[f.f_lasti+2]]
        if opname == 'STORE_NAME': # not all objects will be assigned a name
            # STORE_NAME argument is the name index
            namei = f.f_code.co_code[f.f_lasti+3]
            self.name = f.f_code.co_names[namei]
        else:
            self.name = None

x = MyObject()

x.name == 'x'