برنامه‌نویسی پایتون فهرست مطالب
فصل ۲۵

کد پایتونی، بخش اول

تا اینجا کد شما کار می‌کرد. از این فصل به بعد، کدتان پایتونی می‌شود: کوتاه‌تر، خواناتر و شبیه چیزی که یک برنامه‌نویس حرفه‌ای می‌نویسد، نه شبیه زبان دیگری که به پایتون ترجمه شده باشد.

در این فصل می‌خوانید
  1. برگرداندن چند مقدار
  2. باز کردن پیشرفته
  3. ستاره در محل صدا زدن
  4. تابع enumerate
  5. تابع zip
  6. درک لیستی
  7. شرط در درک لیستی
  8. درک تودرتو
  9. درک دیکشنری و مجموعه
  10. for و else
  11. دستور with
  12. عملگر گردالی
  13. توابع any و all
  14. درستی‌پذیری

۱. برگرداندن چند مقدار

# A function returns one value - but that value can be a tuple,
# which looks exactly like "returning several values".
def min_max(numbers: list[int]) -> tuple[int, int]:
    return min(numbers), max(numbers)      # the parentheses are optional


def statistics(numbers: list[int] | list[float]) -> tuple[float, float, float]:
    return sum(numbers), sum(numbers) / len(numbers), max(numbers)


def main() -> None:
    my_list = [3, 77, -6, 14, 31]

    low, high = min_max(my_list)
    print(f'Min: {low}, Max: {high}')

    total, average, biggest = statistics(my_list)

    # '_' is the usual name for a value we do not care about.
    _, average, _ = statistics(my_list)
    print(average)

چند خروجی: نمونه 01_multiple_return.py

زیرخط تنها، نامی است که همه برنامه‌نویس‌های پایتون برای «مقداری که لازمش ندارم» به کار می‌برند. کاملاً یک متغیر عادی است ولی به خواننده علامت می‌دهد که نادیده‌اش بگیرد.

۲. باز کردن پیشرفته

def main() -> None:
    # Swapping without a temporary variable.
    a, b = 1, 2
    a, b = b, a
    print(a, b)

    # '*' collects the rest into a list.
    first, *rest = [1, 2, 3, 4, 5]
    print(first, rest)

    *start, last = [1, 2, 3, 4, 5]
    print(start, last)

    first, *middle, last = [1, 2, 3, 4, 5]
    print(first, middle, last)

    # Nested unpacking.
    name, (city, code) = ('Ali', ('Rasht', 41))
    print(name, city, code)

    # Very common on a list of pairs.
    pairs = [('math', 20), ('physics', 18)]
    for subject, score in pairs:
        print(subject, score)

    # Merging containers with * and **.
    list1 = [1, 2]
    list2 = [3, 4]
    print([*list1, *list2])

    dict1 = {'a': 1}
    dict2 = {'b': 2}
    print({**dict1, **dict2})

باز کردن: نمونه 02_unpacking.py

2 1
1 [2, 3, 4, 5]
[1, 2, 3, 4] 5
1 [2, 3, 4] 5
Ali Rasht 41
[1, 2, 3, 4]
{'a': 1, 'b': 2}

ستاره فقط یک بار در هر باز کردن مجاز است و «بقیه» را همیشه در یک لیست جمع می‌کند، حتی اگر خالی باشد. ادغام دو لیست یا دو دیکشنری با ستاره، تمیزترین راه موجود است.

۳. ستاره در محل صدا زدن

# In a DEFINITION, * and ** collect the arguments.
def print_all(*args, **kwargs):
    print('args:', args)
    print('kwargs:', kwargs)


def main() -> None:
    print_all(1, 2, 3, name='Ali', age=20)

    # In a CALL, * and ** do the opposite: they spread a container.
    data = [1, 2, 3]
    info = {'name': 'Ali', 'age': 20}
    print_all(*data, **info)          # exactly the same call as above

    # A very frequent use: passing the items of a list to print().
    words = ['Kadoos', 'Institute', 'Rasht']
    print(*words)                     # Kadoos Institute Rasht
    print(*words, sep=' - ')

    # And to hand a configuration dictionary to a function.
    def connect(host: str, port: int, timeout: float = 5.0):
        print(f'connecting to {host}:{port} (timeout {timeout})')

    settings = {'host': 'localhost', 'port': 5432}
    connect(**settings)

