Python中的变量名反射

6 投票
4 回答
7105 浏览
提问于 2025-04-15 16:57

在Python中,有没有办法动态地确定一个变量的名字呢?

举个例子,我有时候会遇到这样的情况:

name = foo if bar else baz
type = alpha or bravo

D = {
    "name": name,
    "type": type
}

如果能用类似 D = makedict(name, type) 的方式来减少重复就好了。

另外,有时候一个函数知道自己的名字也是很有帮助的:

class Item(object):

    def item_create(self, item):
        dispatch("create", item)

    def item_delete(self, item):
        dispatch("delete", item)

在这种情况下,可以通过传递类似 __methodname__ 的东西来减少重复,而不是分别写“create”和“delete”。

(我想可以用装饰器来实现这个,但那样似乎有点过于复杂了。)

4 个回答

2

你可以随心所欲地定义函数:

>>> def blagnorf():
    pass

>>> print blagnorf.__name__
blagnorf

但很遗憾,变量就不行了。不过,你可以考虑为你的Python代码写一个预处理器,来帮你实现这个功能...

值得注意的是,在Scheme/Lisp语言中,你可以通过它们的宏系统来做到这一点。

4

一般来说,你在Python里是做不到这一点的,因为Python使用的是对象,而不是变量。

比如我有:

L1 = [1,2]
L2 = L1

那么,L1L2实际上指向的是同一个对象。这个对象没有一个唯一的名字。

同样的道理:

def f1(): pass
f2 = f1

现在这个函数也没有一个唯一的名字,所以你无法找到“这个”函数的名字。

在Python中,一个对象可以被多个名字引用。当一个对象的引用计数变为零时,Python就会释放它所占用的内存。

11

一般来说,你不能仅仅通过一个值来推断出它的名字(可能没有名字,也可能有多个名字等等)。当你调用假设中的 makedict(name) 时,name 就是 makedict 接收到的内容,所以(再次强调,在一般情况下)它无法判断这个值来自哪个名字(如果有的话)。你可以尝试查看调用者的命名空间,看看是否有特殊情况能让你推断出名字(比如,你接收到 23,而在相关的命名空间中,恰好只有一个名字的值也是 23!),但这显然是一种脆弱且不可靠的做法。而且,在你第一个例子中,绝对可以保证不会出现这种特殊情况——name 中的值和 foobaz 中的值完全相同,因此可以100%确定这个值的名字会非常模糊。

你可以换个思路,比如调用 makedict('name type', locals())(显式传入 locals() 可能会被一些复杂的魔法所替代,但一般来说这不是最稳妥的选择)——传入名字(我建议也传入命名空间!),让 makedict 来推断,这显然是一个更稳妥的方案(因为每个名字只有一个值,但反之则不然)。也就是说:

def makedict(names, *namespaces):
  d = {}
  for n in names.split():
    for ns in namespaces:
      if n in ns:
        d[n] = ns[n]
        break
    else:
      d[n] = None  # or, raise an exception

如果你想通过反射来挖掘命名空间,而不是让调用者清晰地指定它们,可以看看 inspect.getouterframes ——但我建议你再考虑一下。

你提到的第二个问题则完全不同(虽然你可以再次使用 inspect 函数来查看调用者的名字,或者函数自己的名字——真是个奇怪的想法!)。这两种情况的共同点在于,你使用了非常强大且危险的工具来完成一个可以更简单实现的任务(更容易确保正确性,更容易调试问题,更容易测试等等)——与其说装饰器是“过度使用”,不如说它们比你提议的反射方式简单得多,而且更明确。如果你有很多形式类似于:

def item_blah(self, item):
    dispatch("blah", item)

那么创建它们的最简单方法可能是:

class Item(object): pass

def _makedispcall(n):
  def item_whatever(self, item):
    dispatch(n, item)
  item_whatever.__name__ = 'item_' + n
  return item_whatever

for n in 'create delete blah but wait theres more'.split():
  setattr(Item, 'item_' + n, _makedispcall(n))

避免重复是个好主意,但在运行时进行反射通常不是实现这个想法的最佳方式,Python 提供了许多替代的实现方法。

撰写回答