~/icsd.ir — bash
SYSTEM_ONLINE

Functional Programming

Functional Programming در پایتون شامل توابعی است که داده‌ها را بدون تغییر state آن‌ها پردازش می‌کنند. در این فصل با ماژول functools، توابع map/filter، closures، lambda، lru_cache و الگوهای functional آشنا می‌شویم.

Functional Programming در پایتون شامل توابعی است که داده‌ها را بدون تغییر state آن‌ها پردازش می‌کنند. در این فصل با ماژول functools، توابع map/filter، closures، lambda، lru_cache و الگوهای functional آشنا می‌شویم.

توابع به‌عنوان شیء درجه اول

در پایتون، توابع شیء هستند:

def greet(name):
    return f"سلام {name}"

# 1) ذخیره در متغیر
say_hi = greet
print(say_hi("علی"))  # سلام علی

# 2) ارسال به تابع دیگر
def call_twice(func, arg):
    return [func(arg), func(arg)]

print(call_twice(greet, "علی"))  # ['سلام علی', 'سلام علی']

# 3) بازگشت از تابع
def make_multiplier(n):
    def multiplier(x):
        return x * n
    return multiplier

double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5))   # 10
print(triple(5))   # 15

Lambda – توابع ناشناس

# lambda برای توابع کوتاه
square = lambda x: x ** 2
print(square(4))  # 16

# مناسب در توابع high-order
nums = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x ** 2, nums))  # [1, 4, 9, 16, 25]
even = list(filter(lambda x: x % 2 == 0, nums))  # [2, 4]

# مرتب‌سازی با key
people = [{"name": "علی", "age": 30}, {"name": "سارا", "age": 25}]
people.sort(key=lambda p: p["age"])

# استفاده مناسب: یک‌خطی، ساده
# عدم استفاده مناسب: lambda x: x.method() if x else None (پیچیده)
توصیه: اگر lambda‌تان بیش از یک خط می‌شود، بهتر است تابع معمولی تعریف کنید.

map و filter

# map - اعمال تابع روی همه عناصر
prices = [100, 200, 300]
with_tax = list(map(lambda p: p * 1.09, prices))
# [109.0, 218.0, 327.0]

# با چند iterable
list1 = [1, 2, 3]
list2 = [10, 20, 30]
sums = list(map(lambda a, b: a + b, list1, list2))
# [11, 22, 33]

# filter - فیلتر بر اساس شرط
ages = [12, 18, 25, 16, 30]
adults = list(filter(lambda x: x >= 18, ages))
# [18, 25, 30]

# با None - فیلتر falsy values
items = [1, 0, "hello", "", None, [1], []]
truthy = list(filter(None, items))
# [1, 'hello', [1]]

چه زمانی list comprehension بهتر است؟

# معمولاً list comprehension خواناتر است
with_tax = [p * 1.09 for p in prices]
adults = [x for x in ages if x >= 18]

# map/filter وقتی تابع آماده دارید
result = list(map(str.upper, ["a", "b", "c"]))  # تمیز
result = [s.upper() for s in ["a", "b", "c"]]   # هم تمیز

functools.reduce

from functools import reduce

# مجموع
total = reduce(lambda a, b: a + b, [1, 2, 3, 4, 5])
print(total)  # 15

# با مقدار شروع
total = reduce(lambda a, b: a + b, [1, 2, 3], 100)
print(total)  # 106

# حداکثر
biggest = reduce(lambda a, b: a if a > b else b, [3, 1, 4, 1, 5, 9])
print(biggest)  # 9

# ترکیب دیکشنری‌ها
dicts = [{"a": 1}, {"b": 2}, {"c": 3}]
combined = reduce(lambda d1, d2: {**d1, **d2}, dicts)
print(combined)  # {'a': 1, 'b': 2, 'c': 3}
نکته: اغلب توابع built-in (مثل sum، max، min) از reduce ساده‌تر و سریع‌ترند.

functools.partial

ساخت تابع جدید با مقادیر از پیش تعیین‌شده برای برخی پارامترها:

from functools import partial

def power(base, exp):
    return base ** exp

# ساخت توابع جدید
square = partial(power, exp=2)
cube = partial(power, exp=3)

print(square(5))  # 25
print(cube(3))    # 27

# مثال کاربردی - HTTP client
import requests
api_get = partial(requests.get, timeout=30, headers={"Authorization": "Bearer token"})

# حالا فقط URL لازم است
response = api_get("https://api.example.com/users")
response = api_get("https://api.example.com/products")

Closures – بسته‌ها

Closure یعنی تابع داخلی که به متغیرهای تابع بیرونی دسترسی دارد:

def make_counter(start=0):
    count = start
    
    def increment():
        nonlocal count  # دسترسی به متغیر بیرونی
        count += 1
        return count
    
    return increment

counter1 = make_counter()
print(counter1())  # 1
print(counter1())  # 2

counter2 = make_counter(100)
print(counter2())  # 101
print(counter2())  # 102

# هر closure حالت خودش را دارد
print(counter1())  # 3

Closure با چند تابع

def make_account(balance=0):
    def deposit(amount):
        nonlocal balance
        balance += amount
        return balance
    
    def withdraw(amount):
        nonlocal balance
        if amount > balance:
            raise ValueError("موجودی کافی نیست")
        balance -= amount
        return balance
    
    def get_balance():
        return balance
    
    return deposit, withdraw, get_balance

deposit, withdraw, balance = make_account(1000)
deposit(500)         # 1500
withdraw(200)        # 1300
print(balance())     # 1300

