Optimizing Python Performance | Uz

Optimizing Python Performance | Uz

@davronbek_dev


Assalom alaykum hammaga. Bu maqolamizda sizlar bilan python kodingnizni yanada tezlashtirish uchun bazi bir tavsiyalar va builtin module'lar haqida gaplashamiz.


Python kodlarni yanada samarali qilish nega kerak?

Buning sizu biz bilgan bir necha sabablari mavjud ulardan asosiylari sifatida quydagilarni ko'rishimiz mumkin bo'ladi:

  • Python dynamic til, ya'ni siz o'zgaruvchini yaratish jarayonizda hech qanday type'lar bermaysiz, buni python o'zi amalga oshiradi.
  • Interpretator: Python interpretatsiya qilinadigan til, ya'ni kod bajarilish vaqtida o'qiladi va tarjima qilinadi. Bu kompilyatsiya qilinadigan tillarga nisbatan sekinroq. GIL interpretatorining bir vaqtning o'zida faqat bitta thread bajarishiga olib keladi. Bu ko'p yadrolli protsessorlarda Python dasturlarining samaradorligini cheklaydi. (Ko'proq)
  • Global Interpreter Lock (GIL): Bu haqida ko'plab gapirish mumkin qisqa qilib buni loyhada bir vaqtning o'zida faqat bitta thread Python kodini bajara olishini anglatadi. (Ko'proq)
  • Avtomatik xotira boshqaruvi: Python avtomatik xotira boshqaruvidan (garbage collection) foydalanadi, bu esa qo'shimcha resurslarni talab qiladi.
class MyClass:  
    def __init__(self):  
      print("Obyekt yaratildi")   
  
    def __del__(self):  
      print("Obyekt o'chirildi")  

def create_object():  
    obj = MyClass()  
    # Funksiya tugaganda, obj avtomatik ravishda o'chiriladi 

create_object() # Garbage collector ishga tushadi va obyektni o'chiradi


Python kodizni tezlashtirish uchun tavsiyalar


Python juda ko'p kutubxona, tayyor modulari borligi haqida eshitdik, ular haqida kam eshitganmiz, bazilarni real loyihalarda qanday holatlarda ishlatish mumkin ekanligni kod misolarda ko'ramiz:

1) Oddiy list'lardan foydalanmasdan NumPy'dagi array'lardan foydalanishga harakat qiling.

-Nega?

Chunki, numpy'dagi arraylar c levelda implement qilingan, katta hajmdagi datalar bilan ishlaganda list'dan ko'ra ancha tez ishlay oladi.

2) List Comprehension'ldardan foydalanish biroz sizni chalg'itishi mumkin lekin unda ancha performance yaxshiroq

#bad
fruits = ["apple", "banana", "cherry", "kiwi", "mango"]
newlist = []
for x in fruits:
  if "a" in x:
    newlist.append(x)
print(newlist)

#good
fruits = ["apple", "banana", "cherry", "kiwi", "mango"]
newlist = [x for x in fruits if "a" in x]
print(newlist)


#Django loyihada
# views.py da 

def user_list(request):  
  # Bad  
  active_users = []  
  for user in User.objects.all():  
    if user.is_active:  
      active_users.append(user)   

  # Good  
  active_users = [user for user in User.objects.all() if user.is_active]

  return render(request, 'user_list.html', {'users': active_users})


3) Importlarda (Do Not Use .dot Operation) aynan sizga qaysi funksiya kerak bo'lsa ushani chaqirib ishlating, butun bir boshli module'ni emas

#bad 
import math
from some_module import *
math.sqrt(64) #here u use .

#good
from math import sqrt
sqrt(64)

negaki siznig yozgan .(nuqta) birinchi bo'lib __getattribute()__ yoki __getattr()__ metodlarni chaqiradi, bular esa metodlarni nomlarini dict'larni qaytarib beradi bu degani esa .(nuqta) operatsiya ko'proq vaqt oladi degani


4) map & filter: bu tayor funksiyalardan katta massivlar bilan ishlash jarayonida, loop bor joylarda har doim foydalanishga harakat qiling

# Bad 
numbers = [1, 2, 3, 4, 5] 
squared = [] 
for num in numbers:  
  squared.append(num**2)  

# Good 
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x**2, numbers))


# Bad 
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 
even = [] 
for num in numbers:
  if num % 2 == 0:
    even.append(num)  

# Good 
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 
even = list(filter(lambda x: x % 2 == 0, numbers))


5) collections & itertools & functools & contextlib: pythoninga bu 2ala modularidan juda foydali funksiyalarni topishingiz mumkin. Bulardan bazilarni kod misolida ko'rib utamiz

  • collections.Counter(): - (most_common() method)
#Counter() - yordamida biror element necha marta qatnashgani topishingiz mumkin bo'ladi

# Bad
words = ['apple', 'banana', 'apple', 'cherry', 'banana', 'date']
word_count = {}
for word in words:
  if word in word_count:
    word_count[word] += 1
  else:
    word_count[word] = 1

# Good
from collections import Counter
words = ['apple', 'banana', 'apple', 'cherry', 'banana', 'date']
word_count = Counter(words)

# Output: Counter({'apple': 2, 'banana': 2, 'cherry': 1, 'date': 1})


# Django views.py da
from collections import Counter

def product_stats(request):
  # Bad
  categories = {}
  for product in Product.objects.all():
    if product.category in categories:
      categories[product.category] += 1
    else:
      categories[product.category] = 1
   
  # Good
  categories = Counter(product.category for product in Product.objects.all())
  return render(request, 'product_stats.html', {'categories': categories})
 


  • namedtuple() - classlardan ko'ra ko'proq memory efficient hisoblanadi bunda faqat read malumotlar tezroq ishlaydi.
