Почему две функции с одним и тем же "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