ستاره در صدا زدن: نمونه 03_star_args_at_call.py

یک علامت، دو معنی

ستاره در تعریف تابع، آرگومان‌ها را جمع می‌کند. ستاره در صدا زدن تابع، یک ظرف را باز می‌کند. دقیقاً برعکس هم. یادآوری‌اش ساده است: ستاره همیشه بین «چند چیز جدا» و «یک ظرف» تبدیل انجام می‌دهد.

۴. تابع enumerate

names = ['Ali', 'Sara', 'Reza']


# The C way - it works, but it is not Python.
for i in range(len(names)):
    print(i, names[i])

print('---')

# enumerate() gives the index AND the item.
for index, name in enumerate(names):
    print(index, name)

print('---')

# 'start' changes the first index - perfect for a numbered menu.
for number, name in enumerate(names, start=1):
    print(f'{number}) {name}')

print('---')

# enumerate() works on anything we can loop over.
for index, letter in enumerate('Kadoos'):
    print(index, letter)

تابع enumerate: نمونه 04_enumerate.py

قانون ساده

هر بار که دیدید for i in range(len(x)) نوشته‌اید، بدانید که enumerate راه درست است. این پرتکرارترین نشانه‌ای است که کد از زبان دیگری ترجمه شده است. پارامتر start=1 هم برای منوها که از یک شماره‌گذاری می‌شوند عالی است.

۵. تابع zip

subjects = ['math', 'physics', 'english']
scores = [20, 18, 19]
teachers = ['Ahmadi', 'Rezaei', 'Karimi']

# zip() walks several sequences at the same time.
for subject, score in zip(subjects, scores):
    print(f'{subject}: {score}')

# It accepts more than two.
for subject, score, teacher in zip(subjects, scores, teachers):
    print(f'{subject:<10}{score:>3}  ({teacher})')

# zip() stops at the SHORTEST sequence.
print(list(zip([1, 2, 3], 'ab')))

# strict=True (Python 3.10+) raises an error when the lengths differ,
# which catches a bug instead of hiding it.
try:
    print(list(zip([1, 2, 3], 'ab', strict=True)))
except ValueError as e:
    print('strict zip:', e)

# Building a dictionary out of two lists.
print(dict(zip(subjects, scores)))

# zip(*matrix) transposes a matrix - a classic trick.
matrix = [[1, 2, 3], [4, 5, 6]]
print([list(row) for row in zip(*matrix)])

تابع zip: نمونه 05_zip.py

پارامتر strict=True از پایتون ۳٫۱۰ اضافه شده و ارزش دانستن دارد: به جای اینکه بی‌سروصدا اعضای اضافی را دور بیندازد، خطا ایجاد می‌کند. اگر دو لیست شما باید هم‌طول باشند، همیشه بنویسیدش.

ترفند zip(*matrix) ماتریس را ترانهاده می‌کند: ستاره سطرها را باز می‌کند و zip ستون به ستون جفتشان می‌کند.

۶. درک لیستی

درک لیستی شاید معروف‌ترین ویژگی پایتون باشد: ساختن یک لیست در یک عبارت.

# A comprehension builds a list in one expression.
#   [ WHAT      for ITEM in SEQUENCE ]

# The long way:
squares = []
for x in range(10):
    squares.append(x ** 2)
print(squares)

# The Pythonic way:
squares = [x ** 2 for x in range(10)]
print(squares)

# It works on anything we can loop over.
names = ['ali', 'sara', 'reza']
print([name.capitalize() for name in names])
print([len(name) for name in names])

# Turning the lines of a file into numbers (see session 18).
lines = ['22\n', '-7\n', '13\n']
print([int(line.strip()) for line in lines])

درک لیستی: نمونه 06_list_comprehension.py

چطور بخوانیمش

