从对象内部获取对象分配给的变量?

Get variable to which an object is assigned from inside the object?

在 Python 中,我想获取要将对象分配给的变量。类似于:

class Parent(object):

    def __init__(self):
        print(???) # Print the variable to which the created instance of 
                   # Parent has been assigned to. 

p = Parent() # This should print 'p'. 

我想到了这个:

import inspect


class Parent(object):

    def __init__(self):
        print((inspect.stack()[-1].code_context[-1].split()[0]))


p = Parent()

如果我理解正确的话,从字面上看是最外层的调用,那里有左边的字符串部分,它恰好是变量——但对我来说这看起来很奇怪。有没有更好的办法?

您无法在 __init__ 方法中获取实例名称(以常规方式)1。因为 __init__ 方法在 __new__() 创建实例后立即调用,然后 python 将创建的对象返回给调用者。这意味着 python 将变量名映射到全局命名空间中的对象。

不过,解决此问题的一种方法是为您的对象定义另一种方法,以便在初始化后获取实例名称。为此,您可以使用 globals() 内置函数:

In [5]: class Parent:
            def __init__(self):
                pass
            def get_instance_names(self):
                names = [i for i,j in globals().items() if isinstance(j, type(self))]
                if names:
                    return names
   ...:         

演示:

In [6]: a = Parent()

In [7]: a.get_instance_names()
Out[7]: ['a']

In [8]: a = b = c = Parent()

In [9]: a.get_instance_names()
Out[9]: ['c', 'a', 'b']

1. 实际上取决于您在其中执行代码的环境,它可能会有所不同,但是您可以通过解析源代码的执行行或者可能在较低级别(例如查看堆栈)来提取变量,但是这根本不是一种合适的保存方式,而您可以简单地从全局名称空间等上层获取名称。

是的,你可以这样做,同时使用inspectast来解析你可以使用inspect.stack()找到的源代码.

请注意,您的解决方案为我打印 "__main__",,我正在 Python 3.5.2 上进行测试,因此其他 Python 版本的答案可能略有不同。

import inspect
import ast

class Parent(object):

    def __init__(self):
        frame = inspect.stack()[1]
        # Parse python syntax of the assignment line
        st = ast.parse(frame.code_context[0].strip())
        stmt = st.body[0]
        # Assume class being instanced as simple assign statement
        assert(isinstance(stmt, ast.Assign))
        # Parse the target the class is assigned to
        target = stmt.targets[0]
        print(target.id) # prints "p"

p = Parent()

这只适用于简单的赋值语句。如果您有更复杂的赋值语句,比如 p, n = Parent(), 0,您将需要添加更多步骤来解析 AST。原则上,所有涉及 Parent() 的有效语句都可以被解析,但在某些情况下不涉及赋值(例如 Parser())。

因此上面列出的代码只能作为解决问题的示例,而不是完整的解决方案。

由于各种原因,这通常是不可行的。它在 __init__ 或其他 class 初始化方法中是双重不可行的,因为对象尚未准备好 - 只有当初始化完成时,新对象才会返回到调用上下文并且(可能) 分配给一个变量。

但是在 Python 中,如您所见,"unfeasible" 与 "impossible" 不同 - 您和一些答案可以通过解析源代码文件找到它, 找到作业发生的位置,从而猜出名字。

你没有告诉为什么你想要这个,但是对于我能想到的任何东西,这看起来大部分没用。分配给对象的变量在运行时也很少用到 - 无论哪种方式,它都是已知的。

因此,如果您检查一下,在 stdlib 本身中您有 namedtuple,当然还有 class 创建本身,两者都需要在创建时显式传递名称:

myclass = type("myclass", (object, ), {})
mytuple = collections.namedtuple("mytuple", "a b c")

如果您可以在创建并分配对象后知道名称,则可以通过检查调用帧或使用垃圾收集机制来检索它的方法 -它们会比您的 hack 好一点,因为它们不依赖于可用的源代码文件。

但是,我再次建议他们不要在 "production" 代码中这样做。顺便提一下,使用 "gc" 的方法是调用 "gc.get_referrers"。你可以把你的 "name finding" 放在一个方法中,甚至是 属性 做一些事情:

import gc
class Parent(object):
    @property
    def get_names(self):
        names = []
        for referrer in gc.get_referrers(self):
            if not isinstance(referrer, dict):
                continue
            module = referrer.get("__name__", "")
            for key, value in referrer.items():
                if value is self:
                    names.append(".".join((module, key)))
        return names

另一种方法是检查调用者代码框架中的全局字典,但是随后,您会在调用 get_names 的代码中获得 "self" 的名称,而不是实例所在的位置已定义。

但是,我曾经只想要 class 属性这样的东西 - 那么您可以通过三种可能的方式将名称分配为 Parent 实例本身的属性:在metaclass __new____init__ 方法,并且从 Python 3.6 开始,在包含 classes 的基础 class 包含 "Parent" 作为属性 __init_subclass__ 方法,或者,如果您的 Parent class 是一个描述符(即,确实有一个 __get__ 方法),在这种情况下,也来自Python 3.6,它可以有一个 __set_name__ 方法,将在(容器)class 创建时调用。