Я столкнулся с поведением Python, которое мне кажется очень странным, и я хотел бы понять это.

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

Когда я создаю объект и применяю id, результат всегда одинаков, но когда я использую id в связанном методе объекта, результат меняется. Почему это? Создается ли новый метод каждый раз, когда я получаю атрибут метода?

Я впервые заметил это в IPython, но было сложнее создать скрипт, который демонстрирует такое же поведение. Может быть, это частично IPython?

Мне удалось написать небольшой блок, который частично воссоздает поведение.

# Create an object

class Foo(object):
    def bar(self):
        pass

obj = Foo()

for _ in range(10):
    print(id(obj))
# ... prints the same number

for _ in range(10):
    print(id(obj.bar))
# ... in this case the first number is different and the rest are the same

Это немного отличается от простого вставления строки print(id(obj.bar)) в IPython несколько раз, поскольку возвращаемые идентификаторы в основном согласованы. Тем не менее, когда я просто запускаю приведенный выше код в виде скрипта Python, все цифры одинаковы, поэтому кажется, что это причуды IPython. Я думаю, что вопрос сейчас: почему?

2
Erotemic 6 Янв 2017 в 17:55

3 ответа

Лучший ответ

Связанный метод не совпадает с несвязанной функцией класса:

In [539]: Foo.bar
Out[539]: <function __main__.Foo.bar>
In [540]: id(Foo.bar)
Out[540]: 2951600788
In [541]: obj=Foo()
In [542]: obj.bar
Out[542]: <bound method Foo.bar of <__main__.Foo object at 0xaf63c0cc>>
In [543]: id(obj.bar)
Out[543]: 2942557836
In [544]: obj1=Foo()
In [545]: id(obj1.bar)   # different obj, different bound method
Out[545]: 2996305612
In [546]: id(obj.bar)    # different from the previous time
Out[546]: 2942663116

Поэтому он создает новый связанный метод каждый раз, когда вы ссылаетесь на него.

Все связанные методы ссылаются на один и тот же несвязанный метод, Foo.bar:

In [549]: obj.bar.__func__
Out[549]: <function __main__.Foo.bar>
In [550]: id(obj.bar.__func__)
Out[550]: 2951600788
In [551]: id(obj1.bar.__func__)
Out[551]: 2951600788
1
hpaulj 6 Янв 2017 в 20:11

Каждый раз, когда вы извлекаете метод из экземпляра класса, вы получаете связанный метод , который будет заполнять экземпляр как первый параметр (self) при вызове. Новый связанный метод создается каждый раз. Однако в вашем тесте одновременно существует только один связанный метод; предыдущий получает право на сборку мусора перед созданием следующего. Поэтому вероятно (но ни в коем случае не гарантировано), что новый связанный метод будет размещен по тому же адресу, что и только что освобожденный, и, следовательно, будет иметь тот же идентификатор. Если вы соберете их всех в список, чтобы они существовали одновременно, у них определенно были бы разные идентификаторы.

4
jasonharper 6 Янв 2017 в 15:13

Это потому, что Python создает объекты во время выполнения. Когда вы запускаете скрипт, объект создается один раз, и вы можете видеть одно и то же число в каждой итерации цикла. При использовании этой печати в отдельных записях в IPython создаются новые объекты.

0
Leopoldo 6 Янв 2017 в 15:08