Python حرفه‌ای: عملیات‌های I/O

Professional Python: I/O operations

23 اسفند 1399
درسنامه درس 22 از سری پایتون حرفه‌ای
Python حرفه ای: عملیات های I/O (قسمت 22)

عملیات I/O چیست؟

I/O مخفف input/output و به معنی ورودی/خروجی است. در برنامه نویسی اوقاتی پیش می آید که کدهای ما باید با فایل های دیگری صحبت کنند و اینطور نیست که فقط در یک فایل ساده باشیم. به طور مثال شاید کدهای ما باید یک صفحه وب را دانلود کنند، شاید کدهای ما باید یک فایل روی هارد دیسک را بخوانند، شاید در حال صحبت با یک سیستم دیگر و یک پایگاه داده دیگر باشیم و الی آخر. یکی از رایج ترین مثال های موجود برای I/O خواندن یا ویرایش فایل ها است.

خواندن اطلاعات یک فایل در پایتون

زبان پایتون تابع پیش فرضی دارد که به ما اجازه می دهد فایل ها را باز کرده و بخوانیم. نام این تابع open است. من برای انجام این کار یک فایل ساده متنی به نام roxo.txt را ایجاد می کنم و درون آن جمله Learn Development at Roxo.ir/plus را قرار می دهم. در مرحله بعدی آن را بدین شکل باز می کنم:

my_file = open('roxo.txt')

print(my_file)

همانطور که می بینید من در نهایت متغیر my_file را print کرده ام. به نظر شما با این کار چه اتفاقی می افتد؟ من با اجرای این کدها نتیجه زیر را می گیرم:

<_io.TextIOWrapper name='roxo.txt' mode='r' encoding='UTF-8'>

یک شیء به نام TextIOWrapper دریافت کرده ایم، یک name دریافت کرده ایم که نام فایل ما است، یک mode دریافت کرده ایم که در ادامه آن را توضیح خواهم داد و در نهایت encoding فایل را نیز دریافت کرده ایم که UTF-8 می باشد. دیگر خصوصیت های ذکر شده کاملا واضح هستند و نیازی به توضیح ندارند بنابراین قبل از ادامه بحث باید خصوصیت mode را بررسی کنیم. mode می تواند یکی از حالت های زیر را داشته باشد:

  • حالت r: این حالت همان حالت پیش فرض بوده و مخففread-only است. زمانی که فایلی را در این حالت باز کنید، آن فایل فقط قابل خواندن خواهد بود و نمی توانید چیزی را درون آن بنویسید.
  • حالت rb: مخفف read binary است و محتوای فایل را در حالت read-only و در قالب باینری می خواند. استفاده های مختلفی برای این حالت وجود دارد اما معمولا برای کار با فیلم ها و تصاویر مورد استفاده قرار می گیرد.
  • حالت +r: فایل را در حالت read (خواندن محتویات فایل) و write (نوشتن محتویات جدید درون فایل) باز کرده و pointer یا نشانگر را در ابتدای فایل قرار می دهد.
  • حالت w: فایل را فقط در حالت write باز می کند بنابراین نمی توانید محتوای آن فایل را بخوانید اما می توانید داده های جدیدی را در آن بنویسید. در صورتی که نام فایل از قبل در آن مسیر وجود داشته باشد، overwrite می شود (فایل قبلی از بین می رود و فایل جدید جایگزین آن می شود) و اگر چنین فایلی وجود نداشته باشد، برایتان ساخته خواهد شد.
  • حالت wb: فایل را در حالت write اما به صورت باینری باز می کند.
  • حالت +w: فایل را در حالت write و read باز می کند.
  • حالت +wb: فایل را در حالت write و read اما به صورت باینری باز می کند.
  • حالت a: مخفف append (ضمیمه) است؛ در این حالت pointer در انتهای فایل قرار می گیرد بنابراین اطلاعات جدید به اطلاعات قبلی ضمیمه می شود در حالی که حالت های پیشین باعث حذف اطلاعات قبلی می شوند.
  • حالت ab: فایل را در حالت append اما به صورت باینری باز می کند.
  • حالت +a: فایل را در حالت read (خواندن اطلاعات) و append (ضمیمه کردن اطلاعات) باز می کند. تفاوت append با write در این است که append اطلاعات قبلی فایل را حذف نمی کند.
  • حالت +ab: فایلی را برای read و append اما در حالت باینری باز می کند.

