Python Funksiyalarının İşləmə Məntiqi və __code__ Atributu

Bir python faylı işlənərkən aşagıdakı kimi icra olunur :

Python Funksiyalarının İşləmə Məntiqi və __code__ Atributu

Bir python faylı işlənərkən aşagıdakı kimi icra olunur :

Your Python Code (.py file) 
        ↓ 
    [Parser] 
        ↓ 
    Abstract Syntax Tree (AST) 
        ↓ 
    [Compiler] 
        ↓ 
    Bytecode (.pyc file) 
        ↓ 
    [Python Virtual Machine] 
        ↓ 
    CPython Interpreter (C code) 
        ↓ 
    System Calls 
        ↓ 
    Operating System Kernel 
        ↓ 
    CPU Execution

Bu mərhələlərin birindən bytecode -dan bəhs edəcəm.

Python funksiyalarının işləmə məntiqi iki səviyyədə baş verir:

  1. Stack səviyyəsi(variable səviyyəsi)
  2. Funksiyanın öz səviyyəsi (call səviyyəsi)

Yəni, bir funksiya başqa funksiyanı çağırırsa, həmin funksiya işə düşür, sonra çağıran funksiya davam edir.


Modul və Stack Frame

Proqram başladıqda birinci olaraq modul işə düşür. Modul Python-da ən yüksək frame-dir. Python bunu avtomatik yaradır. Modul özü funksiya deyil, amma öz stack frame-i var.Hər bir xarici modulu import etdikdə python avtomatik ona global dəyişən kimi verir.

Call funksiyaları və stack frame-lər Python virtual maşınında (Python bytecode) yaranır və onları proqram daxilində praktik olaraq görə bilərik. Call stack funksiyanın səviyyəsində baş verir və hesablana bilir.

Funksiya Misalı: add

def add(a, b): 
    return a + b
  • Bu funksiya iki integer ab qəbul edir və onların cəmini qaytarır.
  • Funksiyanı işə salanda əvvəlcə modul səviyyəsi başlayır.
  • Daha sonra add funksiyası çağırılır.
  • Əgər funksiya başqa funksiyanı çağırsa, onlar zəncirvari işə düşür və nəticə qaytarılır.

Recursion Misalı: Factorial

def factorial(n): 
    if n == 1: 
        return 1 
    else: 
        return n * factorial(n - 1) 
 
factorial(5)
  • factorial(5) çağıranda əvvəlcə factorial(1) icra olunur, sonra factorial(2), 3, 4, 5
  • Yəni funksiya daxilində çağırılan funksiya öncə ,sonra isə özü tamamlanır və bu ardıcıl olaraq gedir .
  • Stack frame-lər LEGB qaydasına görə izolə olunur: Local, Enclosing, Global, Built-in.

LEGB Qaydası və Dəyişənlər

x = "global" 
 
def alter(): 
    x = "enclosing" 
    def inner(): 
        x = "local" 
        print(x) 
    inner() 
    print(x) 
 
alter() 
print(x)
  • inner() funksiyasını çağıranda x local qaydada “local” olacaq.
  • alter() daxilində x inclusion qaydasında “enclosing” olacaq.
  • Modul səviyyəsində x isə global olaraq “global” qalır.

inis Operatorları

A = [1, 3, 3] 
x = 3 
y = 3 
 
x in A   # True 
x == y   # True 
x is y   # True, çünki referans eynidir
  • in iterable-də bərabərliyi yoxlayır.
  • == dəyərlərin bərabərliyini yoxlayır.
  • is referansın eyniliyini yoxlayır (id).

Əgər iki eyni ədədi fərqli dəyişənlərdə saxlasanız:

  • in== bərabərliyini göstərəcək
  • is isə fərqli olduğunu göstərəcək

Stack Frame və Traceback

import sys 
sys._getframe()  # hazırda işləyən frame-i qaytarır 
 
import inspect 
inspect.stack()  # call stack-i qaytarır

Stack trees-lərini print ilə göstərə bilərsiniz.

Cold və Hot Code (Python 3.11+)

  • Cold code: universal bytecode istifadə edir, yavaşdır.
  • Hot code: tez-tez istifadə olunan kod optimizasiya olunur, inline caching ilə 60% daha sürətlidir.

Call stack və stack frame hər çağırışda yaranır, amma hot code-da əməliyyatlar daha sürətlidir.


Bytecode Misalı: String Concatenation

def string_concat(name): 
    return "hello " + name + "!"
  • load_const"hello " string stəkə qoyulur
  • load_fastname dəyişəni stəkə qoyulur
  • BINARY_ADD – toplama əməliyyatı
  • load_const"!" stəkə qoyulur
  • BINARY_ADD – yenidən toplama
  • RETURN_VALUE – nəticə qaytarılır

Alternativ və daha optimal yanaşma:

def string_concat(name): 
    return f"hello {name}!"
  • load_const"hello " stəkə qoyulur
  • load_fastname stəkə qoyulur
  • FORMAT_VALUE + BUILD_STRING – string birləşdirilir
  • RETURN_VALUE – nəticə qaytarılır
Bu üsul aralıq obyekt yaratmır və daha səmərəlidir.

__code__ Atributu və Code Object

Hər bir Python funksiyası bir code object ilə əlaqəlidir.
__code__ atributu funksiyanın icra edilən bytecode və onun atributlarını göstərir.

code = greet.__code__
  • Burada greet(hər hansısa funksiyadır) funksiyasının code obyektini code dəyişəninə atırıq.
print(f"  - Argument count: {code.co_argcount}")
  • co_argcount → funksiyaya verilən argumentlərin sayı
  • Məsələn, def greet(name, age) funksiyasında co_argcount = 2
print(f"  - Local variables: {code.co_nlocals}")
  • co_nlocals → funksiyanın içində təqdim edilmiş lokal dəyişənlərin sayı.
  • Məsələn, x = 5y = 10 varsa, co_nlocals = 2.
print(f"  - Stack size needed: {code.co_stacksize}")
  • co_stacksize → Python virtual maşını üçün funksiyanın işləməsi zamanı lazım olan maksimum stack hündürlüyü.
print(f"  - Variable names: {code.co_varnames}")
  • co_varnames → funksiyada istifadə olunan lokal dəyişənlərin və argumentlərin adları tuple şəklində.Məsələn: ('name', 'age', 'x', 'y')
print(f"  - Constants used: {code.co_consts}")
  • co_consts → funksiyada istifadə olunan sabitlər (constants).
  • Məsələn, return 5 + 10 olarsa, burada 510 constants kimi görünəcək.
  • Funksiyada stringlər və None da constants siyahısına daxildir.
print(f"  - Bytecode size: {len(code.co_code)} bytes")
  • co_code → funksiyanın compiled bytecode-nu saxlayır.
  • len(code.co_code) → bytecode-un bayt ölçüsü.
  • Bu, funksiyanın Python virtual maşın üçün neçə baytlıq əmrlərdən ibarət olduğunu göstərir.

Pythonda dis kitabxanası ilə bunu asanlıqla yoxlamaq olar .dis kitabxanası python kodlarını bytecode formatında oxunaqlı şəklə çevirir.