Есептер
Еске салайық, математикада n санының факториалы n! ретінде анықталады: n! = 1 ⋅ 2 ⋅ ... ⋅ n. Мысалы, 5! = 1 ⋅ 2 ⋅ 3 ⋅ 4 ⋅ 5 = 120. Факториалды for циклі арқылы оңай есептеуге болатыны анық. Біздің бағдарламамызда (немесе кодтың әртүрлі орындарында) әртүрлі сандардың факториалын бірнеше рет есептеу керек деп елестетіп көрейік. Әрине, факторлық есептеуді бір рет жазуға болады, содан кейін оны қажетті жерге қою үшін Көшіру-Қою пернесін қолдануға болады.
Алайда, егер бастапқы кодта бір рет қателессек, онда бұл қате кодта факториалды есептеуді көшірген барлық жерлерде пайда болады. Сонымен қоса, код мүмкін болатыннан көбірек орын алады. Бір логиканы қайта-қайта жазбау үшін бағдарламалау тілдерінің функциялары бар.
Функциялар программаның қалған бөлігінен оқшауланған және олар шақырылған кезде ғана орындалатын код бөлімдері. Сіз sqrt(), len() және print() функцияларын көрдіңіз. Олардың барлығының ортақ қасиеті бар: олар параметрлерді қабылдай алады (нөл, бір немесе одан да көп) және мәнді қайтара алады (бірақ олар болмауы мүмкін). Мысалы, sqrt() функциясы бір параметрді алып, мәнді (санның түбірі) қайтарады. print() функциясы параметрлердің айнымалы санын қабылдайды және ештеңені қайтармайды.
Бір параметрді - санды қабылдайтын және осы санның факториалы мәнін қайтаратын factorial() функциясын жазу жолын көрсетейік.
Бірнеше түсініктеме берейік. Біріншіден, функция коды бағдарламаның басында, дәлірек айтсақ, factorial() функциясын қолданғымыз келетін жердің алдында орналасуы керек. Бұл мысалдың бірінші жолы функциямыздың сипаттамасы болып табылады. factorial()– идентификатор, яғни функциямыздың атауы. Жақшадағы идентификатордан кейін біздің функция қабылдайтын параметрлер тізімі бар. Тізім үтірмен бөлінген параметр идентификаторларынан тұрады. Біздің жағдайда тізім бір n мәнінен тұрады. Жолдың соңына қос нүкте қойылады.
Одан кейін блок ретінде құрастырылған функцияның денесі келеді, яғни шегініс(табуляция). Функцияның ішінде n факторлық мәні есептеледі және res айнымалысында сақталады. Функцияны тоқтататын және res айнымалысының мәнін қайтаратын return res операторымен аяқталады.
Қайтару нұсқауы функцияның кез келген жерінде пайда болуы мүмкін, оның орындалуы функцияны тоқтатады және көрсетілген мәнді шақырылған жерге қайтарады. Егер функция мәнді қайтармаса, онда қайтару операторы қайтару мәнінсіз пайдаланылады. Мәнді қайтаруды қажет етпейтін функцияларда қайтару мәлімдемесі болмауы мүмкін – оларды процедура деп те атайды.
Тағы бір мысал келтірейік. Екі санды қабылдайтын және олардың максимумын қайтаратын max() функциясын жазайық (шын мәнінде мұндай функция Python-да бар).
Енді үш санды қабылдайтын және олардың максимумын қайтаратын max3() функциясын жаза аламыз.
Біздің құрастырған функцияларымыз 2-3 ұана сан қабылдай алады, демек әлсізб нашар функция болып тұр. Ал Python-ның өзінің негізгі max() функциясы аргументтердің бірнеше санын қабылдап, олардың максимумын қайтара алады. Мұндай функцияны қалай жазуға болатынына мысал келтірейік.
Осы функцияға жіберілген барлық параметрлер функцияны жариялау жолындағы жұлдызшамен көрсетілгендей a деп аталатын бір кортежге жиналады.
Функцияның сыртында жарияланған айнымалы мәндерін функцияның ішіндегі пайдалануға болады.
Мұнда a
айнымалысына 1 мәні тағайындалады және f()
функциясы бұл мәнді басып шығарады, тіпті бұл айнымалы f
функциясы жарияланғанға дейін инициализацияланбаған. f()
шақырылғанда, a
мәні әлдеқашан тағайындалған, сондықтан f()
оны экранда көрсете алады.
Мұндай айнымалылар (функцияның сыртында жарияланған, бірақ функцияның ішінде қолжетімді) глобальды деп аталады.
Бірақ функцияның ішінде айнымалы мәнді инициализацияласаңыз, бұл айнымалы мәнді функциядан тыс пайдалана алмайсыз. Мысалы:
Біз NameError: name 'a' is not defined
қатесін аламыз: 'a'
атауы анықталмаған. Функция ішінде жарияланған мұндай айнымалылар локалдьді деп аталады. Бұл айнымалылар функция шыққаннан кейін қолжетімсіз болады.
Функция ішіндегі глобальды айнымалының мәнін өзгертуге тырыссаңыз, нәтиже қызықты болады:
1 және 0 сандары басып шығарылады, а
айнымалысының мәні функцияның ішінде өзгерсе де, функцияның сыртында ол өзгеріссіз қалады! Бұл функцияның кездейсоқ өзгерістерінен жаһандық айнымалыларды «қорғау» үшін жасалады. Мысалы, егер функция циклден i
айнымалысы арқылы шақырылса және бұл функция циклды ұйымдастыру үшін i
айнымалысын да пайдаланса, онда бұл айнымалылар әртүрлі болуы керек. Соңғы сөйлемді түсінбесеңіз, келесі кодты қарап шығыңыз және функцияның ішінде i
айнымалысы өзгертілсе, оның қалай жұмыс істейтіні туралы ойланыңыз.
Егер глобальды айнымалы i функцияның ішінде өзгертілсе, біз мынаны аламыз:
5! = 1 5! = 2 5! = 6 5! = 24 5! = 120
Сонымен, егер қандай да бір айнымалының мәні функцияның ішінде өзгертілсе, онда бұл атаудағы айнымалы локальді айнымалыға айналады және оның модификациясы бірдей атпен глобальды айнымалыны өзгертпейді.
Ресми түрде: Python интерпретаторы берілген функция үшін жергілікті айнымалыны қарастырады, егер оның кодында айнымалының мәнін өзгертетін кем дегенде бір нұсқау болса, онда бұл айнымалы локальді болып саналады және оны инициализациялау алдында пайдалану мүмкін емес; Айнымалының мәнін өзгертетін нұсқаулар =
, +=
операторлары, сонымен қатар айнымалыны for
циклінің параметрі ретінде пайдалану болып табылады. Сонымен қатар, айнымалыны өзгертетін нұсқау ешқашан орындалмаса да, интерпретатор мұны тексере алмайды және айнымалы әлі де жергілікті болып саналады. Мысалы:
Қате орын алды: UnboundLocalError: local variable 'a' referenced before assignment
. Атап айтқанда, f()
функциясында a
идентификаторы локальді айнымалыға айналады, өйткені функцияда, тіпті ол ешқашан орындалмаса да айнымалыны өзгертетін пәрмен бар (бірақ интерпретатор мұны бақылай алмайды). Сондықтан a
айнымалысын басып шығару инициализацияланбаған локальді айнымалыға қол жеткізуге әкеледі.
Функцияның глобальды айнымалының мәнін өзгертуі үшін, бұл айнымалыны функцияның ішіндегі global
кілт сөзін пайдаланып, глобальды деп жариялау қажет:
Егер сізге бір мәнді емес, екі немесе одан да көп мәнді қайтаратын функция қажет болса, онда бұл мақсат үшін функция екі немесе одан да көп мәндердің тізімін қайтара алады:
return [a, b]
Содан кейін функция шақыруының нәтижесі бірнеше тағайындауда қолданылуы мүмкін:
n, m = f(a, b)
Алдыңғы мысалда экранда 1 1 көрсетіледі, өйткені a
айнымалысы глобальды деп жарияланған және оны функцияның ішінде өзгерту айнымалының функциядан тыс қолжетімді болуына әкеледі.
Дегенмен, функция ішіндегі глобальды айнымалылардың мәндерін өзгертпеген дұрыс. Функцияңыз кейбір айнымалы мәнді өзгертуі керек болса, ол осы мәнді қайтарса жақсы болар еді және сіз функцияны шақырған кезде бұл мәнді айнымалыға нақты тағайындайсыз. Егер сіз осы ережелерді орындасаңыз, онда функциялар кодқа тәуелсіз және оларды бір бағдарламадан екіншісіне оңай көшіруге болады.
Мысалы, сіздің бағдарламаңыз f айнымалысында сақтағыңыз келетін кіріс санының факториалын есептеуі керек делік. Міне, мұны істемеу жолы:
Бұл код нашар жазылған, себебі оны қайта пайдалану қиын. Ертең факторлық функцияны басқа бағдарламада пайдалану қажет болса, бұл функцияны осы жерден көшіріп алып, оны жаңа бағдарламаңызға қоюға болмайды. Есептелген мәнді қайтару жолын өзгертуге тура келеді.
Жоғарыда көргеніміздей, функция басқа функцияны шақыра алады. Бірақ функция өзін шақыра алады! Мұны мысал ретінде факториал есептеу функциясын қолданып қарастырайық. 0!=1, 1!=1 болатыны белгілі. n мәнін қалай есептеу керек! үлкен n үшін? Егер біз (n-1)! мәнін есептей алатын болсақ, онда n!-ді оңай есептей аламыз, өйткені n!=n⋅(n-1)!. Бірақ қалай есептеу керек (n-1)!? (n-2)! есептесек, онда (n-1)!=(n-1)⋅(n-2)! де есептей аламыз!. (n-2) қалай есептеу керек!? Тек... Ақырында біз 1-ге тең 0! мәніне жетеміз. Сонымен факториалды есептеу үшін кішірек сан үшін факториал мәнін қолдануға болады. Мұны Python бағдарламасында да жасауға болады:
Бұл әдіс (өзін шақыратын функция) рекурсия, ал функцияның өзі рекурсивті деп аталады.
Рекурсивті функциялар программалаудағы қуатты механизм болып табылады. Өкінішке орай, олар әрқашан тиімді бола бермейді. Сондай-ақ, рекурсияны пайдалану жиі қателіктерге әкеледі. Бұл қателердің ең жиі кездесетіні шексіз рекурсия болып табылады, функционалдық шақырулар тізбегі ешқашан аяқталмайды және компьютердегі бос жад таусылғанша жалғасады. Шексіз рекурсияның мысалы осы бөлімнің эпиграфында келтірілген. Шексіз рекурсияның ең көп тараған екі себебі:
if n == 0
екенін тексеруді ұмытсақ, онда factorial(0)
factorial(-1)
шақырады, ол factorial(-2)
және т.б.factorial(n)
функциясы factorial(n)
шақырса, онда нәтиже де шексіз тізбек болады.Сондықтан рекурсивті функцияны жасағанда, ең алдымен рекурсияның аяқталу шарттарын рәсімдеу керек және рекурсия неліктен бір кездері тоқтатылатыны туралы ойлану керек.