در واقع ساختار کامل تابع open به شکل زیر است:

open(filename, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)

در صورتی که filename (نام فایل) پاس داده شده وجود نداشته باشد خطای FileNotFoundError را دریافت می کنید. پایتون برای خواندن اطلاعات سه تابع read و readline و readlines را در اختیار ما می گذارد. بگذارید با ساده ترین حالت یعنی read آشنا شویم:

my_file = open('roxo.txt')

print(my_file.read())

با اجرای کد بالا نتیجه زیر را می گیریم:

Learn Development at Roxo.ir/plus

بنابراین به همین سادگی موفق به خواندن محتویات این فایل شده ایم. بیایید محتویات فایل roxo.txt را کمی ویرایش کنیم:

Learn Development at Roxo.ir/plus

This is the second line

and here's the third

حالا اگر دوباره متد read را صدا بزنیم (کد قبلی را اجرا کنیم) چه می شود؟

Learn Development at Roxo.ir/plus

This is the second line

and here's the third

هر سه خط را دریافت می کنیم. بنابراین متوجه می شویم که read کل محتویات فایل را یکجا برمی گرداند بنابراین در هنگام کار با فایل های بسیار بزرگ مناسب نیست چرا که مقدار زیادی ram اشغال می کند اما متد readline چطور؟

Learn Development at Roxo.ir/plus

همانطور که مشخص است readline فقط خط اول را برایمان برمی گرداند. البته می توانید تعداد کاراکترهای مورد نظرتان را به این متد پاس بدهید تا فقط کاراکترهای خاصی را مشاهده کنیم:

my_file = open('roxo.txt')

print(my_file.readline(20))

با اجرای کد بالا نتیجه زیر را می گیریم:

Learn Development at

یعنی فقط ۲۰ کاراکتر اول برایمان برگردانده شده است. در ضمن اگر این متد را پشت سر هم صدا بزنید می توانیم به خطوط بعدی برویم:

my_file = open('roxo.txt')

 

print(my_file.readline())

print(my_file.readline())

print(my_file.readline())

print(my_file.readline())

print(my_file.readline())

نتیجه:

Learn Development at Roxo.ir/plus

This is the second line

and here's the third

در مرحله بعدی متد readlines را داریم:

my_file = open('roxo.txt')

print(my_file.readlines())

با اجرای این کد نتیجه زیر را دریافت می کنیم:

['Learn Development at Roxo.ir/plus\n', '\n', 'This is the second line\n', '\n', "and here's the third\n"]

به عبارت دیگر تمام خط ها به صورت اعضای یک لیست برایمان برگردانده شده اند!

در انتها زمانی که کارمان با فایل تمام شده است باید فایل را به صورت دستی ببندیم در غیر این صورت فایل در کدها باز خواهد ماند! به همین دلیل باید با استفاده از دستور close این کار را انجام بدهیم:

my_file = open('roxo.txt')

 

print(my_file.readlines())

 

my_file.close()

documentation رسمی پایتون هشدار داده است که در عملیات هایی مانند write (نوشتن در فایل) باید حتما فایل را close کنید، در غیر این صورت احتمال این وجود دارد که داده ها به صورت کامل در فایل نوشته نشوند.

روشی بهتر برای خواندن فایل ها

روشی که بالاتر یاد گرفتیم کمی آزار دهنده است چرا که در انتها باید فایل را به صورت دستی می بستیم تا با خطاهای عجیب یا رفتار های غیر منتظره روبرو نشویم اما روش بسیار بهتری برای باز کرن فایل ها و خواندن اطلاعاتشان وجود دارد؛ دستور with open. برای آشنایی با این دستور باید یک بار فایل خودمان را با آن باز کنیم:

