Python中的变量名反射
在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 个回答
你可以随心所欲地定义函数:
>>> def blagnorf():
pass
>>> print blagnorf.__name__
blagnorf
但很遗憾,变量就不行了。不过,你可以考虑为你的Python代码写一个预处理器,来帮你实现这个功能...
值得注意的是,在Scheme/Lisp语言中,你可以通过它们的宏系统来做到这一点。
一般来说,你在Python里是做不到这一点的,因为Python使用的是对象,而不是变量。
比如我有:
L1 = [1,2]
L2 = L1
那么,L1
和L2
实际上指向的是同一个对象。这个对象没有一个唯一的名字。
同样的道理:
def f1(): pass
f2 = f1
现在这个函数也没有一个唯一的名字,所以你无法找到“这个”函数的名字。
在Python中,一个对象可以被多个名字引用。当一个对象的引用计数变为零时,Python就会释放它所占用的内存。
一般来说,你不能仅仅通过一个值来推断出它的名字(可能没有名字,也可能有多个名字等等)。当你调用假设中的 makedict(name)
时,name 的 值
就是 makedict
接收到的内容,所以(再次强调,在一般情况下)它无法判断这个值来自哪个名字(如果有的话)。你可以尝试查看调用者的命名空间,看看是否有特殊情况能让你推断出名字(比如,你接收到 23
,而在相关的命名空间中,恰好只有一个名字的值也是 23
!),但这显然是一种脆弱且不可靠的做法。而且,在你第一个例子中,绝对可以保证不会出现这种特殊情况——name
中的值和 foo
或 baz
中的值完全相同,因此可以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 提供了许多替代的实现方法。