ویدیو مربوط به این مقاله: (کلیک کن)

تصور کن هر برنامه ایی که روی کامپیوتر اجرا میکنی، مثل یه کارگر تو کارخونست. این کارگر برای اینکه کارش رو انجام بده، با سه تا جریان یا لوله اصلی کار میکنه.
لوله ورودی (stdin): مواد اولیه از این لوله وارد میشه.
لوله خروجی (stdout): محصولات سالم و نهایی از این لوله خارج میشن.
لوله خروجی خطا (stderr): محصولات خراب یا هشدارهای حین کار از این لوله خارج میشن.
به این سه تا لوله در دنیای برنامه نویسی میگن Standard Streams یا جریان های استاندارد.
stdin یا ورودی استاندارد چیه؟ stdin مخفف Standard Inputعه و کانال پیش فرضیه که برنامه از طریق اون اطلاعات رو از کاربر یا یک منبع دیگه دریافت میکنه. ساده ترین مثالش وقتیه که برنامه ازت یه چیزی میپرسه و تو با کیبوردت تایپ میکنی. کیبورد در این حالت، منبع اصلی stdinعه. مثال در پایتون:
print("Enter Your Name:") name = input() print(f"Hello {name}")
برنامه اجرا میشه و به خط ()name = input میرسه. برنامه متوقف میشه و به سیستمعامل میگه که من منتظر اطلاعات از لوله ورودی یا همون stdin هستم. سیستم عامل که میدونه stdin در این حالت به کیبورد وصله، منتظر میمونه تا تو تایپ کنی و کلید Enter رو بزنی. وقتی Enter رو میزنی، هرچی تایپ کردی از طریق لوله stdin به برنامه فرستاده میشه و داخل متغیر name ذخیره میشه.
stdout یا خروجی استاندارد چیه؟ stdout مخفف Standard Outputعه و کانال پیش فرضیه که برنامه از طریق اون اطلاعات و نتایج عادی رو به کاربر نمایش میده. تو حالت عادی، این خروجی روی صفحه مانیتور (در پنجره ترمینال یا کنسول) نشون داده میشه. مثال در پایتون:
print("This is a normal message")
دستور ()print در پایتون، به طور پیشفرض اطلاعات رو به stdout میفرسته.
وقتی برنامه به دستور print میرسه، به سیستم عامل میگه لطفا string مربوطه رو به لوله خروجی (stdout) بفرست.
سیستمعامل که میدونه stdout به صفحه نمایش وصله، این متن رو در ترمینال نمایش میده. پس print فقط یک راه ساده و دوستانه در پایتون برای فرستادن اطلاعات به stdout هست.
stderr یا خروجی خطای استاندارد چیه؟ stderr مخفف Standard Errorعه و یک کانال خروجی جداگونست که فقط برای نمایش پیغام های خطا و هشدارها استفاده میشه. این کانال هم، مثل stdout به طور پیش فرض به صفحه نمایش وصله.
چرا دو تا لوله خروجی جدا داریم؟ مگه نمیشه خطا ها رو هم با همون stdout نشون داد؟
جدا بودن این دو لوله فوق العاده مهمه. تصور کن برنامه ایی نوشتی که کلی محاسبه انجام میده و نتایج رو چاپ میکنه. اگه وسط کار یه خطا پیش بیاد، تو دوست نداری پیغام خطا با نتایج اصلی قاطی بشه. مثال در پایتون:
برای فرستادن دستی اطلاعات به stderr در پایتون، میتونیم این کار رو بکنیم:
import sys # این پیام به stdout میره print("start") # فرض کنیم یه مشکلی پیش اومده # این پیام به stderr میره تا با خروجی های عادی قاطی نشه print("Error : file not find", file=sys.stderr) # این پیام هم به stdout میره print("end")
وقتی این کد رو اجرا کنی، هر سه تا پیام رو روی صفحه میبینی و شاید فکر کنی فرقی ندارن. اما در پشت صحنه، پیام خطا از لوله stderr اومده بیرون، در حالی که دو پیام دیگه از لوله stdout خارج شدن.
چطوری مقادیر مختلف رو جداسازی کنم؟
مثلا میخوام بگم خروجی stdout رو داخل ترمینال نمایش بده و خروجی stderr رو بریز داخل فایل.
به کد زیر دقت کن. (این کدها داخل یه فایل به اسم test.py قرار دارن)
import sys # این پیام به stdout میره print("start") # فرض کنیم یک مشکلی پیش اومده # این پیام به stderr میره تا با خروجیهای عادی قاطی نشه print("Error : file not find", file=sys.stderr) # این پیام هم به stdout میره print("end")
اگه کد رو بصورت معمولی اجرا کنم، خروجی زیر بهم برمیگرده:
start Error : file not find end
که خط اول و خط آخر از لوله stdout اومدن ولی خط وسط از لوله stderr.
حالا توی cmd این دستور رو وارد میکنم:
python test.py > rs.txt یا python test.py 1> rs.txt
با اجرای دستور، خروجی ترمینال برابر میشه با: Error : file not find
دو متن دیگه که از لوله stdout اومده بودن نمایش داده نمیشه. چرا؟ علامت < به سیستم عامل میگه خروجی stdout رو به جای نمایش دادن، داخل فایلی که مشخص کردم (rs.txt) قرار بده.
درنهایت فایل rs.txt ساخته میشه و مقادیر مربوط به stdout داخلش قرار میگیره ولی پیغام خطا چون از stderr میاد و ربطی به stdout نداره داخل همون ترمینال نمایش داده میشه.
چطوری برعکس حالت بالا، stderr رو داخل فایل ذخیره کنم؟
python test.py 2> error_log.txt
شماره شناسه stderr عدد 2 هست و من با نوشتن <2 به سیستم عامل گفتم خروجی stderr رو داخل فایل error_log قرار بده. (شناسه stdout عدد 1 عه و شناسه stdin عدد 0)
با اجرای دستور، داخل ترمینال متن های زیر قرار میگیره و متن مربوط به خطا داخل فایلی که مشخص کردم ذخیره میشه.
start end
داخل پایتون یه کتابخونه وجود داره به اسم sys که مخفف systemعه و امکان تعامل با اجزای سطح پایین تر سیستم عامل رو فراهم میکنه. مثل:
دریافت آرگومان های خط فرمان.
خارج شدن از برنامه.
کار با streamهای ورودی و خروجی (یعنی همین stdin، stdout، stderr).
sys.stdin: برای خوندن از ورودی.
این دقیقا معادل استفاده از input هست، ولی کنترل بیشتری داری.
متد read(size): خوندن به اندازهی فلان قدر بایت.
متد ()readline: خوندن یک خط تا n\.
متد ()readlines: خوندن همه خط ها و تبدیل به لیست.
name = input("Enter your name:") # کد پایین معادل کد بالاست import sys sys.stdout.write("Enter your name:") sys.stdout.flush() name = sys.stdin.readline() print(f"Hello {name}")
چرا از flush استفاده کردم؟ اگه بافر خالی نشه، ممکنه پیامی که میخوام چاپ بشه تا زمانی که ورودی رو دریافت نکنه روی ترمینال print نشه.
چرا sys.stdin.readline این قابلیت رو نداره که متن هم به کاربر موقع دریافت ورودی نمایش بده؟ چون فقط مسئول خوندن دادست، نه نوشتن یا نمایش پیام.
درنتیجه، تابع input در پشت پرده از همون sys استفاده میکنه.
من میتونم کاری کنم که کاربر چندین خط رو بتونه وارد کنه. کد:
import sys sys.stdout.write("Enter your text: ") sys.stdout.flush() name = sys.stdin.readlines() print(f"Your text\n{name}")
با اجرای برنامه، پیغام Enter your text ظاهر شد و شروع کردم به نوشتن، خط هارو مینوشتم و پشت سر هم Enter میزدم تا برم سراغ خط بعدی، درآخر وقتی پیامم تموم شد، Ctrl + Z زدم و بعدش Enter زدم تا از حالت input خارج بشه و دستور آخر فراخانی شه. تو خروجی یه لیست برگردونده که هر عضوش یکی از خط هایی بود که وارد کرده بودم.
به این دستور دقت کن:
$ echo -e "line1\nline2" | python myscript.py
با این دستور، sys.stdin دیگه کیبورد رو به عنوان ورودی استاندارد نمیبینه، بلکه از خروجی دستور echo، ورودی خودش رو برمیداره.
sys.stdout: خروجی استاندارد.
متد write(string): یه رشته رو به سمت خروجی استاندارد ارسال میکنه.
متد flush: بافر رو خالی میکنه و بلافاصله نشون میده.
متد isatty: چک میکنه خروجی به ترمیناله یا نه.
print("Hello Amirhosein") # کد بالا معادل کد پایینه import sys sys.stdout.write("Hello Amirhosein\n")
تابع print بصورت پیشفرض بعد هربار نمایش، یک n\ قرار میده، اما sys.stdout اینکارو انجام نمیده که در نتیجه باید دستی n\ رو قرار بدم.
همینطور که متوجه شدید، متد flush مقدار داخل بافر رو خالی میکنه و هرچی بود رو نمایش میده. من میتونم به واسطه متد flush و قابلیت خالی کردن بافر، یه نوار پیشرفت بسازم. کد:
import sys from time import sleep for i in range(0, 100, 5): sys.stdout.write("#") sys.stdout.flush() sleep(0.1) print("\nFinish") # بدین شکل هم میشه from time import sleep for i in "#" * 20: print(i, end = "", flush = True) sleep(0.1)
sys.stderr: خروجی استاندارد خطا.
متد write(string): پیام خطا رو دریافت میکنه و نمایش میده.
متد flush: بافر رو خالی میکنه.
import sys try: x = int("Hello") except ValueError: sys.stderr.write("your value is not number") print("Err")
داخل ترمینال، دستور python test.py 2> rs.txt رو وارد میکنم که پیغام های مربوط به stderr رو داخل فایل بریزه.
دستور که اجرا میشه داخل فایل متن your value is not number قرار میگیره اما تو ترمینال، Err رو نمایش میده. یعنی print از طریق stdout مقدار رو فرستاده، اما sys.stderr از طریق stderr مقدار رو فرستاده.
چطوری میتونم برای تابع print تعریف کنم که از کدوم جریان یا لوله مقدار هارو ارسال کنه؟ با این روش:
import sys try: x = int("Hello") except ValueError: sys.stderr.write("your value is not number\n") print("Err", file = sys.stderr)
تو ترمینال دستور قبلی رو اجرا میکنم و هیچ چیزی روی صفحه چاپ نمیشه، اما اگه وارد فایل بشم، دوتا خط اونجا نوشته شده.