with open('roxo.txt') as my_file:

print(my_file.readlines())

دستور with open آدرس و نام فایل را از شما می گیرد (roxo.txt) و سپس کلیدواژه as را آورده و یک نام دلخواه را به فایل خود می دهید. من در اینجا نام my_file را انتخاب کرده ام. دستور with open دقیقا مانند یک شرط if دارای فروفتگی است و بلوک مخصوص خودش را دارد بنابراین باید دستور print خود را با فرورفتگی بنویسید. با اجرای دستور بالا نتیجه زیر را می گیرید:

['This is a new line from python code!his is the second line\n', '\n', "and here's the third\n"]

بنابراین نتیجه نیز مانند قبل بوده و تفاوتی نمی کند. تا زمانی که درون این بلوک باشید می توانید با فایل خود کار کنید و زمانی که به انتهای آن برسید فایل به صورت خودکار بسته می شود و نیازی به بستن آن به صورت دستی نیست. معمولا زمانی که بحث از کار با فایل ها در پایتون می شود، این روش استاندارد ترین روش است و اکثر برنامه نویسان پایتون از آن استفاده می کنند. ما تا این قسمت فقط فایل ها را خوانده ایم، بیایید کمی در فایل ها بنویسیم. اگر از بخش mode ها یادتان باشد برای این کار باید از حالت +r استفاده کنیم تا قابلیت خواندن و نوشتن در فایل را داشته باشیم:

with open('roxo.txt', mode="r+") as my_file:

text = my_file.write("This is a new line from python code!")

print(text)

من در اینجا برای نوشتن در این فایل از متد write استفاده کرده ام و نتیجه اش را درون متغیری به نام text قرار داده ام. در نهایت نیز آن را چاپ کرده ام تا بدانیم متد write چه چیزی را برمی گرداند. با اجرای کد بالا عدد ۳۶ را دریافت می کنیم! آیا می توانید حدس بزنید این عدد چیست؟ این عدد تعداد کاراکتر هایی است که درون فایل نوشته شده است. اگر به رشته پاس داده شده به write در کد بالا توجه کنید، خواهید دید که تعداد کاراکتر هایش ۳۶ عدد می باشد. حالا اگر به فایل roxo.txt نگاه کنیم، جمله جدید اضافه شده را در آن پیدا خواهیم کرد:

This is a new line from python code!

This is the second line

and here's the third

آیا شما هم متوجه رفتار عجیب پایتون شدید؟ محتوای این فایل قبل از اجرای این دستور به شکل زیر بود:

Learn Development at Roxo.ir/plus

This is the second line

and here's the third

از همین نتیجه متوجه می شویم که پایتون cursor یا نشانگر خود را در ابتدای فایل قرار داده است. cursor یعنی نقطه ای که پایتون از آنجا شروع به نوشتن می کند و زمانی که آن را در ابتدای فایل قرار بدهد یعنی از ابتدای فایل شروع به نوشتن خواهد کرد. شاید با دیدن نتیجه بالا تصور کنید که متد write متن ما را جایگزین خط اول می کند اما اینطور نیست. برای اثبات این موضوع به شما راه حلی دارم! ابتدا به فایل roxo.txt بروید و محتوای آن را به طور کامل پاک کنید. در مرحله بعد متن زیر را در آن قرار داده و فایل را ذخیره کنید:

Learn Development

This is the second line

and here's the third

در مرحله بعد دوباره کد قبلی خود را اجرا کنید:

with open('roxo.txt', mode="r+") as my_file:

text = my_file.write("This is a new line from python code!")

print(text)

حالا پس از اجرای کد دوباره به فایل roxo.txt نگاهی می اندازیم:

This is a new line from python code!d line

and here's the third

