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 به دست می آورید بگیرید. آیا عبارت منظم شما با یک رشته معین مطابقت داشت؟ یکی از دو نتیجه را خواهید دید:

  1. برگرداندن یک شی Match: عبارت منظم شما یک تطابق پیدا کرد.
  2. برگرداندن یک شی هیچ: عبارت منظم شما مطابقت پیدا نکرد.

در بلوک کد زیر، در حال آزمایش هستید که آیا الگوی "خداحافظ" با یک رشته مطابقت دارد یا خیر:

>>> 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 خروجی می دهد به این معنی است که آنها در واقع یک شی هستند.

اگر بخواهید به هیچ کدام اختصاص دهید، یک 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 در پایتون استفاده می کنید؟ نظر خود را در بخش نظرات زیر بنویسید!