diff --git a/lectures/names.md b/lectures/names.md new file mode 100644 index 0000000..28a6274 --- /dev/null +++ b/lectures/names.md @@ -0,0 +1,570 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst +kernelspec: + display_name: Python 3 + language: python + name: python3 +heading-map: + overview: مرور کلی + variable-names-in-python: نام‌های متغیر در Python + namespaces: فضاهای نام + viewing-namespaces: مشاهده فضاهای نام + interactive-sessions: جلسات تعاملی + the-global-namespace: فضای نام عمومی + local-namespaces: فضاهای نام محلی + the-__builtins__-namespace: فضای نام `__builtins__` + name-resolution: تفکیک نام + indexmutable-single-mutable-versus-indeximmutable-single-immutable-parameters: 'پارامترهای {index}`تغییرپذیر ` در مقابل {index}`تغییرناپذیر `' +--- + +(oop_names)= +```{raw} jupyter + +``` + +# نام‌ها و فضاهای نام + +## مرور کلی + +این درس تماماً درباره نام‌های متغیر، نحوه استفاده از آن‌ها و نحوه درک آن‌ها توسط مفسر Python است. + +ممکن است این موضوع کمی خسته‌کننده به نظر برسد، اما مدلی که Python برای مدیریت نام‌ها اتخاذ کرده است، ظریف و جالب است. + +علاوه بر این، اگر درک خوبی از نحوه کار نام‌ها در Python داشته باشید، ساعت‌های زیادی را از اشکال‌زدایی صرفه‌جویی خواهید کرد. + +(var_names)= +## نام‌های متغیر در Python + +```{index} single: Python; Variable Names +``` + +دستور Python زیر را در نظر بگیرید + +```{code-cell} python3 +x = 42 +``` + +اکنون می‌دانیم که وقتی این دستور اجرا می‌شود، Python یک شیء از نوع `int` در حافظه کامپیوتر شما ایجاد می‌کند که شامل موارد زیر است: + +* مقدار `42` +* برخی صفات مرتبط + +اما خود `x` چیست؟ + +در Python، `x` یک **نام** نامیده می‌شود، و دستور `x = 42` نام `x` را به شیء عدد صحیح که تازه در موردش صحبت کردیم **متصل** می‌کند. + +در پشت صحنه، این فرآیند اتصال نام‌ها به اشیاء به عنوان یک دیکشنری پیاده‌سازی می‌شود - بیشتر درباره این موضوع در ادامه خواهیم گفت. + +هیچ مشکلی برای اتصال دو یا چند نام به یک شیء وجود ندارد، صرف‌نظر از اینکه آن شیء چه باشد + +```{code-cell} python3 +def f(string): # Create a function called f + print(string) # that prints any string it's passed + +g = f +id(g) == id(f) +``` + +```{code-cell} python3 +g('test') +``` + +در مرحله اول، یک شیء تابع ایجاد می‌شود و نام `f` به آن متصل می‌شود. + +پس از اتصال نام `g` به همان شیء، می‌توانیم از آن در هر جایی که از `f` استفاده می‌کنیم، استفاده کنیم. + +وقتی تعداد نام‌های متصل به یک شیء به صفر می‌رسد، چه اتفاقی می‌افتد؟ + +در اینجا مثالی از این وضعیت آورده شده است، که در آن نام `x` ابتدا به یک شیء متصل می‌شود و سپس به شیء دیگری **دوباره متصل** می‌شود + +```{code-cell} python3 +x = 'foo' +id(x) +x = 'bar' +id(x) +``` + +در این مورد، پس از اینکه `x` را مجدداً به `'bar'` متصل می‌کنیم، هیچ نامی به شیء اول `'foo'` متصل نیست. + +این یک محرک برای جمع‌آوری زباله `'foo'` است. + +به عبارت دیگر، جای حافظه‌ای که آن شیء را ذخیره می‌کند، آزاد می‌شود و به سیستم عامل بازگردانده می‌شود. + +جمع‌آوری زباله در واقع یک حوزه تحقیقاتی فعال در علوم کامپیوتر است. + +اگر علاقه‌مند هستید، می‌توانید [بیشتر درباره جمع‌آوری زباله بخوانید](https://rushter.com/blog/python-garbage-collector/). + +## فضاهای نام + +```{index} single: Python; Namespaces +``` + +از بحث قبلی به یاد بیاورید که دستور + +```{code-cell} python3 +x = 42 +``` + +نام `x` را به شیء عدد صحیح در سمت راست متصل می‌کند. + +همچنین ذکر کردیم که این فرآیند اتصال `x` به شیء صحیح به عنوان یک دیکشنری پیاده‌سازی می‌شود. + +این دیکشنری، فضای نام نامیده می‌شود. + +```{admonition} تعریف +یک **فضای نام** یک جدول نمادها است که نام‌ها را به اشیاء در حافظه نگاشت می‌کند. +``` + +Python از چندین فضای نام استفاده می‌کند و در صورت لزوم آن‌ها را به صورت پویا ایجاد می‌کند. + +به عنوان مثال، هر بار که ماژولی را import می‌کنیم، Python یک فضای نام برای آن ماژول ایجاد می‌کند. + +برای مشاهده این موضوع در عمل، فرض کنید یک اسکریپت `mathfoo.py` با یک خط می‌نویسیم + +```{code-cell} python3 +%%file mathfoo.py +pi = 'foobar' +``` + +حالا مفسر Python را شروع می‌کنیم و آن را import می‌کنیم + +```{code-cell} python3 +import mathfoo +``` + +بعد بیایید ماژول `math` را از کتابخانه استاندارد import کنیم + +```{code-cell} python3 +import math +``` + +هر دوی این ماژول‌ها یک صفت به نام `pi` دارند + +```{code-cell} python3 +math.pi +``` + +```{code-cell} python3 +mathfoo.pi +``` + +این دو اتصال متفاوت `pi` در فضاهای نام متفاوتی وجود دارند که هر کدام به عنوان یک دیکشنری پیاده‌سازی شده‌اند. + +اگر بخواهید، می‌توانید مستقیماً به دیکشنری نگاه کنید، با استفاده از `module_name.__dict__`. + +```{code-cell} python3 +import math + +math.__dict__.items() +``` + +```{code-cell} python3 +import mathfoo + +mathfoo.__dict__ +``` + +همان‌طور که می‌دانید، ما با استفاده از نشانه‌گذاری صفت نقطه‌دار به عناصر فضای نام دسترسی پیدا می‌کنیم + +```{code-cell} python3 +math.pi +``` + +این کاملاً معادل `math.__dict__['pi']` است + +```{code-cell} python3 +math.__dict__['pi'] +``` + +## مشاهده فضاهای نام + +همان‌طور که در بالا دیدیم، فضای نام `math` را می‌توان با تایپ کردن `math.__dict__` چاپ کرد. + +راه دیگر برای دیدن محتویات آن تایپ کردن `vars(math)` است + +```{code-cell} python3 +vars(math).items() +``` + +اگر فقط می‌خواهید نام‌ها را ببینید، می‌توانید تایپ کنید + +```{code-cell} python3 +# Show the first 10 names +dir(math)[0:10] +``` + +به نام‌های ویژه `__doc__` و `__name__` توجه کنید. + +این‌ها در فضای نام هنگام import کردن هر ماژول مقداردهی اولیه می‌شوند + +* `__doc__` رشته مستندسازی ماژول است +* `__name__` نام ماژول است + +```{code-cell} python3 +print(math.__doc__) +``` + +```{code-cell} python3 +math.__name__ +``` + +## جلسات تعاملی + +```{index} single: Python; Interpreter +``` + +در Python، **تمام** کدهایی که توسط مفسر اجرا می‌شوند در ماژولی اجرا می‌شوند. + +در مورد دستوراتی که در prompt تایپ می‌شوند چطور؟ + +این‌ها نیز به عنوان اجرا شده در یک ماژول در نظر گرفته می‌شوند - در این مورد، ماژولی به نام `__main__`. + +برای بررسی این موضوع، می‌توانیم نام ماژول فعلی را از طریق مقدار `__name__` که در prompt داده می‌شود مشاهده کنیم + +```{code-cell} python3 +print(__name__) +``` + +وقتی یک اسکریپت را با استفاده از دستور `run` در IPython اجرا می‌کنیم، محتویات فایل نیز به عنوان بخشی از `__main__` اجرا می‌شوند. + +برای مشاهده این موضوع، بیایید یک فایل `mod.py` ایجاد کنیم که صفت `__name__` خودش را چاپ کند + +```{code-cell} ipython +%%file mod.py +print(__name__) +``` + +حالا بیایید دو روش متفاوت اجرای آن را در IPython ببینیم + +```{code-cell} python3 +import mod # Standard import +``` + +```{code-cell} ipython +%run mod.py # Run interactively +``` + +در حالت دوم، کد به عنوان بخشی از `__main__` اجرا می‌شود، بنابراین `__name__` برابر با `__main__` است. + +برای دیدن محتویات فضای نام `__main__` از `vars()` به جای `vars(__main__)` استفاده می‌کنیم. + +اگر این کار را در IPython انجام دهید، تعداد زیادی متغیر خواهید دید که IPython به آن‌ها نیاز دارد و هنگام شروع جلسه مقداردهی اولیه کرده است. + +اگر ترجیح می‌دهید فقط متغیرهایی که شما مقداردهی اولیه کرده‌اید را ببینید، از `%whos` استفاده کنید + +```{code-cell} ipython +x = 2 +y = 3 + +import numpy as np + +%whos +``` + +## فضای نام عمومی + +```{index} single: Python; Namespace (Global) +``` + +مستندات Python اغلب به "فضای نام عمومی" اشاره می‌کند. + +فضای نام عمومی *فضای نام ماژولی است که در حال حاضر در حال اجرا است*. + +به عنوان مثال، فرض کنید که مفسر را شروع می‌کنیم و شروع به انجام انتسابات می‌کنیم. + +اکنون ما در ماژول `__main__` کار می‌کنیم، و از این رو فضای نام برای `__main__` فضای نام عمومی است. + +سپس، ماژولی به نام `amodule` را import می‌کنیم + +```{code-block} python3 +:class: no-execute + +import amodule +``` + +در این نقطه، مفسر یک فضای نام برای ماژول `amodule` ایجاد می‌کند و شروع به اجرای دستورات در ماژول می‌کند. + +در حالی که این اتفاق می‌افتد، فضای نام `amodule.__dict__` فضای نام عمومی است. + +پس از اتمام اجرای ماژول، مفسر به ماژولی که دستور import از آن صادر شده بود بازمی‌گردد. + +در این مورد `__main__` است، بنابراین فضای نام `__main__` دوباره فضای نام عمومی می‌شود. + +## فضاهای نام محلی + +```{index} single: Python; Namespace (Local) +``` + +نکته مهم: وقتی یک تابع را فراخوانی می‌کنیم، مفسر یک *فضای نام محلی* برای آن تابع ایجاد می‌کند و متغیرها را در آن فضای نام ثبت می‌کند. + +دلیل این امر در همین لحظه توضیح داده خواهد شد. + +متغیرهای موجود در فضای نام محلی *متغیرهای محلی* نامیده می‌شوند. + +پس از بازگشت تابع، فضای نام آزاد می‌شود و از بین می‌رود. + +در حالی که تابع در حال اجرا است، می‌توانیم محتویات فضای نام محلی را با `locals()` مشاهده کنیم. + +به عنوان مثال، در نظر بگیرید + +```{code-cell} python3 +def f(x): + a = 2 + print(locals()) + return a * x +``` + +حالا بیایید تابع را فراخوانی کنیم + +```{code-cell} python3 +f(1) +``` + +می‌توانید فضای نام محلی `f` را قبل از نابود شدن ببینید. + +## فضای نام `__builtins__` + +```{index} single: Python; Namespace (__builtins__) +``` + +ما از توابع داخلی مختلفی مانند `max(), dir(), str(), list(), len(), range(), type()` و غیره استفاده کرده‌ایم. + +دسترسی به این نام‌ها چگونه کار می‌کند؟ + +* این تعاریف در ماژولی به نام `__builtin__` ذخیره شده‌اند. +* آن‌ها فضای نام خاص خود را به نام `__builtins__` دارند. + +```{code-cell} python3 +# Show the first 10 names in `__main__` +dir()[0:10] +``` + +```{code-cell} python3 +# Show the first 10 names in `__builtins__` +dir(__builtins__)[0:10] +``` + +می‌توانیم به عناصر فضای نام به صورت زیر دسترسی پیدا کنیم + +```{code-cell} python3 +__builtins__.max +``` + +اما `__builtins__` ویژه است، زیرا همیشه می‌توانیم مستقیماً به آن‌ها نیز دسترسی پیدا کنیم + +```{code-cell} python3 +max +``` + +```{code-cell} python3 +__builtins__.max == max +``` + +بخش بعدی توضیح می‌دهد که این چگونه کار می‌کند ... + +## تفکیک نام + +```{index} single: Python; Namespace (Resolution) +``` + +فضاهای نام عالی هستند زیرا به ما کمک می‌کنند تا نام‌های متغیر را سازماندهی کنیم. + +(در prompt عبارت `import this` را تایپ کنید و به آخرین موردی که چاپ می‌شود نگاه کنید) + +با این حال، ما باید بفهمیم که مفسر Python چگونه با فضاهای نام متعدد کار می‌کند. + +درک جریان اجرا به ما کمک می‌کند تا بررسی کنیم که کدام متغیرها در محدوده هستند و چگونه می‌توانیم هنگام نوشتن و اشکال‌زدایی برنامه‌ها بر روی آن‌ها عمل کنیم. + +در هر نقطه از اجرا، در واقع حداقل دو فضای نام وجود دارند که می‌توان به طور مستقیم به آن‌ها دسترسی پیدا کرد. + +("دسترسی مستقیم" به معنای بدون استفاده از نقطه است، مانند `pi` به جای `math.pi`) + +این فضاهای نام عبارتند از + +* فضای نام عمومی (ماژولی که در حال اجرا است) +* فضای نام builtin + +اگر مفسر در حال اجرای یک تابع است، آنگاه فضاهای نام قابل دسترسی مستقیم عبارتند از + +* فضای نام محلی تابع +* فضای نام عمومی (ماژولی که در حال اجرا است) +* فضای نام builtin + +گاهی اوقات توابع در داخل توابع دیگر تعریف می‌شوند، مانند این + +```{code-cell} python3 +def f(): + a = 2 + def g(): + b = 4 + print(a * b) + g() +``` + +در اینجا `f` تابع *احاطه‌کننده* برای `g` است و هر تابع فضای نام خود را دارد. + +حالا می‌توانیم قانون نحوه کار تفکیک فضای نام را بیان کنیم: + +ترتیبی که مفسر برای جستجوی نام‌ها دنبال می‌کند عبارت است از + +1. فضای نام محلی (اگر وجود داشته باشد) +1. سلسله مراتب فضاهای نام احاطه‌کننده (اگر وجود داشته باشند) +1. فضای نام عمومی +1. فضای نام builtin + +اگر نام در هیچ یک از این فضاهای نام نباشد، مفسر یک `NameError` ایجاد می‌کند. + +این قانون **LEGB** نامیده می‌شود (local, enclosing, global, builtin). + +در اینجا مثالی آورده شده است که کمک می‌کند تا توضیح داده شود. + +تجسم‌های اینجا توسط [nbtutor](https://github.com/lgpage/nbtutor) در یک نوت‌بوک Jupyter ایجاد شده‌اند. + +آن‌ها می‌توانند به شما کمک کنند تا برنامه خود را بهتر درک کنید وقتی که در حال یادگیری یک زبان جدید هستید. + +اسکریپت `test.py` را در نظر بگیرید که به صورت زیر است + +```{code-cell} python3 +%%file test.py +def g(x): + a = 1 + x = x + a + return x + +a = 0 +y = g(10) +print("a = ", a, "y = ", y) +``` + +وقتی این اسکریپت را اجرا می‌کنیم، چه اتفاقی می‌افتد؟ + +```{code-cell} ipython +%run test.py +``` + +ابتدا، + +* فضای نام عمومی `{}` ایجاد می‌شود. + +```{figure} /_static/lecture_specific/oop_intro/global.png +``` + +* شیء تابع ایجاد می‌شود و `g` در فضای نام عمومی به آن متصل می‌شود. +* نام `a` به `0` متصل می‌شود، دوباره در فضای نام عمومی. + +```{figure} /_static/lecture_specific/oop_intro/global2.png +``` + +بعد `g` از طریق `y = g(10)` فراخوانی می‌شود که منجر به توالی زیر از اقدامات می‌شود + +* فضای نام محلی برای تابع ایجاد می‌شود. +* نام‌های محلی `x` و `a` متصل می‌شوند، به طوری که فضای نام محلی به `{'x': 10, 'a': 1}` تبدیل می‌شود. + +توجه کنید که `a` عمومی تحت تأثیر `a` محلی قرار نگرفت. + +```{figure} /_static/lecture_specific/oop_intro/local1.png +``` + +* دستور `x = x + a` از `a` محلی و `x` محلی برای محاسبه `x + a` استفاده می‌کند و نام محلی `x` را به نتیجه متصل می‌کند. +* این مقدار بازگردانده می‌شود و `y` در فضای نام عمومی به آن متصل می‌شود. +* `x` و `a` محلی دور انداخته می‌شوند (و فضای نام محلی آزاد می‌شود). + +```{figure} /_static/lecture_specific/oop_intro/local_return.png +``` + +(mutable_vs_immutable)= +### پارامترهای {index}`تغییرپذیر ` در مقابل {index}`تغییرناپذیر ` + +این زمان خوبی است که کمی بیشتر درباره اشیاء تغییرپذیر در مقابل تغییرناپذیر صحبت کنیم. + +بخش کد زیر را در نظر بگیرید + +```{code-cell} python3 +def f(x): + x = x + 1 + return x + +x = 1 +print(f(x), x) +``` + +اکنون می‌فهمیم که اینجا چه اتفاقی می‌افتد: کد `2` را به عنوان مقدار `f(x)` و `1` را به عنوان مقدار `x` چاپ می‌کند. + +ابتدا `f` و `x` در فضای نام عمومی ثبت می‌شوند. + +فراخوانی `f(x)` یک فضای نام محلی ایجاد می‌کند و `x` را به آن اضافه می‌کند که به `1` متصل است. + +بعد، این `x` محلی به شیء عدد صحیح جدید `2` دوباره متصل می‌شود و این مقدار بازگردانده می‌شود. + +هیچ یک از این‌ها بر `x` عمومی تأثیر نمی‌گذارد. + +با این حال، وقتی از یک نوع داده **تغییرپذیر** مانند لیست استفاده می‌کنیم، داستان متفاوت است + +```{code-cell} python3 +def f(x): + x[0] = x[0] + 1 + return x + +x = [1] +print(f(x), x) +``` + +این `[2]` را به عنوان مقدار `f(x)` و *همان* را برای `x` چاپ می‌کند. + +در اینجا چه اتفاقی می‌افتد + +* `f` به عنوان یک تابع در فضای نام عمومی ثبت می‌شود + +```{figure} /_static/lecture_specific/oop_intro/mutable1.png +``` + +* `x` به `[1]` در فضای نام عمومی متصل می‌شود + +```{figure} /_static/lecture_specific/oop_intro/mutable2.png +``` + +* فراخوانی `f(x)` + * یک فضای نام محلی ایجاد می‌کند + * `x` را به فضای نام محلی اضافه می‌کند که به `[1]` متصل است + +```{figure} /_static/lecture_specific/oop_intro/mutable3.png +``` + +```{note} +`x` عمومی و `x` محلی به همان `[1]` اشاره می‌کنند +``` + +می‌توانیم ببینیم که هویت `x` محلی و هویت `x` عمومی یکسان است + +```{code-cell} python3 +def f(x): + x[0] = x[0] + 1 + print(f'the identity of local x is {id(x)}') + return x + +x = [1] +print(f'the identity of global x is {id(x)}') +print(f(x), x) +``` + +* در داخل `f(x)` + * لیست `[1]` به `[2]` تغییر می‌کند + * لیست `[2]` را برمی‌گرداند + +```{figure} /_static/lecture_specific/oop_intro/mutable4.png +``` +* فضای نام محلی آزاد می‌شود و `x` محلی از بین می‌رود + +```{figure} /_static/lecture_specific/oop_intro/mutable5.png +``` + +اگر می‌خواهید `x` محلی و `x` عمومی را به طور جداگانه تغییر دهید، می‌توانید یک [*کپی*](https://docs.python.org/3/library/copy.html) از لیست ایجاد کنید و کپی را به `x` محلی اختصاص دهید. + +این را برای شما برای کاوش باقی می‌گذاریم. \ No newline at end of file