from collections import namedtuple

# Create a namedtuple called 'Car' with fields 'make', 'model', and 'year'
Car = namedtuple('Car', ['make', 'model', 'year'])

# Create an instance of the Car namedtuple
my_car = Car(make='Toyota', model='Corolla', year=2020)

# Access fields by name
print(my_car.make) # Output: Toyota
print(my_car.model) # Output: Corolla
print(my_car.year)  # Output: 2020

# You can still access fields by index if you prefer
print(my_car[0]) # Output: Toyota

# Namedtuples are immutable, so this will raise an error:
# my_car.year = 2021 # Uncommenting this will cause AttributeError

  • collections.defaultdict():

#defaultdict - o'z nomi bilan kurishiz mumkin default holatda value berib ketishda ishlatishimiz mumkin

# Bad
word_list = ['apple', 'banana', 'apple', 'cherry', 'banana', 'date']
word_count = {}
for word in word_list:
  if word not in word_count:
    word_count[word] = 0
  word_count[word] += 1

# Good
from collections import defaultdict
word_list = ['apple', 'banana', 'apple', 'cherry', 'banana', 'date']
word_count = defaultdict(int)
for word in word_list:
  word_count[word] += 1

# Output: defaultdict(<class 'int'>, {'apple': 2, 'banana': 2, 'cherry': 1, 'date': 1})

# Define default values in Dictionaries with •get() and ,setdefault()

my_dict = {"item": "footbal", "price": 10.00}
count = my_dict.get("count", 0)
print(count)

count = my_dict.setdefault("count", 0)
print(count) 
print(my_dict)



# Django views.py da
from collections import defaultdict

def order_summary(request):
  # Bad
  product_totals = {}
  for order in Order.objects.all():
    for item in order.items.all():
      if item.product.name not in product_totals:
        product_totals[item.product.name] = 0
      product_totals[item.product.name] += item.quantity
   
  # Good
  product_totals = defaultdict(int)
  for order in Order.objects.all():
    for item in order.items.all():
      product_totals[item.product.name] += item.quantity
   
  return render(request, 'order_summary.html', {'product_totals': dict(product_totals)})


  • itertools.combinations():
combinations(items, r) - berilgan items malumotlar asosida r martalik kambinatsiyalar tuzishda ishlatishiz mumkin.

# Bad
def get_combinations(items, r):
  result = []
  for i in range(len(items)):
    for j in range(i+1, len(items)):
      if r == 2:
        result.append((items[i], items[j]))
      elif r == 3:
        for k in range(j+1, len(items)):
          result.append((items[i], items[j], items[k]))
  return result

# Good
from itertools import combinations
def get_combinations(items, r):
  return list(combinations(items, r))

# OutPut: [(1, 2, 4), (1, 2, 5), (1, 2, 6), (1, 4, 5), (1, 4, 6), (1, 5, 6), (2, 4, 5), (2, 4, 6), (2, 5, 6), (4, 5, 6)] 


  • functools.lru_cache(): ko'p marotaba run bo'layotgan funksiyangiz uchun juda foydali bo'lad oladi. funksiyadan qaytayotgan natija kashlanish orqali biroz saqlanib turiladi
# Bad
def fibonacci(n):
  if n < 2:
    return n
  return fibonacci(n-1) + fibonacci(n-2)

# Good
from functools import lru_cache

@lru_cache(maxsize=None)
def fibonacci(n):
  if n < 2:
    return n
  return fibonacci(n-1) + fibonacci(n-2)


  • any() va all()
# Bad
numbers = [1, 2, 3, 4, 5]
all_positive = True
for num in numbers:
  if num <= 0:
    all_positive = False
    break

# Good
numbers = [1, 2, 3, 4, 5]
all_positive = all(num > 0 for num in numbers)


  • itertools.groupby():
# Bad
data = [('a', 1), ('b', 2), ('a', 3), ('b', 4), ('c', 5)]
grouped = {}
for key, value in data:
  if key not in grouped:
    grouped[key] = []
  grouped[key].append(value)

# Good
from itertools import groupby
from operator import itemgetter

data = [('a', 1), ('b', 2), ('a', 3), ('b', 4), ('c', 5)]
sorted_data = sorted(data, key=itemgetter(0))
grouped = {k: list(map(itemgetter(1), v)) for k, v in groupby(sorted_data, key=itemgetter(0))}


# Django views.py da
from itertools import groupby
from operator import attrgetter

def orders_by_status(request):
  orders = Order.objects.all().order_by('status')
  # Good
  grouped_orders = {
    status: list(orders)
    for status, orders in groupby(orders, key=attrgetter('status'))
  }
  return render(request, 'orders_by_status.html', {'grouped_orders': grouped_orders})


  • slots (alohida mavzu) - memory managmentda ancha as qotadi va machina xotirasida kamroq joy egalay oladi.
# Bad
class Point:
  def __init__(self, x, y):
    self.x = x
    self.y = y

# Good
class Point:
  __slots__ = ['x', 'y']
  def __init__(self, x, y):
    self.x = x
    self.y = y


  • contextlib.suppress():
# Bad
try:
  os.remove('temp_file.txt')
except FileNotFoundError:
  pass

# Good
from contextlib import suppress
import os

with suppress(FileNotFoundError):
  os.remove('temp_file.txt')


  • enumerate & zip - id va value bir vaqtda olishda(enumerate)
def range_len_pattern():
  a = [1,2,4]
  b = [4,5,6]
  for i, (av, bv) in enumerate(zip(a,b)):
    ...


Report Page