مقدمهای بر انواع نوع در پایتون¶
پایتون از "نوعنما"های اختیاری (که بهشون "type hints" یا "type annotations" هم میگن) پشتیبانی میکنه.
این "نوعنماها" یا annotationها یه سینتکس خاص هستن که بهت اجازه میدن نوع یه متغیر رو مشخص کنی.
با مشخص کردن نوع متغیرها، ویرایشگرها و ابزارها میتونن پشتیبانی بهتری بهت بدن.
این فقط یه آموزش سریع / یادآوری در مورد نوعنماهای پایتونه. فقط حداقل چیزایی که برای استفاده ازشون با FastAPI لازمه رو پوشش میده... که در واقع خیلی کمه.
FastAPI کاملاً بر پایه این نوعنماهاست و این بهش کلی مزیت و فایده میده.
ولی حتی اگه هیچوقت از FastAPI استفاده نکنی، بازم یادگیری یه کم در موردشون به نفعته.
Note
اگه حرفهای پایتونی و همهچیز رو در مورد نوعنماها میدونی، برو سراغ فصل بعدی.
انگیزه¶
بیاید با یه مثال ساده شروع کنیم:
def get_full_name(first_name, last_name):
full_name = first_name.title() + " " + last_name.title()
return full_name
print(get_full_name("john", "doe"))
وقتی این برنامه رو اجرا کنی، خروجی اینه:
John Doe
این تابع این کارا رو میکنه:
- یه
first_nameوlast_nameمیگیره. - حرف اول هر کدوم رو با
title()بزرگ میکنه. - ترکیبشون میکنه با یه فاصله وسطشون.
def get_full_name(first_name, last_name):
full_name = first_name.title() + " " + last_name.title()
return full_name
print(get_full_name("john", "doe"))
ویرایشش کن¶
این یه برنامه خیلی سادهست.
ولی حالا تصور کن داری از صفر مینویسیش.
یه جایی شروع کردی به تعریف تابع، پارامترهات آمادهست...
ولی بعد باید "اون متدی که حرف اول رو بزرگ میکنه" رو صدا کنی.
آیا اسمش upper بود؟ یا uppercase؟ شاید first_uppercase؟ یا capitalize؟
بعد، با دوست قدیمی برنامهنویسا، تکمیل خودکار ویرایشگر، امتحان میکنی.
پارامتر اول تابع، first_name رو تایپ میکنی، بعد یه نقطه (.) میذاری و Ctrl+Space رو میزنی تا تکمیل خودکار بیاد.
ولی متأسفانه، چیز مفیدی نمیگیری:

نوع اضافه کن¶
بیا فقط یه خط از نسخه قبلی رو تغییر بدیم.
دقیقاً این بخش، پارامترهای تابع رو، از:
first_name, last_name
به:
first_name: str, last_name: str
عوض میکنیم.
همینه.
اینا همون "نوعنماها" هستن:
def get_full_name(first_name: str, last_name: str):
full_name = first_name.title() + " " + last_name.title()
return full_name
print(get_full_name("john", "doe"))
این با تعریف مقدار پیشفرض فرق داره، مثل:
first_name="john", last_name="doe"
یه چیز متفاوته.
ما از دونقطه (:) استفاده میکنیم، نه علامت مساوی (=).
و اضافه کردن نوعنماها معمولاً چیزی که اتفاق میافته رو از چیزی که بدون اونا میافتاد تغییر نمیده.
ولی حالا، دوباره تصور کن وسط ساختن اون تابع هستی، ولی این بار با نوعنماها.
توی همون نقطه، سعی میکنی تکمیل خودکار رو با Ctrl+Space فعال کنی و اینو میبینی:

با این، میتونی اسکرول کنی، گزینهها رو ببینی، تا وقتی که اون چیزی که "به نظرت آشنا میاد" رو پیدا کنی:

انگیزه بیشتر¶
این تابع رو چک کن، الان نوعنما داره:
def get_name_with_age(name: str, age: int):
name_with_age = name + " is this old: " + age
return name_with_age
چون ویرایشگر نوع متغیرها رو میدونه، فقط تکمیل خودکار نمیگیری، بلکه چک خطاها هم داری:

حالا میدونی که باید درستش کنی، age رو با str(age) به یه رشته تبدیل کنی:
def get_name_with_age(name: str, age: int):
name_with_age = name + " is this old: " + str(age)
return name_with_age
تعریف نوعها¶
تازه اصلیترین جا برای تعریف نوعنماها رو دیدی. بهعنوان پارامترهای تابع.
این هم اصلیترین جاییه که با FastAPI ازشون استفاده میکنی.
نوعهای ساده¶
میتونی همه نوعهای استاندارد پایتون رو تعریف کنی، نه فقط str.
مثلاً میتونی از اینا استفاده کنی:
intfloatboolbytes
def get_items(item_a: str, item_b: int, item_c: float, item_d: bool, item_e: bytes):
return item_a, item_b, item_c, item_d, item_d, item_e
نوعهای عمومی با پارامترهای نوع¶
یه سری ساختار داده هستن که میتونن مقدارهای دیگه رو نگه دارن، مثل dict، list، set و tuple. و مقدارهای داخلیشون هم میتونن نوع خودشون رو داشته باشن.
به این نوعها که نوعهای داخلی دارن میگن "عمومی" یا "generic". و میشه اونا رو تعریف کرد، حتی با نوعهای داخلیشون.
برای تعریف این نوعها و نوعهای داخلیشون، میتونی از ماژول استاندارد پایتون typing استفاده کنی. این ماژول مخصوص پشتیبانی از نوعنماهاست.
نسخههای جدیدتر پایتون¶
سینتکس با استفاده از typing با همه نسخهها، از پایتون 3.6 تا جدیدترینها، از جمله پایتون 3.9، 3.10 و غیره سازگاره.
با پیشرفت پایتون، نسخههای جدیدتر پشتیبانی بهتری برای این نوعنماها دارن و توی خیلی موارد حتی لازم نیست ماژول typing رو وارد کنی و ازش برای تعریف نوعنماها استفاده کنی.
اگه بتونی برای پروژهات از یه نسخه جدیدتر پایتون استفاده کنی، میتونی از این سادگی اضافه بهره ببری.
توی همه مستندات، مثالهایی هستن که با هر نسخه پایتون سازگارن (وقتی تفاوتی هست).
مثلاً "Python 3.6+" یعنی با پایتون 3.6 یا بالاتر (مثل 3.7، 3.8، 3.9، 3.10 و غیره) سازگاره. و "Python 3.9+" یعنی با پایتون 3.9 یا بالاتر (مثل 3.10 و غیره) سازگاره.
اگه بتونی از جدیدترین نسخههای پایتون استفاده کنی، از مثالهای نسخه آخر استفاده کن، چون اونا بهترین و سادهترین سینتکس رو دارن، مثلاً "Python 3.10+".
لیست¶
مثلاً، بیایم یه متغیر تعریف کنیم که یه list از str باشه.
متغیر رو با همون سینتکس دونقطه (:) تعریف کن.
بهعنوان نوع، list رو بذار.
چون لیست یه نوعه که نوعهای داخلی داره، اونا رو توی کروشهها میذاری:
def process_items(items: list[str]):
for item in items:
print(item)
از typing، List رو (با L بزرگ) وارد کن:
from typing import List
def process_items(items: List[str]):
for item in items:
print(item)
متغیر رو با همون سینتکس دونقطه (:) تعریف کن.
بهعنوان نوع، List رو که از typing وارد کردی بذار.
چون لیست یه نوعه که نوعهای داخلی داره، اونا رو توی کروشهها میذاری:
from typing import List
def process_items(items: List[str]):
for item in items:
print(item)
Info
اون نوعهای داخلی توی کروشهها بهشون "پارامترهای نوع" میگن.
توی این مورد، str پارامتر نوعیه که به List (یا list توی پایتون 3.9 و بالاتر) پاس داده شده.
یعنی: "متغیر items یه list هست، و هر کدوم از آیتمهای این لیست یه str هستن".
Tip
اگه از پایتون 3.9 یا بالاتر استفاده میکنی، لازم نیست List رو از typing وارد کنی، میتونی همون نوع معمولی list رو به جاش استفاده کنی.
با این کار، ویرایشگرت حتی وقتی داری آیتمهای لیست رو پردازش میکنی بهت کمک میکنه:

بدون نوعها، رسیدن به این تقریباً غیرممکنه.
توجه کن که متغیر item یکی از عناصر توی لیست items هست.
و با این حال، ویرایشگر میدونه که یه str هست و براش پشتیبانی میده.
تاپل و ست¶
برای تعریف tupleها و setها هم همین کار رو میکنی:
def process_items(items_t: tuple[int, int, str], items_s: set[bytes]):
return items_t, items_s
from typing import Set, Tuple
def process_items(items_t: Tuple[int, int, str], items_s: Set[bytes]):
return items_t, items_s
یعنی:
- متغیر
items_tیهtupleبا 3 تا آیتمه، یهint، یهintدیگه، و یهstr. - متغیر
items_sیهsetهست، و هر کدوم از آیتمهاش از نوعbytesهستن.
دیکشنری¶
برای تعریف یه dict، 2 تا پارامتر نوع میدی، که با کاما از هم جدا شدن.
پارامتر نوع اول برای کلیدهای dict هست.
پارامتر نوع دوم برای مقدارهای dict هست:
def process_items(prices: dict[str, float]):
for item_name, item_price in prices.items():
print(item_name)
print(item_price)
from typing import Dict
def process_items(prices: Dict[str, float]):
for item_name, item_price in prices.items():
print(item_name)
print(item_price)
یعنی:
- متغیر
pricesیهdictهست:- کلیدهای این
dictاز نوعstrهستن (مثلاً اسم هر آیتم). - مقدارهای این
dictاز نوعfloatهستن (مثلاً قیمت هر آیتم).
- کلیدهای این
اتحادیه¶
میتونی تعریف کنی که یه متغیر میتونه هر کدوم از چند تا نوع باشه، مثلاً یه int یا یه str.
توی پایتون 3.6 و بالاتر (از جمله پایتون 3.10) میتونی از نوع Union توی typing استفاده کنی و نوعهای ممکن رو توی کروشهها بذاری.
توی پایتون 3.10 یه سینتکس جدید هم هست که میتونی نوعهای ممکن رو با یه خط عمودی (|) جدا کنی.
def process_item(item: int | str):
print(item)
from typing import Union
def process_item(item: Union[int, str]):
print(item)
توی هر دو حالت یعنی item میتونه یه int یا یه str باشه.
شاید None¶
میتونی تعریف کنی که یه مقدار میتونه یه نوع باشه، مثلاً str، ولی میتونه None هم باشه.
توی پایتون 3.6 و بالاتر (از جمله پایتون 3.10) میتونی با وارد کردن و استفاده از Optional از ماژول typing اینو تعریف کنی.
from typing import Optional
def say_hi(name: Optional[str] = None):
if name is not None:
print(f"Hey {name}!")
else:
print("Hello World")
استفاده از Optional[str] به جای فقط str به ویرایشگر کمک میکنه خطاهایی که ممکنه فکر کنی یه مقدار همیشه str هست رو پیدا کنه، در حالی که میتونه None هم باشه.
Optional[Something] در واقع میانبر برای Union[Something, None] هست، این دو تا معادلن.
یعنی توی پایتون 3.10، میتونی از Something | None استفاده کنی:
def say_hi(name: str | None = None):
if name is not None:
print(f"Hey {name}!")
else:
print("Hello World")
from typing import Optional
def say_hi(name: Optional[str] = None):
if name is not None:
print(f"Hey {name}!")
else:
print("Hello World")
from typing import Union
def say_hi(name: Union[str, None] = None):
if name is not None:
print(f"Hey {name}!")
else:
print("Hello World")
استفاده از Union یا Optional¶
اگه از نسخه پایتون زیر 3.10 استفاده میکنی، یه نکته از دید خیلی شخصی خودم:
- 🚨 از
Optional[SomeType]استفاده نکن - به جاش ✨ از
Union[SomeType, None]استفاده کن ✨.
هر دو معادلن و زیر پوسته یکیان، ولی من Union رو به Optional ترجیح میدم چون کلمه "اختیاری" انگار暗示 میکنه که مقدار اختیاریه، در حالی که در واقع یعنی "میتونه None باشه"، حتی اگه اختیاری نباشه و هنوز لازم باشه.
فکر میکنم Union[SomeType, None] واضحتر نشون میده چی معنی میده.
فقط بحث کلمات و اسمهاست. ولی این کلمات میتونن رو طرز فکر تو و تیمت نسبت به کد تأثیر بذارن.
بهعنوان مثال، این تابع رو ببین:
from typing import Optional
def say_hi(name: Optional[str]):
print(f"Hey {name}!")
🤓 Other versions and variants
def say_hi(name: str | None):
print(f"Hey {name}!")
پارامتر name بهعنوان Optional[str] تعریف شده، ولی اختیاری نیست، نمیتونی تابع رو بدون پارامتر صدا کنی:
say_hi() # اوه نه، این خطا میده! 😱
پارامتر name هنوز لازمه (نه اختیاری) چون مقدار پیشفرض نداره. با این حال، name مقدار None رو قبول میکنه:
say_hi(name=None) # این کار میکنه، None معتبره 🎉
خبر خوب اینه که وقتی رو پایتون 3.10 باشی، لازم نیست نگران این باشی، چون میتونی بهسادگی از | برای تعریف اتحادیه نوعها استفاده کنی:
def say_hi(name: str | None):
print(f"Hey {name}!")
🤓 Other versions and variants
from typing import Optional
def say_hi(name: Optional[str]):
print(f"Hey {name}!")
اون موقع دیگه لازم نیست نگران اسمهایی مثل Optional و Union باشی. 😎
نوعهای عمومی¶
این نوعهایی که پارامترهای نوع رو توی کروشهها میگیرن بهشون نوعهای عمومی یا Generics میگن، مثلاً:
میتونی از همون نوعهای داخلی بهعنوان نوعهای عمومی استفاده کنی (با کروشهها و نوعها داخلشون):
listtuplesetdict
و همونطور که توی پایتون 3.8 بود، از ماژول typing:
UnionOptional(همونطور که توی پایتون 3.8 بود)- ...و بقیه.
توی پایتون 3.10، بهعنوان جایگزین برای استفاده از نوعهای عمومی Union و Optional، میتونی از خط عمودی (|) برای تعریف اتحادیه نوعها استفاده کنی، که خیلی بهتر و سادهتره.
میتونی از همون نوعهای داخلی بهعنوان نوعهای عمومی استفاده کنی (با کروشهها و نوعها داخلشون):
listtuplesetdict
و همونطور که توی پایتون 3.8 بود، از ماژول typing:
UnionOptional- ...و بقیه.
ListTupleSetDictUnionOptional- ...و بقیه.
کلاسها بهعنوان نوع¶
میتونی یه کلاس رو هم بهعنوان نوع یه متغیر تعریف کنی.
فرض کن یه کلاس Person داری، با یه نام:
class Person:
def __init__(self, name: str):
self.name = name
def get_person_name(one_person: Person):
return one_person.name
بعد میتونی یه متغیر رو از نوع Person تعریف کنی:
class Person:
def __init__(self, name: str):
self.name = name
def get_person_name(one_person: Person):
return one_person.name
و بعد، دوباره، همه پشتیبانی ویرایشگر رو داری:

توجه کن که این یعنی "one_person یه نمونه از کلاس Person هست".
یعنی "one_person خود کلاس به اسم Person نیست".
مدلهای Pydantic¶
Pydantic یه کتابخونه پایتونه برای اعتبارسنجی دادهها.
"شکل" دادهها رو بهعنوان کلاسهایی با ویژگیها تعریف میکنی.
و هر ویژگی یه نوع داره.
بعد یه نمونه از اون کلاس رو با یه سری مقدار میسازی و اون مقدارها رو اعتبارسنجی میکنه، به نوع مناسب تبدیلشون میکنه (اگه لازم باشه) و یه شیء با همه دادهها بهت میده.
و با اون شیء نهایی همه پشتیبانی ویرایشگر رو میگیری.
یه مثال از مستندات رسمی Pydantic:
from datetime import datetime
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str = "John Doe"
signup_ts: datetime | None = None
friends: list[int] = []
external_data = {
"id": "123",
"signup_ts": "2017-06-01 12:22",
"friends": [1, "2", b"3"],
}
user = User(**external_data)
print(user)
# > User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3]
print(user.id)
# > 123
from datetime import datetime
from typing import Union
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str = "John Doe"
signup_ts: Union[datetime, None] = None
friends: list[int] = []
external_data = {
"id": "123",
"signup_ts": "2017-06-01 12:22",
"friends": [1, "2", b"3"],
}
user = User(**external_data)
print(user)
# > User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3]
print(user.id)
# > 123
from datetime import datetime
from typing import List, Union
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str = "John Doe"
signup_ts: Union[datetime, None] = None
friends: List[int] = []
external_data = {
"id": "123",
"signup_ts": "2017-06-01 12:22",
"friends": [1, "2", b"3"],
}
user = User(**external_data)
print(user)
# > User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3]
print(user.id)
# > 123
Info
برای اطلاعات بیشتر در مورد Pydantic، مستنداتش رو چک کن.
FastAPI کاملاً بر پایه Pydantic هست.
توی آموزش - راهنمای کاربر خیلی بیشتر از اینا رو توی عمل میبینی.
Tip
Pydantic یه رفتار خاص داره وقتی از Optional یا Union[Something, None] بدون مقدار پیشفرض استفاده میکنی، میتونی توی مستندات Pydantic در مورد فیلدهای اختیاری لازم بیشتر بخونی.
نوعنماها با Annotationهای متادیتا¶
پایتون یه قابلیت هم داره که بهت اجازه میده متادیتا اضافی رو توی این نوعنماها بذاری با استفاده از Annotated.
توی پایتون 3.9، Annotated بخشی از کتابخونه استاندارده، پس میتونی از typing واردش کنی.
from typing import Annotated
def say_hello(name: Annotated[str, "this is just metadata"]) -> str:
return f"Hello {name}"
توی نسخههای زیر پایتون 3.9، Annotated رو از typing_extensions وارد میکنی.
با FastAPI از قبل نصب شده.
from typing_extensions import Annotated
def say_hello(name: Annotated[str, "this is just metadata"]) -> str:
return f"Hello {name}"
خود پایتون با این Annotated کاری نمیکنه. و برای ویرایشگرها و ابزارهای دیگه، نوع هنوز str هست.
ولی میتونی از این فضا توی Annotated استفاده کنی تا به FastAPI متادیتای اضافی در مورد اینکه چطور میخوای برنامهات رفتار کنه بدی.
نکته مهم اینه که اولین پارامتر نوع که به Annotated میدی، نوع واقعی هست. بقیش فقط متادیتا برای ابزارهای دیگهست.
الان فقط باید بدونی که Annotated وجود داره، و اینکه پایتون استاندارده. 😎
بعداً میبینی که چقدر قوی میتونه باشه.
Tip
اینکه این پایتون استاندارده یعنی هنوز بهترین تجربه توسعهدهنده رو توی ویرایشگرت، با ابزارهایی که برای تحلیل و بازسازی کدت استفاده میکنی و غیره میگیری. ✨
و همینطور کدت با خیلی از ابزارها و کتابخونههای دیگه پایتون خیلی سازگار میمونه. 🚀
نوعنماها توی FastAPI¶
FastAPI از این نوعنماها استفاده میکنه تا چند تا کار بکنه.
با FastAPI پارامترها رو با نوعنماها تعریف میکنی و اینا رو میگیری:
- پشتیبانی ویرایشگر.
- چک نوعها.
...و FastAPI از همون تعریفها برای اینا استفاده میکنه:
- تعریف نیازها: از پارامترهای مسیر درخواست، پارامترهای کوئری، هدرها، بدنهها، وابستگیها و غیره.
- تبدیل داده: از درخواست به نوع مورد نیاز.
- اعتبارسنجی داده: که از هر درخواست میاد:
- تولید خطاهای خودکار که به کلاینت برمیگرده وقتی داده نامعتبره.
- مستندسازی API با استفاده از OpenAPI:
- که بعدش توسط رابطهای کاربری مستندات تعاملی خودکار استفاده میشه.
اینا شاید همهش انتزاعی به نظر بیاد. نگران نباش. همه اینا رو توی عمل توی آموزش - راهنمای کاربر میبینی.
نکته مهم اینه که با استفاده از نوعهای استاندارد پایتون، توی یه جا (به جای اضافه کردن کلاسهای بیشتر، دکوراتورها و غیره)، FastAPI کلی از کار رو برات انجام میده.
Info
اگه همه آموزش رو گذروندی و برگشتی که بیشتر در مورد نوعها ببینی، یه منبع خوب "تقلبنامه" از mypy هست.