Pythonda o'zgaruvchilarni nomlashda tagchiziqlardan foydalanish
ᅠ★LEARNER★Menimcha pythonda klasslar bilan ishla(yot)ganlarni ko'pchiligi o'zgaruvchilarni nomlashda ular oldiga tagchiziq ishlatilishini ko'rishgan. Maqolada shu haqida gaplashib o'tamiz.
(!)Ushbu maqolaning medium.com dagi implementatsiyasi mavjud: https://medium.com/@learner-beginner/pythonda-ozgaruvchilarni-tagchiziq-bilan-nomlash-6695eb4d0a1b
_var shaklidagi o'zgaruvchilar
Pythonda Java, C++ va shunga o'xshash tillar kabi o'zgaruvchilar public/private larga ajratilmaydi. O'zgaruvchilarni _var shaklida yozib ketish orqali o'sha kodni yozayotgan dasturchi shu kod bilan ishlovchi boshqa dasturchiga belgi qoldirgan bo'ladi:
Ushbu o'zgaruvchi klassning tashqi interfeysida ahamiyatga ega emas, undan foydalanishingiz shartmas
kabi ma'noda. Bu PEP8 da ham yozilgan.
Bu shaklda o'zgaruvchi yozishda kichik bir tryukni aytishim mumkin. wildcard import(tarjima qilib aytishga kaminaning so'z boyligi yetmaganligi sababli uni o'z holicha yozib ketyapman) yordamida modul import qilinganda, tagchiziq bilan boshlanadigan o'zgaruvchilar import qilinmaydi. Keling misol ko'ramiz. Biror py kengaytmali fayl oching, nomini masalan test.py deb nomlaylikda, unga quyidagi kodlarni kiritaylik:
def func_test():
print("func_test ishlayapti!")
def _func_test():
print("_func_test ham ishlayapti!")
Endi o'sha test.py turgan papkaga yana bitta py kengaytmali ixtiyoriy nomdagi fayl ochib, kod yozamiz:
from test import *
func_test()
_func_test()
Kod _func_test funksiyani chaqirishga kelganda "Uzr akasi, bu nomdagi o'zgaruvchi yo'g'akan" mazmunidagi xatolik haqida xabar chiqadi.
[Ilova: wildcard import deyilganda from <module> import * shakldagi sintaksis nazarda tutilgan]
Lekin koddagi import qilishni o'zgartirsak, uni ishlatishimiz mumkin:
import test
test._func_test()
Yuqoridagi namunada istisnoli holatlar ham mavjud, _func_test funksiyamizni wildcard importing orqali ham ishlatishimiz mumkin, bunda __all__ yordam beradi. test.py faylimizni o'zgartirib olamiz:
__all__ = ["func_test", "_func_test"]
def func_test():
print("func_test ishlayapti")
def _func_test():
print("_func_test ham ishlayapti :)")
def func3():
print("__all__ ga yozilmaganligi uchun ishlamaydi!")
Endi buni yana wildcard importing orqali importlab, har bir funksiyamizni chaqirib olganimizda func3 dan tashqari ikkala funksiyalarimiz ishlaydi.
__all__ haqida yaxshiroq tushuntirish uchun stackoverflowga murojaat qilgandim ':) __all__ bu wildcard importing yordamida import qilinganda nimalar import qilinishini aytish uchun o'sha nomlardan tuzilgan list obyekti. Nomlarni stringda yozamiz, __all__ ni ichidagilarga qarab wildcard importing bajariladi, lekin oddiy import qilishda __all__ ni aytayotganlariga quloq solmasdan modul ichidagilarni ishlatishimiz mumkin.
Savol tug'ilishi mumkin, agar modulimiz ichida bir X funksiya ishlashida o'sha moduldagi Y funksiyasi kerak bo'lsa, __all__ ga Y ni belgilamasdan wildcard importing ni amalga oshirsak-chi, xatolik bo'lmaydimi?
Yo'q, bu holatda ham kodimiz ishlaydi. Quyidagi kichik misolchani ishlatib ko'rishingiz mumkin.
arithmetic.py kodi:
__all__ = ["qoshish", "qoldiqli_bolish"]
def qoshish(a, b):
return a+b
def ayrish(a, b):
return a-b
def qoldiqli_bolish(a, b):
while a>=b:
a = ayrish(a, b)
return a
qoldiqli_bolish funksiyasi ayrish funksiyasidan foydalanyapti, lekin __all__ ichida ayrish funksiyasi yo'q. Ushbu modulni import qilib ishlatib ko'ramiz(va u ishlaydi):
from arithmetic import *
print(qoldiqli_bolish(11, 4))
Shu joyiga kelganda aytish kerak-ki, wildcard importing unchalik yaxshi narsamas, bunda asosiy koddagi ba'zi nomlar moduldagi bilan bir xil bo'lib qolganda kutilgan natija bermaydi, birini ustiga boshqasi yozilib chalkashliklar kelib chiqishi mumkin.
var_ shaklidagi o'zgaruvchilar
Bunday usulda biz shunchaki ba'zi o'zgaruvchilarni nomlashdagi xatoliklarni oldini olamiz. Masalan funksiyamizga ikkita argument berish kerak, ularni mos ravishda var, class deb nomladik, lekin class kalit so'z bo'lganligi uchun kod xatolik beradi. Yaxshisi class ni class_ deb nomlab qo'ya qolamiz.
Bu foydali usul deyish mumkin. Ko'pchilik pythonni endi boshlaganlar ba'zi o'zgaruvchilarni nomlashda built-in funksiyalar nomlari, obyekt turlaridan o'zgaruvchiga nom sifatida o'zlari bilib, ammo ahamiyat bermagan holda foydalanib ketishadi, masalan:
min = 3
list = [1,2,3]
bu holatda xatolik yo'q, ishlayapti. Lekin shu kod ichida min funksiyasidan foydalanishga to'g'ri kelib qolganda yoki list tipi bilan ishlash kerak bo'lganda bu xatolikka olib keladi. Shu holatlarni oldini olish uchun o'zgaruvchilarni min_ va list_ kabi nomlab olishimiz mumkin. Bu holat PEP8da izohlab ketilgan:
single_trailing_underscore_
: used by convention to avoid conflicts with Python keyword, e.g.:
Tkinter.Toplevel(master, class_='ClassName')
__var shaklidagi o'zgaruvchilar
Double underscored var - ikkita tagchiziq bilan boshlanadigan o'zgaruvchilar. Bular bilan ishlash biroz e'tibor talab qiladi. Yuqorida ta'kidlaganimdek, python OOPda public/private o'zgaruvchilar bilan ishlamaydi, bu haqida PEP8 da yozilgan:
We don’t use the term “private” here, since no attribute is really private in Python (without a generally unnecessary amount of work).
Lekin yuqoridagi shakldan klasimizda foydalanishimiz errorga olib keladi:
class Test:
def __init__(self):
self.name = "Test class v1"
self.__my_var = 20
t = Test
print(t.__my_var)
Error: AttributeError: 'Test' object has no attribute '__my_var'
Ups, nimadir xato ketdimi? Yozilishicha, Test obyektida __my_var o'zgaruvchisi yo'q ekan, keling unda obyektda nimalar borligini dir built-in funksiyasi yordamida ko'raylik:
print(dir(t))
Natijasi:
['_Test__my_var', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name']
name o'zgaruvchisi o'shanday qolibdi, lekin __my_var nomi _Test__my_var ga o'zgarib qolibdi. Bu narsa pythonda name mangling[neym mengling] - nom buzilishi(deb tarjima qilishimiz) deyiladi. Xo'sh, bu narsani ishlab chiqish nega kerak axir dersiz. Bunday usulda nomlaganimizda interpreter o'zi nomni _ClassNomi__ozgaruvchiNomi shakliga keltirib, keyinroq klass kengaytirilganda kelib chiqishi mumkin bo'lgan nomlar to'qnashuvini oldini oladi. Keling yaxshiroq tushuntirish uchun kod ko'rsatay, yuqoridagi test klasimizni yozib olgach, pastidan undan voris olib Testv2 deb nomlangan klass yozamiz:
class Testv2(Test):
def __init__(self):
super().__init__()
self.name = "Nom qayta yozildi"
self.__my_var = "My_var ham qayta yozildi"
t = Testv2()
print(t.name)
print(t.__my_var)
Yana __my_var ni olmoqchi bo'lganimizda xatolik beradi, endi yana dir yordamida uni ichidagilarni ko'ramiz:
['_Test__my_var', '_Testv2__my_var', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name']
Ikkita tagchiziq orqali o'zgaruvchini nomlaganimizda Parent klasimizdagi o'zgaruvchi qayta yozilmadi, uni o'rniga Testv2 ning o'ziga o'zgaruvchi ochildi.
Pythonda private o'zgaruvchilar yo'qligi, va dirda ko'ringanlarni hisobga olsak, Testv2 obyektidagi __my_var ni chaqirib olishimiz mumkin:
print(t._Testv2__my_var)
Xuddi shundek Testv2 tipidagi obyekt orqali Test klasidagi _my__var ni _Test__my_var deb chaqirib olishimiz mumkin.
Klass ichida ikkita tagchiziqli o'zgaruvchilarni shundayligicha ishlata olamiz:
def my_var(self):
return self.__my_var
Name mangling klass metodlariga ham ta'sir qiladimi? Albatta, u klass ichidagi 2 yoki undan ortiq tagchiziq bilan boshlanuvchi barcha nomlarga ta'sirini o'tkazadi(2 tadan ortiq tagchiziq qo'llanilgandagi holatlarini o'zingiz test qilib ko'rishingiz mumkin).
Yana bir qiziq holatni ko'ramiz:
_Test__my_var = 25
class Test:
def get_my_var(self):
return __my_var
t = Test()
print(t.get_my_var())
Natija: 25
Name mangling klass ichidagi 2 tagchiziq bilan boshlangan nomlarni _ClassNomi__ozgaruvchi_nomi shaklida o'zgartiradi, va yuqoridagi o'zgaruvchini klass ichida shunchaki __my_var shaklida ishlatishimiz mumkin bo'ladi.
"dunder" haqida
Python Community larda, yoki pythonga aloqador sayt/kitoblarda, masalan PEP8 ni o'qiyotganda shu atamani eshitgan/ko'rgan bo'lsangiz kerak. Bu Double Underscore ni qisqartirilgan shakli. Yuqoridagi __my_var ni "dunder my var" deb atashingiz mumkin, yoki __init__ ni ham "dunder init" yoki "dunder init dunder" deb atash ham mumkin.
__var__ shaklidagi o'zgaruvchilar
Odatda bunaqa formatdagi nomlashlarni biz "magic methods" da ko'rganmiz. Masalan __init__, __call__ kabilar. Yuqorida ko'rganimiz __var shaklidan farqli ravishda klass ichida bu formatda o'zgaruvchilarni nomlasak name mangling ga uchramaymiz. Lekin yaxshisi, bu formatda o'zgaruvchilarni nomlamay qo'yganimiz ma'qul.
_ ni o'zgaruvchiga nom sifatida ishlatish
_ ni o'zgaruvchiga nom sifatida qo'llash mumkin, sintaksis jihatdan xatolik yo'q. Odatda bunday formatdan temporary(vaqtinchalik) yoki kerak bo'lmagan qiymatlarni o'zlashtirib qo'yishda foydalaniladi. Masalan:
for _ in range(10):
print("Hello underscore")
Yoki masalan biror funksiyamiz tuple tipida 3 ta elementdan iborat qiymat qaytaradi(yil, oy, kun formatda), bizga oy va kun qiymatlari kerak bo'lsin, kodda esa qiymatni quyidagi shaklda olamiz:
yil, oy, kun = sana()
Lekin yil o'zgaruvchisini kodda keyinchalik ishlatmasak, warning chiqib qoladi. Bunday hollarda agar biror o'zgaruvchi rostan ham kerak bo'lmasa, o'rniga _ ni qo'yishimiz mumkin:
_, oy, kun = sana()
Yana bir tryuk: interpreterda(faqat interpreterda) ishlayotganimizda biror ifodani o'zgaruvchiga saqlamaganimizda u avtomatik _ ga yozilib qoladi, bu haqida docs.python.org da ham ma'lumot bor:
The special identifier_
is used in the interactive interpreter to store the result of the last evaluation; it is stored in thebuiltins
module. When not in interactive mode,_
has no special meaning and is not defined.
Masalan:
>>> 20+3
23
>>> _
23
>>> list()
[]
>>> _.append("Hello")
>>> _.append("World")
>>> _
['Hello', 'World']
Mavzu bo'yicha gaplashadiganlarimiz shulardan iborat edi. Agar biror joyda xatolikka yo'l qo'ygan bo'lsam, yoki sizda qo'shimcha sifatida biror fikr bo'lsa yuqorida ko'rsatilgan muallif manziliga murojaat qilishingiz mumkin, ismingiz maqolada ko'rsatib o'tiladi.
Foydalanilgan manbalar: