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)):
...