قالبش این است: [چه چیزی for هر عضو in دنباله]. از وسط شروع کنید: «برای هر x در بازه ده‌تایی»، بعد به اول برگردید: «مربعش را بگذار».

کجا به وظیفه آن نبرید

درک لیستی برای ساختن لیست است. اگر فقط می‌خواهید کاری انجام دهید (مثلاً چاپ کنید) و نتیجه‌ای نمی‌خواهید، حلقه عادی بنویسید. نوشتن [print(x) for x in items] یک لیست بی‌فایده از None ها می‌سازد و بد است. همچنین اگر درک لیستی‌تان از یک خط بلندتر شد یا سه شرط داشت، حلقه عادی خواناتر است.

۷. شرط در درک لیستی

# A condition at the END filters: it decides WHICH items are kept.
squares_of_even = [x ** 2 for x in range(10) if x % 2 == 0]
print(squares_of_even)

names = ['Ali', 'Sara', 'Reza', 'Arad']
print([name for name in names if name.startswith('A')])

# A ternary at the BEGINNING transforms: it decides WHAT is stored.
print(['even' if x % 2 == 0 else 'odd' for x in range(6)])

# Both at the same time.
print([x * 2 if x > 0 else 0 for x in [-3, 1, -5, 7] if x != 1])

# Remember the two positions:
#   [ <expression, may use a ternary>  for x in seq  if <filter> ]

# A realistic example: the passing students of a list of dictionaries.
students = [
    {'name': 'Ali', 'score': 18},
    {'name': 'Sara', 'score': 8},
    {'name': 'Reza', 'score': 12},
]
print([s['name'] for s in students if s['score'] >= 10])

شرط: نمونه 07_comprehension_conditions.py

جای شرطوظیفه آنشکل
آخرفیلتر: کدام اعضا بمانند[x for x in s if cond]
اولتبدیل: چه چیزی ذخیره شود[a if cond else b for x in s]

تفاوت مهم: شرط آخر else ندارد، چون فقط فیلتر می‌کند. شرط اول حتماً باید else داشته باشد، چون عملگر شرطی است و باید همیشه مقداری تولید کند.

۸. درک تودرتو

VALUE = 1
LENGTH = 4

# A 2-D list: the inner comprehension builds one row,
# the outer one repeats it.
two_d = [[VALUE for _ in range(LENGTH)] for _ in range(LENGTH)]
print(two_d)

# NEVER write [[0] * 4] * 4 : every row would be the SAME list.
wrong = [[0] * 4] * 4
wrong[0][0] = 9
print(wrong)          # every row changed!

right = [[0] * 4 for _ in range(4)]
right[0][0] = 9
print(right)          # only the first row changed

# A multiplication table.
table = [[i * j for j in range(1, 5)] for i in range(1, 5)]

# Flattening a 2-D list: the two 'for' are read from left to right,
# exactly like nested loops.
matrix = [[1, 2, 3], [4, 5, 6]]
flat = [value for row in matrix for value in row]
print(flat)

درک تودرتو: نمونه 08_nested_comprehension.py

دام ساختن ماتریس با ضرب

[[0] * 4] * 4 چهار ارجاع به یک لیست می‌سازد، نه چهار لیست. تغییر یک خانه، همه سطرها را عوض می‌کند. این همان دام «ارجاع در برابر کپی» فصل نهم است. راه درست همیشه [[0] * 4 for _ in range(4)] است.

برای صاف کردن ماتریس، دو for پشت سر هم می‌آیند و از چپ به راست خوانده می‌شوند، دقیقاً به همان ترتیبی که اگر حلقه تودرتو می‌نوشتید.

۹. درک دیکشنری و مجموعه

names = ['Ali', 'Sara', 'Reza']
scores = [18, 20, 12]

# Dictionary comprehension: { KEY: VALUE for ... }
print({name: len(name) for name in names})
print({name: score for name, score in zip(names, scores)})
print({name: score for name, score in zip(names, scores) if score >= 15})

# Reversing a dictionary.
original = {'a': 1, 'b': 2}
print({value: key for key, value in original.items()})

# Set comprehension: { VALUE for ... } - duplicates disappear.
sentence = 'kadoos institute rasht'
print({letter for letter in sentence if letter != ' '})