آیا نتیجه برایتان تعجب آور بود؟ همانطور که می بینید خط اول به طور کامل حذف شده است، خط دوم تا حرف d (کلمه second) حذف شده است و سپس بقیه فایل را داریم! برای زبان پایتون خطوط جداگانه اهمیتی ندارد بلکه پایتون و دیگر زبان های برنامه نویسی همه چیز را در قالب کاراکتر ها می بینند. حتی رفتن به خط بعد یک کاراکتر است (n\) که به آن line break می گوییم. به زبان ساده تر از نظر پایتون محتوای فایل ما بدین شکل بوده است:

Learn Development\nThis is the second line\nand here's the third

بنابراین پایتون همه چیز را در یک خط می بیند و شروع به نوشتن در فایل می کند. ما ۳۶ کاراکتر را به آن پاس داده ایم بنابراین ۳۶ کاراکتر از ابتدای فایل ما حذف کرده و ۳۶ کاراکتر جدید را جایگزین آن می کند! در چنین موقعیتی سوالی مطرح می شود: چطور می توانیم داده های قبلی خود را حفظ کنیم؟ با استفاده از حالت append یا ضمیمه! ما در این مثال دیدیم که cursor در ابتدای فایل قرار می گرفت بنابراین از اتبدای فایل شروع به نوشتن می کرد. از طرفی من در بخش mode ها برایتان توضیح دادم که باز کردن فایل در حالت append باعث قرار گرفتن cursor در انتهای فایل می شود بنابراین به جای اینکه متون قبلی را حذف کنیم، به آن ها اضافه می کنیم. برای تست حالت append ابتدا به فایل roxo.txt رفته و باز هم متن آن را به شکل زیر تغییر بدهید:

Learn Development

This is the second line

and here's the third

حالا کدهای خود را به شکل زیر ویرایش می کنیم:

with open('roxo.txt', mode="a") as my_file:

text = my_file.write("This is a new line from python code!")

print(text)

توجه کنید که من mode را روی a (مخفف append) گذاشته ام. با اجرای کد بالا نتیجه زیر را دریافت می کنیم:

Learn Development

This is the second line

and here's the thirdThis is a new line from python code!

همانطور که می بینید هیچ قسمتی از متن قبلی ما حذف نشده است، بلکه رشته جدید ما به  انتهای متن اضافه شده است. چرا؟ به دلیل اینکه پایتون همه چیز را در به شکل یک خط می بیند. در صورتی که بخواهیم متن ما به خط جدیدی اضافه شود دو راه وجود دارد:

راه اول: باید کاراکتر line break را نیز به رشته خود پاس بدهیم:

with open('roxo.txt', mode="a") as my_file:

text = my_file.write("\nThis is a new line from python code!")

print(text)

کاراکتر های n\ و r\ برای مشخص کردن انتهای خط در سیستم عامل های مختلف (لینوکس، ویندوز، iOs و غیره) استفاده می شوند. از آنجایی که من با لینوکس کار می کنم، از یک n\ استفاده کرده ام. با اجرای این کد محتویات roxo.txt به شکل زیر نمایش داده خواهد شد:

Learn Development

This is the second line

and here's the third

This is a new line from python code!

در صورتی که می خواهیم نوشته ما با یک خط فاصله باشد باید دو بار n\ را قرار بدهیم:

with open('roxo.txt', mode="a") as my_file:

text = my_file.write("\n\nThis is a new line from python code!")

print(text)

متن roxo.txt را به حالت اول برگردانید و سپس دوباره کد بالا را اجرا کنید. توجه کنید که در این کد دو کاراکتر line break داریم. نتیجه:

Learn Development

This is the second line

and here's the third

This is a new line from python code!

راه دوم: به فایل roxo.txt رفته و یک یا دو خط خالی را در انتهای آن قرار بدهید:

Learn Development

This is the second line

and here's the third

در مرحله بعدی کد زیر را می نویسیم:

with open('roxo.txt', mode="a") as my_file:

text = my_file.write("This is a new line from python code!")

print(text)

با اجرای این کد محتوای فایل roxo.txt به شکل زیر تغییر پیدا می کند:

Learn Development

This is the second line

and here's the third

This is a new line from python code!

چرا؟ به دلیل اینکه من در انتهای فایل دو n\ داشتم بنابراین cursor در آخر فایل قرار می گیرد (پس از n\ ها) بنابراین در حالت عادی چیزی وجود ندارد که بخواهد overwrite شود.

لازم به ذکر است که در حالت append و write، اگر فایل پاس داده شده وجود نداشته باشد، یک فایل جدید برایمان ساخته می شود.

مسیردهی به فایل ها

در این قسمت می خواهم به صورت خلاصه در رابطه با مسیر دهی در زبان پایتون صحبت کنم. تا این قسمت از این آموزش ما همیشه فایل roxo.txt را در کنار فایل پایتون خودمان (test.py) داشته ایم اما اگر اینطور نبود چه؟

تصور کنید که فایل roxo.txt در یک پوشه دیگر به نام text files باشد. در این حالت چطور می توانیم درون کدهایمان به این فایل آدرس بدهیم؟ به کد زیر توجه کنید:

with open('roxo.txt', mode="r") as my_file:

text = my_file.read()

print(text)

به نظر شما این کد پاسخگو خواهد بود؟ من mode را روی r (خواندن) گذاشته ام تا اگر فایل را پیدا نکردیم حتما خطا دریافت کنیم. اگر mode روی a یا w باشد، خطایی نمی گیریم بلکه فایل جدیدی را خواهیم ساخت. همانطور که قبلا هم توضیح دادم برای اجرای کدهایتان چند راه وجود دارد. یا اینکه vscode را در پوشه مورد نظر باز کنید و سپس با کلیک روی run کد را اجرا نمایید یا اینکه ترمینال را در پوشه مورد نظر باز کرده و دستور زیر را اجرا نمایید:

python3 test.py

من از همین روش (روش دوم) برای اجرای این کدها استفاده می کنم:

Traceback (most recent call last):

File "test.py", line 1, in <module>

with open('roxo.txt', mode="r") as my_file:

FileNotFoundError: [Errno 2] No such file or directory: 'roxo.txt'

از آنجایی که فایل roxo.txt را به درون پوشه ای دیگر انتقال دادم خطا گرفته ایم. برای حل این مشکل باید آدرس دهی را به شکل زیر انجام بدهیم:

with open('./text files/roxo.txt', mode="r") as my_file:

text = my_file.read()

print(text)

به عبارتی نام پوشه را نیز ذکر کرده ام. در ضمن علامت /. در ابتدای آدرس به معنی آدرس دهی نسبی (relative path) می باشد. یعنی چه؟ یعنی آدرس دهی از همین پوشه شروع می شود؛ یعنی از همین پوشه به دنبال پوشه ای به نام text files بگرد که درون خود فایلی به نام roxo.txt داشته باشد. از آنجایی که به صورت پیش فرض از آدرس دهی نسبی استفاده می شود، نیازی به آوردن آن نداشتیم و در کدهای قبلی به جای roxo.txt/. از roxo.txt استفاده می کردیم بنابراین بدانید که هر دو حالت به آدرس دهی نسبی اشاره می کنند.

در مقابل آدرس نسبی (relative path) آدرس مطلق (absolute path) را داریم که در آن آدرس کامل فایل را ذکر می کنیم:

with open('/home/amir/Desktop/Roxo Academy/Python/text files/roxo.txt', mode="r") as my_file:

text = my_file.read()

print(text)

این آدرس کامل فایل در سیستم من است. من از لینوکس استفاده می کنم بنابراین آدرس کامل فایل به شکل بالا در آمده است اما اگر از ویندوز استفاده می کنید باید آدرس کامل خود را به آن بدهید. به طور مثال:

C:/user/amir/roxo/python/text files/roxo.txt

در هر دو حالت کد ما بدون مشکل اجرا خواهد شد.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری پایتون حرفه‌ای توصیه می‌کند:
نویسنده شوید

دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.