در بخش های قبلی با مفاهیم پایه پایتون، شرط ها، حلقه ها، توابع و دیکشنری ها آشنا شدیم.
در بخش چهارم آموزش پایتون، با محدوده متغیرها و نحوه دسترسی به آن ها در بخش های مختلف برنامه آشنا می شویم. سپس یاد می گیریم چگونه متغیرهای سراسری را مدیریت کنیم و چرا استفاده از ثابت ها می تواند کد ما را خواناتر کند. در ادامه یک بازی حدس عدد را مرحله به مرحله می سازیم و در پایان نیز با اصول دیباگ کردن و پیدا کردن چند خطای رایج در یک برنامه واقعی آشنا می شویم.
این آموزش با الهام از دوره Python Bootcamp از Angela Yu و با بازنویسی آموزشی، مثالهای تکمیلی و تمرینهای اختصاصی تهیه شده است.
1. محدوده متغیرها در پایتون
هر متغیری که در پایتون ایجاد می کنیم، در بخش مشخصی از برنامه قابل دسترسی است. به این محدوده، Scope گفته می شود.
برای مثال:
enemies = 3
def show_enemies():
enemies = 5
print(f"Enemies inside function: {enemies}")
show_enemies()
print(f"Enemies outside function: {enemies}")خروجی:
Enemies inside function: 5
Enemies outside function: 3با اینکه هر دو متغیر نام یکسانی دارند، یکی داخل تابع و دیگری خارج از تابع قرار گرفته است. به همین دلیل پایتون آن ها را دو متغیر جداگانه در نظر می گیرد.
محدوده محلی
متغیری که داخل یک تابع تعریف می شود، معمولا فقط در همان تابع قابل استفاده است.
def calculate_price():
discount = 20
print(discount)
calculate_price()متغیر discount فقط داخل تابع calculate_price وجود دارد.
اگر بخواهیم خارج از تابع به آن دسترسی داشته باشیم:
def calculate_price():
discount = 20
calculate_price()
print(discount)برنامه خطا می دهد، زیرا discount خارج از تابع شناخته شده نیست.
محدوده سراسری
اگر متغیری خارج از تمام توابع تعریف شود، در محدوده سراسری قرار می گیرد.
username = "Ali"
def welcome():
print(f"Welcome {username}")
welcome()تابع می تواند مقدار username را بخواند، چون این متغیر خارج از تابع تعریف شده است.
خروجی:
Welcome Ali2. فضای نام در پایتون
پایتون برای مدیریت نام متغیرها، توابع و سایر اجزای برنامه از مفهوم Namespace یا فضای نام استفاده می کند. هر محدوده می تواند فضای نام مخصوص خودش را داشته باشد.
message = "Outside function"
def show_message():
message = "Inside function"
print(message)
show_message()
print(message)خروجی:
Inside function
Outside functionدر این مثال دو متغیر با نام message داریم، اما هر کدام در فضای نام متفاوتی قرار گرفته اند.
متغیر داخل تابع متعلق به فضای نام محلی تابع است و متغیر بیرون تابع در فضای نام سراسری قرار دارد.
3. نبود محدوده بلوکی در پایتون
در پایتون ساختارهایی مانند if و for معمولا محدوده محلی جدید ایجاد نمی کنند.
برای مثال:
game_level = 3
enemies = ["Skeleton", "Zombie", "Alien"]
if game_level < 5:
new_enemy = enemies[0]
print(new_enemy)خروجی:
Skeletonبا اینکه new_enemy داخل شرط ساخته شده است، بعد از پایان شرط نیز می توانیم به آن دسترسی داشته باشیم. همین اتفاق در حلقه نیز رخ می دهد:
for number in range(3):
result = number * 2
print(result)خروجی:
4اما تابع محدوده مخصوص خودش را ایجاد می کند:
def create_enemy():
enemy = "Zombie"
create_enemy()
print(enemy)این برنامه خطا می دهد، چون متغیر enemy فقط داخل تابع وجود دارد.
پس باید به خاطر داشته باشیم که شرط ها و حلقه ها محدوده محلی مستقل ایجاد نمی کنند، اما توابع این کار را انجام می دهند.
4. تغییر متغیر سراسری داخل تابع
فرض کنید یک متغیر سراسری برای تعداد دشمنان بازی داریم:
enemies = 1اگر بخواهیم مستقیما داخل تابع مقدار آن را تغییر دهیم:
enemies = 1
def increase_enemies():
enemies += 1
increase_enemies()برنامه خطا می دهد.
پایتون تصور می کند enemies داخل تابع یک متغیر محلی است، اما قبل از افزایش مقدار، هیچ مقدار اولیه ای برای آن تعریف نشده است.
استفاده از global
با کلمه کلیدی global می توانیم مشخص کنیم که منظور ما همان متغیر سراسری است.
enemies = 1
def increase_enemies():
global enemies
enemies += 1
print(f"Enemies inside function: {enemies}")
increase_enemies()
print(f"Enemies outside function: {enemies}")خروجی:
Enemies inside function: 2
Enemies outside function: 2این روش کار می کند، اما بهتر است تا جای ممکن متغیرهای سراسری را مستقیما از داخل توابع تغییر ندهیم.
روش بهتر برای تغییر مقدار
روش بهتر این است که مقدار را به تابع بدهیم و مقدار جدید را با return برگردانیم.
enemies = 1
def increase_enemies(current_enemies):
return current_enemies + 1
enemies = increase_enemies(enemies)
print(enemies)خروجی:
2در این روش مشخص است که تابع چه مقداری دریافت می کند و چه نتیجه ای برمی گرداند. به همین دلیل کد خواناتر و قابل کنترل تر می شود.
5. ثابت های سراسری
گاهی در برنامه مقادیری داریم که قرار نیست تغییر کنند.
برای مثال:
PI = 3.14159
#یا
MAX_ATTEMPTS = 5در پایتون چیزی مانع تغییر این متغیرها نمی شود، اما طبق یک قرارداد رایج، نام ثابت ها را با حروف بزرگ می نویسیم.
DEFAULT_SPEED = 100
MAX_PLAYERS = 4
TAX_RATE = 0.10این نوع نام گذاری به سایر برنامه نویسان نشان می دهد که این مقادیر قرار نیست در طول اجرای برنامه تغییر کنند.
6. پروژه بازی حدس عدد
در این پروژه کامپیوتر یک عدد تصادفی بین 1 تا 100 انتخاب می کند و کاربر باید آن را پیدا کند.
بازی دارای دو سطح است:
- سطح آسان با 10 فرصت
- سطح سخت با 5 فرصت
بعد از هر حدس نیز برنامه مشخص می کند که عدد وارد شده بزرگ تر یا کوچک تر از جواب صحیح است.
تولید عدد تصادفی
برای تولید عدد تصادفی از randint استفاده می کنیم.
from random import randintحالا می توانیم یک عدد بین 1 تا 100 تولید کنیم:
answer = randint(1, 100)هر بار که برنامه اجرا شود، مقدار متفاوتی ممکن است در answer قرار بگیرد.
تعریف تعداد فرصت ها
تعداد فرصت های هر سطح را به صورت ثابت تعریف می کنیم:
EASY_LEVEL_TURNS = 10
HARD_LEVEL_TURNS = 5استفاده از ثابت ها باعث می شود تنظیمات اصلی بازی در یک قسمت مشخص قرار بگیرند.
ساخت تابع بررسی پاسخ
حالا تابعی می سازیم که حدس کاربر را با عدد اصلی مقایسه کند.
def check_answer(guess, answer, turns):
if guess > answer:
print("Too high.")
return turns - 1
elif guess < answer:
print("Too low.")
return turns - 1
else:
print(f"You got it! The answer was {answer}.")
return turnsاین تابع سه ورودی دریافت می کند:
guessحدس کاربرanswerعدد صحیحturnsتعداد فرصت های باقی مانده
اگر حدس اشتباه باشد، یک فرصت کم می شود و مقدار جدید برگردانده می شود.
ساخت تابع انتخاب سطح بازی
حالا از کاربر می خواهیم سطح سختی را انتخاب کند.
def set_difficulty():
level = input("Choose a difficulty. Type 'easy' or 'hard': ").lower()
if level == "easy":
return EASY_LEVEL_TURNS
else:
return HARD_LEVEL_TURNSاگر کاربر easy را انتخاب کند 10 فرصت دارد و در غیر این صورت 5 فرصت دریافت می کند.
ساخت تابع اصلی بازی
بدنه اصلی بازی را داخل تابع game قرار می دهیم.
def game():
print("Welcome to the number guessing game!")
print("I'm thinking of a number between 1 and 100.")
answer = randint(1, 100)
turns = set_difficulty()ابتدا پیام شروع بازی نمایش داده می شود، سپس یک عدد تصادفی ساخته می شود و بعد تعداد فرصت ها بر اساس سطح انتخابی کاربر مشخص می شود.
دریافت حدس در حلقه
تا زمانی که کاربر عدد درست را پیدا نکرده باشد، باید بازی ادامه پیدا کند.
guess = 0
while guess != answer:
print(f"You have {turns} attempts remaining.")
guess = int(input("Make a guess: "))مقدار اولیه guess را صفر قرار می دهیم. چون عدد اصلی بین 1 تا 100 است، در شروع بازی مقدار guess با جواب برابر نخواهد بود و حلقه اجرا می شود.
کم کردن فرصت ها
بعد از دریافت حدس، تابع check_answer را اجرا می کنیم:
turns = check_answer(guess, answer, turns)اگر کاربر اشتباه حدس زده باشد، تابع یک فرصت از او کم می کند.
بررسی پایان بازی
اگر فرصت ها تمام شوند، باید بازی متوقف شود.
if turns == 0:
print(f"You've run out of guesses. The answer was {answer}.")
returnreturn اجرای تابع game را متوقف می کند.
اگر هنوز فرصت باقی مانده باشد اما جواب اشتباه باشد:
elif guess != answer:
print("Guess again.")کاربر دوباره وارد حلقه می شود و حدس بعدی را وارد می کند.
کد کامل بازی حدس عدد
from random import randint
EASY_LEVEL_TURNS = 10
HARD_LEVEL_TURNS = 5
def check_answer(guess, answer, turns):
if guess > answer:
print("Too high.")
return turns - 1
elif guess < answer:
print("Too low.")
return turns - 1
else:
print(f"You got it! The answer was {answer}.")
return turns
def set_difficulty():
level = input("Choose a difficulty. Type 'easy' or 'hard': ").lower()
if level == "easy":
return EASY_LEVEL_TURNS
else:
return HARD_LEVEL_TURNS
def game():
print("Welcome to the number guessing game!")
print("I'm thinking of a number between 1 and 100.")
answer = randint(1, 100)
turns = set_difficulty()
guess = 0
while guess != answer:
print(f"You have {turns} attempts remaining.")
guess = int(input("Make a guess: "))
turns = check_answer(guess, answer, turns)
if turns == 0:
print(f"You've run out of guesses. The answer was {answer}.")
return
elif guess != answer:
print("Guess again.")
game()در این پروژه از توابع، شرط ها، حلقه while، مقدار بازگشتی، تولید عدد تصادفی، ثابت های سراسری و محدوده متغیرها در کنار یکدیگر استفاده کردیم.
7. دیباگ کردن در پایتون
دیباگ کردن یعنی پیدا کردن و اصلاح مشکلات برنامه. بعضی خطاها باعث توقف کامل برنامه می شوند و بعضی دیگر بدون نمایش پیام خطا، نتیجه اشتباهی تولید می کنند.
یکی از بهترین روش ها برای یادگیری دیباگ این است که چند خطا را همزمان در یک برنامه بررسی کنیم.
مثال: پیدا کردن چند خطا در یک برنامه
فرض کنید می خواهیم برنامه ای بنویسیم که تعدادی نمره از یک لیست را بررسی کند، نمره های قبولی را دو برابر کند و داخل لیست جدید قرار دهد.
کد اولیه به شکل زیر نوشته شده است:
scores = ["12", "18", "7", "20"]
passed_scores = []
for index in range(1, 5):
score = scores[index]
if score >= 10:
new_score = score * 2
passed_scores.append(new_score)
print(passed_scores)این برنامه چند مشکل مختلف دارد.
اولین مشکل این است که اعضای لیست به صورت رشته ذخیره شده اند:
scores = ["12", "18", "7", "20"]در نتیجه نمی توانیم مستقیما مقدار score را با عدد 10 مقایسه کنیم.
مشکل دوم در محدوده range قرار دارد:
range(1, 5)اندیس های لیست چهار عضوی از صفر تا سه هستند. اگر حلقه به اندیس 4 برسد، برنامه با خطای خارج شدن از محدوده لیست مواجه می شود.
مشکل سوم مربوط به تورفتگی است. دستور:
passed_scores.append(new_score)خارج از حلقه قرار گرفته است و در نتیجه برای هر نمره اجرا نمی شود.
مشکل دیگر این است که اگر بخواهیم مقدار عددی را دو برابر کنیم، ابتدا باید رشته را به عدد تبدیل کنیم.
نسخه اصلاح شده برنامه:
scores = ["12", "18", "7", "20"]
passed_scores = []
for index in range(0, 4):
score = int(scores[index])
if score >= 10:
new_score = score * 2
passed_scores.append(new_score)
print(passed_scores)خروجی:
[24, 36, 40]این مثال چند نکته مهم دیباگ را به ما یاد می دهد:
- همیشه نوع داده متغیرها را بررسی کنید.
- به اندیس های لیست توجه داشته باشید.
- مقدار ابتدایی و انتهایی
rangeرا بررسی کنید. - تورفتگی خطوط را با دقت ببینید.
- بررسی کنید هر دستور دقیقا در کدام قسمت برنامه اجرا می شود.
استفاده از print برای پیدا کردن خطا
اگر برنامه اجرا می شود اما نتیجه مورد انتظار را نمی دهد، می توانیم مقادیر میانی را چاپ کنیم.
برای مثال:
scores = ["12", "18", "7", "20"]
passed_scores = []
for index in range(0, 4):
score = int(scores[index])
print(f"Current index: {index}")
print(f"Current score: {score}")
if score >= 10:
new_score = score * 2
print(f"New score: {new_score}")
passed_scores.append(new_score)
print(passed_scores)با این روش می توانیم مرحله به مرحله ببینیم برنامه چه مقادیری تولید می کند.
بررسی مقادیر مرزی
یکی دیگر از خطاهای رایج مربوط به شرایط مرزی است.
مثلا:
age = int(input("Enter your age: "))
if age > 18:
print("Adult")
elif age < 18:
print("Under 18")اگر کاربر عدد 18 را وارد کند، هیچ خروجی نمایش داده نمی شود.
نسخه درست:
age = int(input("Enter your age: "))
if age >= 18:
print("Adult")
else:
print("Under 18")هنگام دیباگ کردن شرط ها باید همیشه مقادیر مرزی مانند 0، 10، 18، 100 یا هر عددی که مرز بین دو شرط است را آزمایش کنیم.
استفاده از دیباگر
برای برنامه های کوچک استفاده از print بسیار مفید است، اما در برنامه های بزرگ تر بهتر است از Debugger استفاده کنیم.
برای مثال:
def calculate_total(numbers):
total = 0
for number in numbers:
total += number
return total
result = calculate_total([10, 20, 30, 40])
print(result)می توانیم روی این خط یک Breakpoint قرار دهیم:
total += numberسپس برنامه را مرحله به مرحله اجرا کنیم و مقدار number و total را در هر مرحله ببینیم. این روش زمانی بسیار مفید است که برنامه اجرا می شود اما نمی دانیم دقیقا در کدام مرحله مقدار اشتباهی تولید شده است.
8. پروژه بازی مقایسه محبوبیت
در پروژه دوم، دو فرد به بازیکن نمایش داده می شوند و کاربر باید حدس بزند کدام فرد دنبال کننده بیشتری دارد.
اطلاعات هر فرد را در قالب یک دیکشنری ذخیره می کنیم و همه دیکشنری ها را داخل یک لیست قرار می دهیم.
ساخت داده های بازی
game_data = [
{
"name": "Alex Morgan",
"follower_count": 180,
"description": "Athlete",
"country": "United States"
},
{
"name": "Lucas Martin",
"follower_count": 245,
"description": "Actor",
"country": "France"
},
{
"name": "Emma Lee",
"follower_count": 310,
"description": "Singer",
"country": "Canada"
},
{
"name": "Daniel Costa",
"follower_count": 205,
"description": "Content creator",
"country": "Brazil"
},
{
"name": "Sofia Adams",
"follower_count": 275,
"description": "Model",
"country": "United Kingdom"
}
]آماده کردن اطلاعات برای نمایش
تعداد دنبال کننده ها نباید به بازیکن نمایش داده شود. بنابراین تابعی می سازیم که فقط اطلاعات مورد نیاز را برگرداند.
def format_data(person):
name = person["name"]
description = person["description"]
country = person["country"]
return f"{name}, a {description}, from {country}"بررسی پاسخ کاربر
def check_answer(guess, a_followers, b_followers):
if a_followers > b_followers:
return guess == "a"
else:
return guess == "b"اگر تعداد دنبال کننده های گزینه A بیشتر باشد، پاسخ کاربر باید a باشد. در غیر این صورت پاسخ صحیح b خواهد بود.
کد کامل بازی
import random
game_data = [
{
"name": "Alex Morgan",
"follower_count": 180,
"description": "Athlete",
"country": "United States"
},
{
"name": "Lucas Martin",
"follower_count": 245,
"description": "Actor",
"country": "France"
},
{
"name": "Emma Lee",
"follower_count": 310,
"description": "Singer",
"country": "Canada"
},
{
"name": "Daniel Costa",
"follower_count": 205,
"description": "Content creator",
"country": "Brazil"
},
{
"name": "Sofia Adams",
"follower_count": 275,
"description": "Model",
"country": "United Kingdom"
}
]
def format_data(person):
name = person["name"]
description = person["description"]
country = person["country"]
return f"{name}, a {description}, from {country}"
def check_answer(guess, a_followers, b_followers):
if a_followers > b_followers:
return guess == "a"
else:
return guess == "b"
score = 0
game_should_continue = True
person_b = random.choice(game_data)
while game_should_continue:
person_a = person_b
person_b = random.choice(game_data)
while person_a == person_b:
person_b = random.choice(game_data)
print()
print("Compare A:")
print(format_data(person_a))
print()
print("VS")
print()
print("Against B:")
print(format_data(person_b))
guess = input("Who has more followers? Type 'A' or 'B': ").lower()
a_followers = person_a["follower_count"]
b_followers = person_b["follower_count"]
is_correct = check_answer(
guess,
a_followers,
b_followers
)
if is_correct:
score += 1
print(f"You're right! Current score: {score}")
else:
game_should_continue = False
print(f"Sorry, that's wrong. Final score: {score}")یکی از نکات مهم بازی این است که گزینه B مرحله قبل در دور بعد به گزینه A تبدیل می شود:
person_a = person_bسپس یک فرد جدید برای گزینه B انتخاب می کنیم:
person_b = random.choice(game_data)برای اینکه هر دو گزینه یک فرد نباشند نیز این بررسی انجام می شود:
while person_a == person_b:
person_b = random.choice(game_data)به این ترتیب بازی تا زمانی که کاربر پاسخ صحیح بدهد ادامه پیدا می کند.
جمعبندی بخش چهارم
در این بخش با محدوده محلی و سراسری متغیرها، فضای نام و نبود محدوده بلوکی در پایتون آشنا شدیم. همچنین یاد گرفتیم که بهتر است تا جای ممکن به جای تغییر مستقیم متغیرهای سراسری، مقدار جدید را از تابع برگردانیم.
بعد از آن بازی حدس عدد را مرحله به مرحله ساختیم و دیدیم چگونه می توان توابع، حلقه ها، شرط ها، ثابت ها و تولید اعداد تصادفی را در یک پروژه واقعی کنار هم قرار داد.
در بخش دیباگ نیز یاد گرفتیم که بررسی نوع داده، اندیس لیست، محدوده range، تورفتگی، شرایط مرزی و چاپ مقادیر میانی می تواند به پیدا کردن بسیاری از خطاهای برنامه کمک کند.
9. چند تمرین برای این بخش:
تمرین 1: تابعی بنویسید که قیمت یک محصول را دریافت کند و 15 درصد تخفیف را از آن کم کند. قیمت نهایی باید با return برگردانده شود
مشاهده پاسخ تمرین 1
def calculate_discount(price):
discount = price * 0.15
final_price = price - discount
return final_price
result = calculate_discount(800)
print(result)تمرین 2: متغیری به نام score با مقدار 20 بسازید. سپس تابعی بنویسید که 10 امتیاز به مقدار فعلی اضافه کند، بدون اینکه از global استفاده شود.
مشاهده پاسخ تمرین 2
score = 20
def increase_score(current_score):
return current_score + 10
score = increase_score(score)
print(score)تمرین 3: کد زیر قرار است تمام اعداد لیست را سه برابر کند، اما نتیجه درستی تولید نمی کند. مشکل را پیدا و اصلاح کنید.
def triple_numbers(numbers):
result = []
for number in numbers:
new_number = number * 3
result.append(new_number)
return resultمشاهده پاسخ تمرین 3
def triple_numbers(numbers):
result = []
for number in numbers:
new_number = number * 3
result.append(new_number)
return result
print(triple_numbers([1, 2, 3, 4]))تمرین 4: برنامه ای بنویسید که یک عدد از کاربر دریافت کند. اگر عدد کمتر از 10 بود Low، اگر بین 10 تا 20 بود Medium و اگر بیشتر از 20 بود High نمایش داده شود.
مشاهده پاسخ تمرین 4
number = int(input("Enter a number: "))
if number < 10:
print("Low")
elif number <= 20:
print("Medium")
else:
print("High")تمرین 5: یک عدد تصادفی بین 1 تا 20 انتخاب کنید. کاربر فقط 4 فرصت داشته باشد تا آن را حدس بزند. اگر حدس بزرگ تر از جواب بود Too high و اگر کوچک تر بود Too low نمایش داده شود. در صورت تمام شدن فرصت ها نیز جواب صحیح چاپ شود.
مشاهده پاسخ تمرین 5
from random import randint
MAX_ATTEMPTS = 4
def check_guess(guess, answer):
if guess > answer:
print("Too high.")
return False
elif guess < answer:
print("Too low.")
return False
else:
print("Correct!")
return True
answer = randint(1, 20)
attempts = MAX_ATTEMPTS
game_over = False
while attempts > 0 and not game_over:
print(f"You have {attempts} attempts remaining.")
guess = int(input("Guess a number between 1 and 20: "))
game_over = check_guess(guess, answer)
if not game_over:
attempts -= 1
if not game_over:
print(f"You lost. The correct answer was {answer}.")
آی آر سورس کد |