امروز توی یه مصاحبه سوال زیر مطرح شد و گفتن که خروجی برنامه چیه و من بعدا که تستش کردم سورپرایز شدم.
1 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class A: val = 1 class B(A): pass class C(A): pass print(A.val, B.val, C.val) B.val = 2 print(A.val, B.val, C.val) A.val = 3 print(A.val, B.val, C.val) |
خروجی کد از قرار زیره:
1 2 3 4 |
C:\code\interview>python test.py 1 1 1 1 2 1 3 2 3 |
بله سورپرایز شدیم. حالا برای اینکه بفهمیم چرا اینطور شد کد زیر رو اجرا میکنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class A: val = 1 class B(A): pass class C(A): pass print("Class memory locations:", id(A), id(B), id(C)) print("val memory locations:", id(A.val), id(B.val), id(C.val)) print("------------------") B.val = 2 print("Class memory locations:", id(A), id(B), id(C)) print("val memory locations:", id(A.val), id(B.val), id(C.val)) print("------------------") A.val = 3 print("Class memory locations:", id(A), id(B), id(C)) print("val memory locations:", id(A.val), id(B.val), id(C.val)) print("------------------") |
و خروجی کد:
1 2 3 4 5 6 7 8 9 |
:\code\interview>python test.py Class memory locations: 1555734830272 1555734824320 1555734826304 val memory locations: 140704966724024 140704966724024 140704966724024 ------------------ Class memory locations: 1555734830272 1555734824320 1555734826304 val memory locations: 140704966724024 140704966724056 140704966724024 ------------------ Class memory locations: 1555734830272 1555734824320 1555734826304 val memory locations: 140704966724088 140704966724056 140704966724088 |
خوب مشخص شد مساله چیه. توی ارثبری کلاس ها توی پایتون حتی اگه هیچی هم تغییر نکنه کلاس جدید به جای جدیدی از حافظه اشاره میکنه. در واقع کلاس پدر کپی میشه توی جای جدیدی از حافظه و کلاس فرزند یه pointer به جای جدیده. و در زمان اجرا کلاس هر تغییری هم بکنه به جای جدیدی نمیره که بنابراین آدرس حافظه کلاس در هیچ شرایطی تغییر نمیکنه.
اما متغیری که توی کلاس تعریف شده داستانش فرق میکنه. توی کلاس A پدر متغیر val به یه آدرس توی حافظه اشاره میکنه. کلاس B و C که فرزند هستن به همون آدرس حافظه اشاره مکنه. حالا وقتی B.val رو تغییر میدیم (از اونجایی که integer توی پایتون immutable هست) اون خونه حافظه تغییر نمیکنه بلکه یک خونه جدید با مقدار جدید ساخته میشه و B.val به اون اشاره میکنه. حالا باید خونه قبلی حافظه رو پاک کنه اما چون 2 تا pointer دیگه بهش وصله اونو پاک نمیکنه (Garbage collector وقتی اون خونه حافظه رو پاک میکنه که هیچ pointer ای به اون اشاره نکنه)
حالا داستان دوم. با این منطق اگر A.val رو تغییر بدیم باید C.val تغییر نکنه چون A.val به آدرس جدید اشاره میکنه ولی خانه قبلی پاک نمیشه چون هنوز C.val به خانه قبلی اشاره میکنه. اما مساله دیگه ای هم هست این جا از ارثبری استفاده کردیم و C فرزند A هست پس حالا که A.val به خانه جدیدی از حافظه اشاره میکنه این pointer رو توی فرزندانش هم عوض میکنه و بازم C.val و A.val به یک خانه جدید یکسان اشاره میکنن. ولی B.val دیگه این اتفاق براش نمیوفته چون از قبل به خونه دیگه ای اشاره میکنه (تغییر کرده) اگر پایتون این رو جوری دیگه پیاده سازی میکرد قطعا به مشکل میخوردیم.
واقعا سوال باحالی بود.