# A generator expression uses ( ) and computes the values one by one,
# without building the whole list in memory (see session 26).
total = sum(x ** 2 for x in range(1_000_000))
print(total)

درک دیکشنری و مجموعه: نمونه 09_dict_set_comprehension.py

همان نحو با پرانتزهای متفاوت: کروشه لیست می‌سازد، آکولاد با دونقطه دیکشنری، آکولاد بدون دونقطه مجموعه، و پرانتز یک مولد که موضوع فصل بعد است. مولد چیزی در حافظه نمی‌سازد و مقدارها را یکی‌یکی تولید می‌کند؛ برای یک میلیون عدد، تفاوتش با لیست، تفاوت چند مگابایت حافظه است.

۱۰. for و else

# The 'else' of a loop runs only when the loop was NOT stopped by a break.
def main() -> None:
    for num in range(2, 30):
        for x in range(2, num):
            if num % x == 0:
                print(f'{num} = {x} * {num // x}')
                break
        else:
            print(f'{num} is prime!')

    # The same idea on a search.
    names = ['Ali', 'Sara', 'Reza']
    target = 'Omid'

    for name in names:
        if name == target:
            print('found!')
            break
    else:
        print(f'{target} is not in the list')

for و else: نمونه 10_for_else.py

این را در فصل هشتم دیدیم. الگویش همیشه یکی است: «جست‌وجو کن؛ اگر چیزی پیدا نشد، این کار را بکن». جایگزینش، یک متغیر پرچم است که کد را شلوغ‌تر می‌کند.

۱۱. دستور with

# Before 'with', closing a file safely needed a try/finally.
def the_old_way() -> str:
    f1 = open('data.txt', 'r', encoding='utf-8')
    try:
        return f1.read()
    finally:
        f1.close()          # runs even when read() raises


# 'with' does exactly the same thing, in one line.
def the_pythonic_way() -> str:
    with open('data.txt', 'r', encoding='utf-8') as f2:
        return f2.read()
    # the file is already closed here


def main() -> None:
    # Several context managers in one 'with'.
    with open('data.txt', encoding='utf-8') as source, \
         open('copy.txt', 'w', encoding='utf-8') as target:
        target.write(source.read())

    print('copied')

دستور with: نمونه 11_with_statement.py

حالا که فصل بیست و چهارم را خوانده‌اید، می‌دانید with چطور کار می‌کند: با __enter__ و __exit__. دو مدیر زمینه را می‌شود در یک with با ویرگول نوشت. بک‌اسلش انتهای خط فقط برای ادامه دادن دستور در خط بعد است.

۱۲. عملگر گردالی

# The walrus operator ':=' assigns AND returns the value at the same time.

# Without it:
numbers = [1, 2, 3, 4, 5]
total = sum(numbers)
if total > 10:
    print(f'the total {total} is big')

# With it:
if (total := sum(numbers)) > 10:
    print(f'the total {total} is big')

# Its best use: a read-until loop written without 'while True'.
while (answer := input('Say something (or "exit"): ')) != 'exit':
    print('You said:', answer)

# And inside a comprehension, to avoid computing the same thing twice.
words = ['python', 'is', 'wonderful']
print([length for word in words if (length := len(word)) > 3])

عملگر گردالی: نمونه 12_walrus.py

نامش از شباهت := به چشم و عاج شیر دریایی می‌آید. وظیفه آن این است که همزمان مقدار بدهد و همان مقدار را برگرداند. بهترین کاربردش، همان حلقه خواندن ورودی است که در فصل هشتم مجبور بودیم با while True و break بنویسیم.

زیاده‌روی نکنید

گردالی وقتی خوب است که یک تکرار را حذف کند. اگر خط را شلوغ و سخت‌خوان کرد، همان انتساب عادی در خط جدا بهتر است. پرانتز دورش هم تقریباً همیشه لازم است.

۱۳. توابع any و all

scores = [18, 12, 9, 20]

# any() -> True when AT LEAST ONE item is true
# all() -> True when EVERY item is true
print(any(score < 10 for score in scores))     # True: somebody failed
print(all(score >= 10 for score in scores))    # False

