为什么两个ID相同的函数会有不同的属性?

16 投票
4 回答
561 浏览
提问于 2025-04-17 23:20

为什么两个具有相同 id 值的函数会有不同的属性,比如 __doc____name__ 呢?

这里有个简单的例子:

some_dict = {}
for i in range(2):
    def fun(self, *args):
        print i
    fun.__doc__ = "I am function {}".format(i)
    fun.__name__ = "function_{}".format(i)
    some_dict["function_{}".format(i)] = fun

my_type = type("my_type", (object,), some_dict)
m = my_type()

print id(m.function_0)
print id(m.function_1)
print m.function_0.__doc__
print m.function_1.__doc__
print m.function_0.__name__
print m.function_1.__name__
print m.function_0()
print m.function_1()

这个例子输出:

57386560
57386560
I am function 0
I am function 1
function_0
function_1
1 # <--- Why is it bound to the most recent value of that variable?
1

我尝试过使用 copy.deepcopy 来混合调用(不确定对函数来说递归复制是否必要,或者说这样做是否太过了),但这并没有改变任何东西。

4 个回答

2

你应该保存当前的 i,这样才能让这个:

1 # <--- Why is it bound to the most recent value of that variable?
1

正常工作,比如可以给函数的参数设置一个默认值:

for i in range(2):
    def fun(self, i=i, *args):
        print i
# ...

或者创建一个闭包:

for i in range(2):
    def f(i):
        def fun(self, *args):
            print i
        return fun
    fun = f(i)
# ...
2

@Martjin Pieters 说得完全正确。为了说明这一点,试试这个修改

some_dict = {}

for i in range(2):
    def fun(self, *args):
        print i

    fun.__doc__ = "I am function {}".format(i)
    fun.__name__ = "function_{}".format(i)
    some_dict["function_{}".format(i)] = fun
    print "id",id(fun)

my_type = type("my_type", (object,), some_dict)
m = my_type()

print id(m.function_0)
print id(m.function_1)
print m.function_0.__doc__
print m.function_1.__doc__
print m.function_0.__name__
print m.function_1.__name__
print m.function_0()
print m.function_1()

c = my_type()
print c
print id(c.function_0)

你会看到每次调用这个函数时,它的 ID 都不一样,而且和最后的那个 ID 也不同。这是因为创建方法的逻辑让它指向了同一个地方,因为那是存放类代码的地方。此外,如果你把 my_type 当作一种类来使用,用它创建的实例在这个函数上会有相同的内存地址。

这段代码的输出是:
id 4299601152
id 4299601272
4299376112
4299376112

我就是函数 0
我就是函数 1
function_0
function_1
1
None
1
None
<main.my_type 对象在 0x10047c350 的地址上>
4299376112

3

根据你对ndpu回答的评论,这里有一种方法可以创建函数,而不需要使用可选参数:

for i in range(2):
    def funGenerator(i):
        def fun1(self, *args):
            print i
        return fun1
    fun = funGenerator(i)
    fun.__doc__ = "I am function {}".format(i)
    fun.__name__ = "function_{}".format(i)
    some_dict["function_{}".format(i)] = fun
18

你在比较方法,每次你访问一个实例或类的方法时,方法对象都会被重新创建(这是通过描述符协议实现的)。

一旦你测试了它们的id(),你就会把这个方法丢掉(没有任何引用指向它),所以Python可以在你创建另一个方法时重新使用这个id。这里你想要测试的是实际的函数,可以通过m.function_0.__func__m.function_1.__func__来实现:

>>> id(m.function_0.__func__)
4321897240
>>> id(m.function_1.__func__)
4321906032

方法对象会继承它们所包装的函数的__doc____name__属性。实际上,底层的函数仍然是不同的对象。

至于这两个函数返回1的情况;这两个函数都使用i作为闭包;i的值是在你调用方法时查找的,而不是在你创建函数时查找的。你可以查看Python嵌套函数中的局部变量了解更多。

最简单的解决办法是添加另一个作用域,使用工厂函数:

some_dict = {}
for i in range(2):
    def create_fun(i):
        def fun(self, *args):
            print i
        fun.__doc__ = "I am function {}".format(i)
        fun.__name__ = "function_{}".format(i)
        return fun
    some_dict["function_{}".format(i)] = create_fun(i)

撰写回答