Null در پایتون: درک آبجکت NoneType پایتون
اگر تجربه ای با زبان های برنامه نویسی دیگر مانند C یا Java دارید، احتمالا مفهوم null
را شنیده اید. بسیاری از زبان ها از این برای نشان دادن اشاره گر استفاده می کنند که به چیزی اشاره نمی کند، برای نشان دادن زمانی که یک متغیر خالی است، یا برای علامت گذاری پارامترهای پیش فرضی که هنوز ارائه نکرده اید. null
اغلب در آن
زبان ها 0 تعریف می شود، اما null
در پایتون متفاوت است.
پایتون از کلمه کلیدی None
برای تعریف اشیاء و متغیرهای تهی
استفاده می کند. در حالی که None
برخی از اهداف مشابه null
در زبان های دیگر را دنبال نمی کند، اما کاملا جانور دیگری است. به عنوان null
در پایتون، None
به عنوان 0
یا هر مقدار دیگری تعریف نشده است. در پایتون، هیچ کدام
یک شی و یک شهروند درجه یک است!
در این آموزش یاد خواهید گرفت:
None
چیست و چگونه می توان آن را آزمایش کرد- چه زمانی و چرا از
None
به عنوان پارامتر پیش فرض استفاده کنیم؟ - معنای
None
وNoneType
در ردیابی شما چیست؟ - نحوه استفاده
از None
در بررسی تایپ - چگونه
null
در پایتون زیر کاپوت کار می کند
آشنایی با Null در پایتون
هیچ
مقداری است که یک تابع زمانی که دستور بازگشتی
در تابع وجود ندارد برمی گرداند:
>>> def has_no_return():
... pass
>>> has_no_return()
>>> print(has_no_return())
None
وقتی با has_no_return()
تماس می گیرید، هیچ خروجی برای دیدن شما وجود ندارد. با این حال، هنگامی که یک تماس را به آن چاپ می کنید، None
پنهان را می بینید که برمی گردد.
در واقع، None
به طور مکرر به عنوان یک مقدار بازگشتی ظاهر می شود که Python REPL None
را چاپ نمی کند مگر اینکه صریحا به آن بگویید:
>>> None
>>> print(None)
None
هیچ کدام
به خودی خود خروجی ندارند، اما چاپ آن None
را به کنسول نشان می دهد.
جالب اینجاست که print()
به خودی خود هیچ مقدار بازگشتی ندارد. اگر بخواهید فراخوانی را برای print()
چاپ کنید، هیچ کدام را
دریافت خواهید کرد:
>>> print(print("Hello, World!"))
Hello, World!
None
ممکن است عجیب به نظر برسد، اما print(print("... ")) به
شما نشان می دهد که
چاپ درونی()
برمی گرداند.
هیچ کدام
نیز اغلب به عنوان سیگنالی برای پارامترهای گمشده یا پیش فرض استفاده نمی شوند. به عنوان مثال، None
دو بار در اسناد list.sort
ظاهر می شود:
>>> help(list.sort)
Help on method_descriptor:
sort(...)
L.sort(key=None, reverse=False) -> None -- stable sort *IN PLACE*
در اینجا، None
مقدار پیش فرض برای پارامتر کلید
و همچنین اشاره نوع برای مقدار بازگشتی است. خروجی دقیق کمک
می تواند از پلتفرمی به پلتفرم دیگر متفاوت باشد. ممکن است هنگام اجرای این دستور در مفسر خود، خروجی متفاوتی دریافت کنید، اما مشابه خواهد بود.
استفاده از آبجکت Null پایتون None
اغلب، شما از None
به عنوان بخشی از مقایسه استفاده می کنید. یک مثال زمانی است که باید بررسی کنید و ببینید آیا برخی از نتایج یا پارامترها هیچ
است. نتیجه ای را که از re.match
به دست می آورید بگیرید. آیا عبارت منظم شما با یک رشته معین مطابقت داشت؟ یکی از دو نتیجه را خواهید دید:
- برگرداندن یک شی
Match
: عبارت منظم شما یک تطابق پیدا کرد. - برگرداندن یک شی
هیچ:
عبارت منظم شما مطابقت پیدا نکرد.
در بلوک کد زیر، در حال آزمایش هستید که آیا الگوی "خداحافظ"
با یک رشته مطابقت دارد یا خیر:
>>> import re
>>> match = re.match(r"Goodbye", "Hello, World!")
>>> if match is None:
... print("It doesn't match.")
It doesn't match.
در اینجا، شما از is None
استفاده می کنید تا آزمایش کنید که آیا الگو با رشته "Hello, World!"
مطابقت دارد یا خیر. این بلوک کد قانون مهمی را نشان می دهد که باید هنگام بررسی None
به خاطر بسپارید:
- آیا استفاده از عملگرهای هویت
است
ونیست
. - از عملگرهای برابری
==
و!=
استفاده نکنید.
اپراتورهای برابری می توانند فریب بخورند وقتی اشیاء تعریف شده توسط کاربر را که آنها را نادیده می گیرند مقایسه می کنید:
>>> class BrokenComparison:
... def __eq__(self, other):
... return True
>>> b = BrokenComparison()
>>> b == None # Equality operator
True
>>> b is None # Identity operator
False
در اینجا، عملگر برابری ==
پاسخ اشتباه را برمی گرداند. از سوی دیگر، اپراتور هویت را نمی توان فریب داد زیرا نمی توانید آن را لغو کنید.
توجه: برای اطلاعات بیشتر در مورد نحوه مقایسه با هیچکدام
، بایدها و نبایدها: توصیه های برنامه نویسی پایتون را بررسی کنید.
هیچ کدام
دروغ نیست، به این معنی که هیچ کدام
درست
نیست. اگر تنها چیزی که می خواهید بدانید این است که آیا نتیجه نادرست است یا خیر، آزمونی مانند موارد زیر کافی است:
>>> some_result = None
>>> if some_result:
... print("Got a result!")
... else:
... print("No result.")
...
No result.
خروجی به شما نشان نمی دهد که some_result
دقیقا هیچ
است، فقط جعلی است. اگر باید بدانید که آیا یک شی None
دارید یا خیر، پس استفاده است و
نیست
.
اشیاء زیر همگی جعلی هستند:
- لیست های خالی
- فرهنگ لغت های خالی
- مجموعه های خالی
- رشته های خالی
0
False
برای اطلاعات بیشتر در مورد مقایسه ها، مقادیر واقعی و مقادیر غلط، می توانید در مورد نحوه استفاده از پایتون یا
عملگر، نحوه استفاده از پایتون و
عملگر و نحوه استفاده از عملگر Not
پایتون مطالعه کنید.
اعلان متغیرهای null در پایتون
در برخی از زبان ها، متغیرها از یک اعلامیه زنده می شوند. آنها مجبور نیستند یک مقدار اولیه به آنها اختصاص داده شود. در آن زبان ها، مقدار پیش فرض اولیه برای برخی از انواع متغیرها ممکن است صفر
باشد. با این حال، در پایتون، متغیرها از دستورات تخصیص زنده می شوند. به بلوک کد زیر نگاهی بیندازید:
>>> print(bar)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'bar' is not defined
>>> bar = None
>>> print(bar)
None
در اینجا می بینید که متغیری با مقدار None
با یک متغیر تعریف نشده متفاوت است. همه متغیرها در پایتون با تخصیص به وجود می آیند. یک متغیر تنها در صورتی زندگی خود را به صورت null
در پایتون شروع می کند که None
را به آن اختصاص دهید.
استفاده از None
به عنوان پارامتر پیش فرض
اغلب، شما از None
به عنوان مقدار پیش فرض برای یک پارامتر اختیاری استفاده می کنید. دلیل بسیار خوبی برای استفاده از None
در اینجا به جای یک نوع قابل تغییر مانند لیست وجود دارد. تابعی مانند این را تصور کنید:
def bad_function(new_elem, starter_list=[]):
starter_list.append(new_elem)
return starter_list
bad_function()
حاوی یک شگفتی تند و زننده است. وقتی آن را با یک لیست موجود فراخوانی می کنید، خوب کار می کند:
>>> my_list = ['a', 'b', 'c']
>>> bad_function('d', my_list)
['a', 'b', 'c', 'd']
در اینجا، بدون هیچ مشکلی "d"
را به انتهای لیست اضافه می کنید.
اما اگر این تابع را چند بار بدون پارامتر starter_list
فراخوانی کنید، رفتار نادرست را مشاهده می کنید:
>>> bad_function('a')
['a']
>>> bad_function('b')
['a', 'b']
>>> bad_function('c')
['a', 'b', 'c']
مقدار پیش فرض برای starter_list
فقط یک بار در زمان تعریف تابع ارزیابی می شود، بنابراین کد هر بار که یک لیست موجود را ارسال نمی کنید، از آن استفاده مجدد می کند.
راه درست برای ساخت این تابع این است که از None
به عنوان مقدار پیش فرض استفاده کنید، سپس آن را آزمایش کنید و در صورت نیاز یک لیست جدید نمونه سازی کنید:
>>> def good_function(new_elem, starter_list=None):
... if starter_list is None:
... starter_list = []
... starter_list.append(new_elem)
... return starter_list
...
>>> good_function('e', my_list)
['a', 'b', 'c', 'd', 'e']
>>> good_function('a')
['a']
>>> good_function('b')
['b']
>>> good_function('c')
['c']
good_function()
با ایجاد یک لیست جدید با هر تماس که در آن لیست موجود را ارسال نمی کنید، همانطور که می خواهید رفتار می کند. این کار می کند زیرا کد شما هر بار که تابع را با پارامتر پیش فرض فراخوانی می کند، خطوط 2 و 3 را اجرا می کند.
استفاده از None
به عنوان یک مقدار null در پایتون
وقتی None
یک شی ورودی معتبر است چه می کنید؟ به عنوان مثال، اگر good_function()
بتواند عنصری را به لیست اضافه کند یا نه، و None
عنصر معتبری برای اضافه کردن نباشد، چه؟ در این مورد، می توانید یک کلاس را به طور خاص برای استفاده به عنوان پیش فرض تعریف کنید، در حالی که از هیچ
کدام متمایز هستید:
>>> class DontAppend: pass
...
>>> def good_function(new_elem=DontAppend, starter_list=None):
... if starter_list is None:
... starter_list = []
... if new_elem is not DontAppend:
... starter_list.append(new_elem)
... return starter_list
...
>>> good_function(starter_list=my_list)
['a', 'b', 'c', 'd', 'e']
>>> good_function(None, my_list)
['a', 'b', 'c', 'd', 'e', None]
در اینجا، کلاس DontAppend
به عنوان سیگنالی برای اضافه نکردن عمل می کند، بنابراین برای آن نیازی به None
ندارید. این شما را آزاد می کند تا هر زمان که بخواهید None
را اضافه کنید.
شما می توانید از این تکنیک زمانی استفاده کنید که امکان None
برای مقادیر بازگشتی نیز وجود دارد. به عنوان مثال، dict.get
به طور پیش فرض اگر کلیدی در فرهنگ لغت یافت نشود، None
را برمی گرداند. اگر None
یک مقدار معتبر در فرهنگ لغت شما بود، می توانید dict.get
را به این صورت فراخوانی کنید:
>>> class KeyNotFound: pass
...
>>> my_dict = {'a':3, 'b':None}
>>> for key in ['a', 'b', 'c']:
... value = my_dict.get(key, KeyNotFound)
... if value is not KeyNotFound:
... print(f"{key}->{value}")
...
a->3
b->None
در اینجا شما یک کلاس سفارشی KeyNotFound
تعریف کرده اید. اکنون، به جای برگرداندن None
زمانی که یک کلید در فرهنگ لغت نیست، می توانید KeyNotFound
را برگردانید. این شما را آزاد می کند تا None
را برگردانید در حالی که این مقدار واقعی در فرهنگ لغت است.
رمزگشایی هیچ
کدام در Tracebacks
وقتی NoneType
در traceback شما ظاهر می شود، به این معنی است که چیزی که انتظار نداشتید None
باشد در واقع None
بوده است، و سعی کرده اید از آن به گونه ای استفاده کنید که نمی توانید از None
استفاده کنید. تقریبا همیشه، به این دلیل است که شما سعی می کنید متدی را روی آن فراخوانی کنید.
به عنوان مثال، شما در بالا در my_list
append()
را فراخوانی کردید، اما اگر به نحوی به
چیزی غیر از لیست تبدیل my_list، append()
شکست می خورد:
>>> my_list.append('f')
>>> my_list
['a', 'b', 'c', 'd', 'e', None, 'f']
>>> my_list = None
>>> my_list.append('g')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'append'
در اینجا، کد شما AttributeError
بسیار رایج را افزایش می دهد زیرا شی زیربنایی، my_list
، دیگر یک لیست نیست. شما آن را روی None
تنظیم کرده اید، که نمی داند چگونه append()
را اضافه کند، و بنابراین کد یک استثنا ایجاد می کند.
هنگامی که ردیابی مانند این را در کد خود مشاهده کردید، ابتدا به دنبال مشخصه ای باشید که خطا را افزایش داده است. در اینجا، append()
است. از آنجا، شیئی را که سعی کرده اید با آن فراخوانی کنید، خواهید دید. در این مورد، my_list
است، همانطور که می توانید از کد درست بالای traceback تشخیص دهید. در نهایت، بفهمید که چگونه آن شی به None
تبدیل شده است و اقدامات لازم را برای رفع کد خود انجام دهید.
بررسی Null در پایتون
دو مورد بررسی نوع وجود دارد که در پایتون به null
اهمیت می دهید. اولین مورد زمانی است که هیچ کدام را
برمی گردانید:
>>> def returns_None() -> None:
... pass
این حالت شبیه به زمانی است که شما اصلا دستور بازگشت
ندارید، که به طور پیش فرض None
را برمی گرداند.
مورد دوم کمی چالش برانگیزتر است. این جایی است که شما مقداری را می گیرید یا برمی گردانید که ممکن است None
باشد، اما ممکن است نوع دیگری (تک) نیز باشد. این مورد مانند کاری است که شما با re.match
در بالا انجام دادید، که یا یک شی Match
یا None
را برگرداند.
این فرآیند برای پارامترها مشابه است:
from typing import Any, List, Optional
def good_function(new_elem:Any, starter_list:Optional[List]=None) -> List:
pass
شما good_function()
را از بالا تغییر می دهید و اختیاری
را از تایپ
وارد می کنید تا یک اختیاری [مطابق]
برگردانید.
نگاهی به زیر کاپوت
در بسیاری از زبان های دیگر، null
فقط مترادف 0 است
، اما null
در پایتون یک شی کامل است:
>>> type(None)
<class 'NoneType'>
این خط نشان می دهد که None
یک شی است و نوع آن NoneType
است.
هیچ
کدام به خودی خود در زبان به عنوان null
در پایتون تعبیه نشده است:
>>> dir(__builtins__)
['ArithmeticError', ..., 'None', ..., 'zip']
در اینجا، می توانید None
را در لیست __builtins__
مشاهده کنید که فرهنگ لغتی است که مفسر برای ماژول داخلی
نگه می دارد.
هیچ کدام
یک کلمه کلیدی نیست، درست مانند True
و False
. اما به همین دلیل، شما نمی توانید مستقیما از __builtins__
به None
دسترسی پیدا کنید، به عنوان مثال، ArithmeticError
. با این حال، می توانید آن را با یک ترفند getattr()
دریافت کنید:
>>> __builtins__.ArithmeticError
<class 'ArithmeticError'>
>>> __builtins__.None
File "<stdin>", line 1
__builtins__.None
^
SyntaxError: invalid syntax
>>> print(getattr(__builtins__, 'None'))
None
هنگامی که از getattr()
استفاده می کنید، می توانید None
واقعی را از __builtins__
دریافت کنید، که نمی توانید با درخواست آن با __builtins__ انجام دهید. هیچ کدام
.
حتی اگر پایتون کلمه NoneType
را در بسیاری از پیام های خطا چاپ می کند، NoneType
یک شناسه در پایتون نیست. در داخلی
نیست. فقط با type(None)
می توانید به آن دسترسی پیدا کنید.
هیچ کدام
تک نفره نیستند. یعنی کلاس NoneType
فقط همان نمونه None
را به شما می دهد. فقط یک None
در برنامه پایتون شما وجود دارد:
>>> my_None = type(None)() # Create a new instance
>>> print(my_None)
None
>>> my_None is None
True
حتی اگر سعی می کنید یک نمونه جدید ایجاد کنید، باز هم هیچ
کدام موجود را دریافت می کنید.
با استفاده از id()
می توانید ثابت کنید که None
و my_None
یک شی هستند:
>>> id(None)
4465912088
>>> id(my_None)
4465912088
در اینجا، این واقعیت که id
مقدار صحیح یکسانی را برای None
و my_None
خروجی می دهد به این معنی است که آنها در واقع یک شی هستند.
توجه: مقدار واقعی تولید شده توسط id
در سیستم ها و حتی بین اجرای برنامه متفاوت خواهد بود. تحت CPython، محبوب ترین زمان اجرا پایتون، id()
کار خود را با گزارش آدرس حافظه یک شی انجام می دهد. دو شی که در یک آدرس حافظه زندگی می کنند، یک شی هستند.
اگر بخواهید به هیچ
کدام اختصاص دهید، یک SyntaxError
دریافت خواهید کرد:
>>> None = 5
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
SyntaxError: can't assign to keyword
>>> None.age = 5
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'age'
>>> setattr(None, 'age', 5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'age'
>>> setattr(type(None), 'age', 5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't set attributes of built-in/extension type 'NoneType'
تمام مثال های بالا نشان می دهد که شما نمی توانید None
یا NoneType
را تغییر دهید. آنها ثابت های واقعی هستند.
شما نمی توانید NoneType
را نیز زیر کلاس کنید:
>>> class MyNoneType(type(None)):
... pass
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: type 'NoneType' is not an acceptable base type
این traceback نشان می دهد که مفسر به شما اجازه نمی دهد کلاس جدیدی بسازید که از type(None)
به ارث می برد.
نتیجه
None
یک ابزار قدرتمند در جعبه ابزار پایتون نیست. مانند درست
و غلط
، None
یک کلمه کلیدی تغییرناپذیر است. به عنوان null
در پایتون، از آن برای علامت گذاری مقادیر و نتایج از دست رفته و حتی پارامترهای پیش فرض استفاده می کنید که انتخاب بسیار بهتری نسبت به انواع قابل تغییر است.
اکنون می توانید:
- تست برای
هیچ کدام
باis
ونیست
- انتخاب کنید چه زمانی
None
یک مقدار معتبر در کد شما باشد - از
None
و جایگزین های آن به عنوان پارامترهای پیش فرض استفاده کنید - رمزگشایی
None
وNoneType
در tracebacks خود - استفاده از
None
وOptional
در نکات تایپ
چگونه از null
در پایتون استفاده می کنید؟ نظر خود را در بخش نظرات زیر بنویسید!