Почему две функции с одним и тем же "id" имеют разные атрибуты?

Почему две функции с тем же значением 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 (не уверен, нужна ли рекурсивная копия для функций или это слишком много), но это ничего не меняет.

Ответы

Ответ 1

Вы сравниваете методы, а объекты метода создаются заново каждый раз, когда вы обращаетесь к одному из экземпляра или класса (через протокол дескриптора).

После того, как вы протестировали их id(), вы снова отбросьте метод (ссылок на него нет), поэтому Python может повторно использовать идентификатор при создании другого метода. Вы хотите проверить фактические функции здесь, используя 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.

Самый простой способ - добавить еще одну область с помощью функции factory:

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)

Ответ 2

В вашем комментарии к 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

Ответ 3

Вы должны сохранить текущий 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)
# ...

Ответ 4

@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)

Вы видите, что веселье получает разные идентификаторы каждый раз и отличается от последнего. Это логика создания метода, которая отправляет ее, указывая на то же место, что и там, где хранится код класса. Кроме того, если вы используете my_type как своего рода класс, созданные с ним экземпляры имеют одинаковый адрес памяти для этой функции

Этот код дает:
id 4299601152
id 4299601272
4299376112
4299376112

Я функция 0
Я - функция 1
function_0
function_1
1
None
1
None
< основной объект .my_type на 0x10047c350 >
4299376112