functools.lru_cache

کش خودکار برای توابع pure:

from functools import lru_cache

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

# بدون کش: O(2^n) - برای 50 دقیقه‌ها طول می‌کشد
# با کش: O(n) - فوری
print(fibonacci(100))  # سریع

# اطلاعات کش
print(fibonacci.cache_info())
# CacheInfo(hits=98, misses=101, maxsize=128, currsize=101)

# پاک کردن کش
fibonacci.cache_clear()

# maxsize=None - کش بدون محدودیت
@lru_cache(maxsize=None)
def expensive(x):
    return slow_computation(x)

cache (Python 3.9+) – معادل lru_cache(maxsize=None)

from functools import cache

@cache
def factorial(n):
    return 1 if n <= 1 else n * factorial(n-1)

Immutability

در functional programming، داده‌ها تغییر نمی‌کنند:

# بد - تغییر in-place
def add_tax_bad(prices):
    for i in range(len(prices)):
        prices[i] *= 1.09  # داده ورودی تغییر کرد!

# خوب - برگرداندن داده جدید
def add_tax_good(prices):
    return [p * 1.09 for p in prices]

original = [100, 200, 300]
with_tax = add_tax_good(original)
# original دست نخورده باقی می‌ماند

# tuple و frozenset برای داده‌های immutable
config = ("host", 8080, "production")  # tuple
allowed = frozenset(["read", "write"])  # frozenset

# dataclass(frozen=True)
from dataclasses import dataclass

@dataclass(frozen=True)
class Point:
    x: float
    y: float

p = Point(1, 2)
# p.x = 5  # خطا - immutable

ترکیب توابع

from functools import reduce

def compose(*funcs):
    """ترکیب چند تابع: compose(f, g, h)(x) = f(g(h(x)))"""
    def composed(x):
        return reduce(lambda acc, f: f(acc), reversed(funcs), x)
    return composed

# مثال
add_one = lambda x: x + 1
double = lambda x: x * 2
square = lambda x: x ** 2

# (((5 + 1) * 2) ** 2) = 144
pipeline = compose(square, double, add_one)
print(pipeline(5))  # 144

Pipelining با Generator

# pipeline داده‌ای - حافظه کم، خوانا
def read_logs(filename):
    with open(filename) as f:
        for line in f:
            yield line.strip()

def filter_errors(lines):
    return (line for line in lines if "ERROR" in line)

def parse(lines):
    return ({"time": l[:19], "msg": l[20:]} for l in lines)

# ترکیب
errors = parse(filter_errors(read_logs("app.log")))
for error in errors:
    print(error)

ماژول operator

توابعی برای عملگرهای استاندارد – مفید در map/filter/reduce:

import operator
from functools import reduce

# به‌جای lambda‌های ساده
nums = [1, 2, 3, 4, 5]

# مجموع
total = reduce(operator.add, nums)  # به‌جای lambda a,b: a+b

# ضرب همه
product = reduce(operator.mul, nums)  # 120

# itemgetter - دسترسی به آیتم
from operator import itemgetter

people = [
    {"name": "علی", "age": 30},
    {"name": "سارا", "age": 25},
]
people.sort(key=itemgetter("age"))  # به‌جای lambda p: p["age"]

# attrgetter - دسترسی به attribute
from operator import attrgetter

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

people = [Person("علی", 30), Person("سارا", 25)]
people.sort(key=attrgetter("age"))

مثال جامع: پایپ‌لاین داده

from functools import reduce, partial
from operator import itemgetter

# داده ورودی
sales = [
    {"product": "فرش کاشان", "price": 5_000_000, "category": "carpet", "qty": 2},
    {"product": "گلیم", "price": 1_500_000, "category": "carpet", "qty": 5},
    {"product": "تابلو فرش", "price": 8_000_000, "category": "art", "qty": 1},
    {"product": "تابلو نقاشی", "price": 3_000_000, "category": "art", "qty": 3},
]

# pipeline functional
def calc_total(item):
    return {**item, "total": item["price"] * item["qty"]}

def filter_category(category):
    return lambda item: item["category"] == category

# پردازش
carpet_sales = list(map(calc_total, filter(filter_category("carpet"), sales)))
total_carpet_revenue = reduce(
    lambda acc, item: acc + item["total"],
    carpet_sales,
    0
)

print(f"درآمد فرش: {total_carpet_revenue:,}")
# درآمد فرش: 17,500,000

# همان با sum و generator (پایتون‌تر!)
total = sum(item["price"] * item["qty"]
            for item in sales
            if item["category"] == "carpet")

بهترین شیوه‌ها

  • برای کارهای ساده، list/dict comprehension خواناتر از map/filter است
  • به‌جای reduce از sum، max، any، all استفاده کنید
  • lru_cache برای توابع pure که سنگین هستند
  • توابع pure (بدون side-effect) راحت‌تر تست می‌شوند
  • tuple و frozenset برای داده‌های immutable
  • partial برای ساخت specialized version از تابع

جمع‌بندی

  • توابع پایتون شیء درجه اول هستند
  • map/filter/reduce ابزارهای functional
  • closures برای حالت خصوصی
  • functools: partial، reduce، lru_cache، cache
  • operator module برای جایگزینی lambda‌های ساده
  • immutability برای کد قابل پیش‌بینی

نمایش سایت

رنگ سایت
حالت نمایش
اندازهٔ متن
خوانایی

این تنظیمات فقط روی مرورگر شما ذخیره می‌شود.