# The long version needed a loop and a flag:
has_failure = False
for score in scores:
    if score < 10:
        has_failure = True
        break
print(has_failure)

# sum() with a condition counts.
print(sum(1 for score in scores if score >= 10))   # how many passed
print(sum(score for score in scores if score >= 10))

# min/max with a key.
students = [{'name': 'Ali', 'score': 18}, {'name': 'Sara', 'score': 20}]
print(max(students, key=lambda s: s['score'])['name'])

# any()/all() stop as soon as the answer is known - they do not read the rest.
print(any(x > 2 for x in range(1_000_000)))

any و all: نمونه 13_any_all.py

شش خط حلقه و پرچم، تبدیل می‌شود به یک خط خوانا. و sum(1 for ... if ...) اصطلاح استاندارد شمردن اعضایی است که شرطی دارند. هر دو تابع تنبل هستند: به محض معلوم شدن جواب متوقف می‌شوند و بقیه دنباله را نمی‌خوانند.

۱۴. درستی‌پذیری

# In a condition, Python treats these values as False:
#   False, None, 0, 0.0, '', [], (), {}, set(), range(0)
# Everything else is True.

for value in [0, 1, '', 'a', [], [0], {}, {'a': 1}, None, set()]:
    print(repr(value), '->', bool(value))

names: list[str] = []

# Not Pythonic:
if len(names) == 0:
    print('empty (long version)')

# Pythonic:
if not names:
    print('empty (short version)')

names.append('Ali')
if names:
    print('there is at least one name')

درستی‌پذیری: نمونه 14_truthiness.py

مقدارهایی که نادرست حساب می‌شوند
False None 0 0.0 '' [] () {} set()
یک تفاوت مهم

if x: و if x is not None: یکی نیستند. اگر x برابر صفر یا رشته خالی باشد، اولی نادرست است ولی دومی درست. وقتی می‌خواهید بین «مقدار نداده» و «مقدار صفر داده» تفاوت بگذارید، حتماً is not None بنویسید. این دقیقاً همان جایی است که پیش‌فرض None در فصل سیزدهم به کار می‌آمد.

فایل‌های تمرینی این فصل

پوشه S25-pythonic-code-1 شامل نمونه‌های 01_multiple_return.py تا 14_truthiness.py.

خلاصه فصل

به جای ایناین را بنویسید
for i in range(len(x))for i, item in enumerate(x)
حلقه روی اندیس دو لیستzip(a, b)
حلقه با appendدرک لیستی
حلقه با پرچم بولیany() یا all()
حلقه شمارنده با شرطsum(1 for ... if ...)
if len(x) == 0:if not x:
try / finally برای بستنwith
while True با breakگاهی عملگر گردالی
متغیر موقت برای جابه‌جاییa, b = b, a
[[0] * 4] * 4[[0] * 4 for _ in range(4)]

تمرین‌های فصل بیست و پنج

  1. حلقه‌ای که مکعب اعداد ۱ تا ۲۰ را می‌سازد، با درک لیستی بازنویسی کنید.
  2. لیست کلمه‌های بلندتر از چهار حرف یک جمله را بسازید.
  3. دیکشنری {کلمه: طولش} را برای همه کلمات یک جمله بسازید.
  4. مجموعه حروف اول متمایز یک لیست از نام‌ها را بسازید.
  5. یکی از حلقه‌های range(len(...)) فصل‌های قبل را با enumerate() بازنویسی کنید.
  6. با enumerate و zip یک جدول شماره‌دار از نام‌ها و نمره‌ها چاپ کنید.
  7. با for/else جست‌وجویی بنویسید که اگر چیزی پیدا نشد بگوید «پیدا نشد».
  8. با any() و all() جواب دهید: «کسی مردود شده؟» و «همه قبول شده‌اند؟»
  9. یک حلقه while True با break را با عملگر گردالی بازنویسی کنید.
  10. جدول ضرب پنج در پنج را با درک تودرتو بسازید.
فصل قبل: متدهای جادویی فصل بعد: کد پایتونی، بخش دوم