منابع ترجمه · The Python Tutorial · فصل ۷
ورودی و خروجی (آموزش رسمی پایتون، فصل ۷)
منبع اصلی: The Python Tutorial, Chapter 7: Input and Output — Python Software Foundation
💡 این صفحه مثل یک نوتبوک Jupyter است: متغیر و تابعی که در یک سلول تعریف شود، در سلولهای بعدی همین صفحه در دسترس است. سلولها را به ترتیب اجرا کنید، یا با «⏮ تا اینجا» همهٔ سلولهای قبلیِ اجرانشده را یکجا اجرا کنید.
راههای متعددی برای ارائهٔ خروجی یک برنامه وجود دارد؛ داده را میتوان به شکلی قابلخواندن برای انسان چاپ کرد، یا برای استفادهٔ آینده در یک فایل نوشت. این فصل برخی از امکانات را بررسی میکند.
۷٫۱. قالببندی پیشرفتهتر خروجی
تا اینجا با دو روش برای نوشتن مقادیر روبهرو شدهایم: دستورهای عبارت (expression statements) و تابع print(). (روش سوم استفاده از متد write() اشیای فایل است؛ فایل خروجی استاندارد را میتوان با sys.stdout ارجاع داد. برای اطلاعات بیشتر دربارهٔ این به مرجع کتابخانه نگاه کنید.)
اغلب میخواهید کنترل بیشتری بر قالببندی خروجی داشته باشید تا صرفاً چاپ مقادیر جداشده با فاصله. چند راه برای قالببندی خروجی وجود دارد.
-
برای استفاده از لیترالهای رشتهٔ قالببندیشده، یک رشته را با
fیاFپیش از گیومهٔ آغازین یا سهگیومهٔ آغازین شروع کنید. درون این رشته، میتوانید یک عبارت پایتون را بین نویسههای{و}بنویسید که میتواند به متغیرها یا مقادیر لیترال ارجاع دهد.>>> year = 2016 >>> event = 'Referendum' >>> f'Results of the {year} {event}' 'Results of the 2016 Referendum' -
متد
str.format()رشتهها تلاش دستی بیشتری لازم دارد. هنوز از{و}برای علامتگذاری جایی که یک متغیر جایگزین میشود استفاده میکنید و میتوانید دستورالعملهای دقیق قالببندی بدهید، اما باید اطلاعاتی را که قرار است قالببندی شود هم ارائه کنید. در بلوک کد زیر دو مثال از نحوهٔ قالببندی متغیرها هست:>>> yes_votes = 42_572_654 >>> total_votes = 85_705_149 >>> percentage = yes_votes / total_votes >>> '{:-9} YES votes {:2.2%}'.format(yes_votes, percentage) ' 42572654 YES votes 49.67%'توجه کنید که
yes_votesبا فاصله پر شده و علامت منفی فقط برای اعداد منفی میآید. مثال همچنینpercentageرا ضربشده در ۱۰۰، با ۲ رقم اعشار و به دنبال آن علامت درصد چاپ میکند (برای جزئیات مشخصات قالب را ببینید). -
در پایان، میتوانید با استفاده از عملیات برش و اتصال رشتهها، همهٔ کار مدیریت رشته را خودتان انجام دهید تا هر چیدمانی را که میتوانید تصور کنید بسازید. نوع رشته چند متد دارد که عملیات مفیدی برای پر کردن رشتهها تا یک عرض ستون مشخص انجام میدهند.
وقتی به خروجی پیچیده نیاز ندارید و فقط میخواهید برای اهداف اشکالزدایی نمایش سریعی از چند متغیر داشته باشید، میتوانید هر مقداری را با توابع repr() یا str() به رشته تبدیل کنید.
تابع str() قرار است نمایشهایی از مقادیر برگرداند که برای انسان نسبتاً خواناست، در حالی که repr() قرار است نمایشهایی تولید کند که مفسر میتواند بخواند (یا اگر نحو معادلی وجود نداشته باشد یک SyntaxError را اجبار میکند). برای اشیایی که نمایش خاصی برای مصرف انسان ندارند، str() همان مقدار repr() را برمیگرداند. بسیاری از مقادیر، مانند اعداد یا ساختارهایی مانند فهرستها و دیکشنریها، با هر دو تابع نمایش یکسانی دارند. رشتهها، بهطور خاص، دو نمایش متمایز دارند.
چند مثال:
>>> s = 'Hello, world.'
>>> str(s)
'Hello, world.'
>>> repr(s)
"'Hello, world.'"
>>> str(1/7)
'0.14285714285714285'
>>> x = 10 * 3.25
>>> y = 200 * 200
>>> s = 'The value of x is ' + repr(x) + ', and y is ' + repr(y) + '...'
>>> print(s)
The value of x is 32.5, and y is 40000...
>>> # The repr() of a string adds string quotes and backslashes:
>>> hello = 'hello, world\n'
>>> hellos = repr(hello)
>>> print(hellos)
'hello, world\n'
>>> # The argument to repr() may be any Python object:
>>> repr((x, y, ('spam', 'eggs')))
"(32.5, 40000, ('spam', 'eggs'))"
ماژول string از طریق string.Template پشتیبانی از یک رویکرد قالبسازی ساده مبتنی بر عبارتهای باقاعده (regular expressions) را دارد. این راه دیگری برای جایگزین کردن مقادیر در رشتهها فراهم میکند، با استفاده از جانگهدارهایی مانند $x و جایگزین کردن آنها با مقادیر از یک دیکشنری. این نحو استفادهاش آسان است، هرچند کنترل بسیار کمتری برای قالببندی ارائه میدهد.
۷٫۱٫۱. لیترالهای رشتهٔ قالببندیشده
لیترالهای رشتهٔ قالببندیشده (که به اختصار f-string هم نامیده میشوند) به شما اجازه میدهند مقدار عبارتهای پایتون را درون یک رشته بگنجانید، با پیشوند f یا F برای رشته و نوشتن عبارتها به شکل {expression}.
یک مشخصکنندهٔ قالب (format specifier) اختیاری میتواند بعد از عبارت بیاید. این کنترل بیشتری بر نحوهٔ قالببندی مقدار میدهد. مثال زیر عدد pi را تا سه رقم بعد از اعشار گرد میکند:
>>> import math
>>> print(f'The value of pi is approximately {math.pi:.3f}.')
The value of pi is approximately 3.142.
دادن یک عدد صحیح بعد از ':' باعث میشود آن فیلد حداقل آن تعداد نویسه عرض داشته باشد. این برای همتراز کردن ستونها مفید است.
>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 7678}
>>> for name, phone in table.items():
... print(f'{name:10} ==> {phone:10d}')
...
Sjoerd ==> 4127
Jack ==> 4098
Dcab ==> 7678
اصلاحکنندههای دیگری را میتوان برای تبدیل مقدار پیش از قالببندی آن به کار برد. '!a' تابع ascii() را اعمال میکند، '!s' تابع str() را و '!r' تابع repr() را:
>>> animals = 'eels'
>>> print(f'My hovercraft is full of {animals}.')
My hovercraft is full of eels.
>>> print(f'My hovercraft is full of {animals!r}.')
My hovercraft is full of 'eels'.
مشخصکنندهٔ = را میتوان برای گسترش یک عبارت به متن خود عبارت، یک علامت مساوی، سپس نمایش عبارت ارزیابیشده به کار برد:
>>> bugs = 'roaches'
>>> count = 13
>>> area = 'living room'
>>> print(f'Debugging {bugs=} {count=} {area=}')
Debugging bugs='roaches' count=13 area='living room'
برای اطلاعات بیشتر دربارهٔ مشخصکنندهٔ = به عبارتهای خودمستندساز نگاه کنید. برای مرجعی دربارهٔ این مشخصات قالب، راهنمای مرجع مینیزبان مشخصات قالب را ببینید.
۷٫۱٫۲. متد format() رشته
استفادهٔ پایهٔ متد str.format() اینگونه است:
>>> print('We are the {} who say "{}!"'.format('knights', 'Ni'))
We are the knights who say "Ni!"
کروشهها و نویسههای درون آنها (که فیلدهای قالب نامیده میشوند) با اشیایی که به متد str.format() داده میشوند جایگزین میشوند. عددی در آکولادها میتواند برای ارجاع به موقعیت شیء دادهشده به متد str.format() به کار رود.
>>> print('{0} and {1}'.format('spam', 'eggs'))
spam and eggs
>>> print('{1} and {0}'.format('spam', 'eggs'))
eggs and spam
اگر در متد str.format() از آرگومانهای کلیدواژهای استفاده شود، مقادیرشان با استفاده از نام آرگومان ارجاع داده میشود.
>>> print('This {food} is {adjective}.'.format(
... food='spam', adjective='absolutely horrible'))
This spam is absolutely horrible.
آرگومانهای موقعیتی و کلیدواژهای را میتوان بهطور دلخواه ترکیب کرد:
>>> print('The story of {0}, {1}, and {other}.'.format('Bill', 'Manfred',
... other='Georg'))
The story of Bill, Manfred, and Georg.
اگر یک رشتهٔ قالب واقعاً بلند دارید که نمیخواهید آن را بشکنید، خوب است اگر بتوانید بهجای موقعیت، با نام به متغیرهایی که قرار است قالببندی شوند ارجاع دهید. این را میتوان بهسادگی با ارسال dict و استفاده از کروشههای '[]' برای دسترسی به کلیدها انجام داد.
>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
>>> print('Jack: {0[Jack]:d}; Sjoerd: {0[Sjoerd]:d}; '
... 'Dcab: {0[Dcab]:d}'.format(table))
Jack: 4098; Sjoerd: 4127; Dcab: 8637678
این را میتوان با ارسال دیکشنری table بهعنوان آرگومانهای کلیدواژهای با نمادگذاری ** هم انجام داد.
>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
>>> print('Jack: {Jack:d}; Sjoerd: {Sjoerd:d}; Dcab: {Dcab:d}'.format(**table))
Jack: 4098; Sjoerd: 4127; Dcab: 8637678
این بهخصوص در ترکیب با تابع درونساخت vars() که دیکشنریای شامل همهٔ متغیرهای محلی برمیگرداند مفید است:
>>> table = {k: str(v) for k, v in vars().items()}
>>> message = " ".join([f'{k}: ' + '{' + k +'};' for k in table.keys()])
>>> print(message.format(**table))
__name__: __main__; __doc__: None; __package__: None; __loader__: ...
بهعنوان مثال، خطوط زیر مجموعهای از ستونهای مرتبشده تولید میکنند که اعداد صحیح و مربعها و مکعبهای آنها را نشان میدهد:
>>> for x in range(1, 11):
... print('{0:2d} {1:3d} {2:4d}'.format(x, x*x, x*x*x))
...
1 1 1
2 4 8
3 9 27
4 16 64
5 25 125
6 36 216
7 49 343
8 64 512
9 81 729
10 100 1000
برای مرور کامل قالببندی رشته با str.format()، نحو رشتهٔ قالب را ببینید.
۷٫۱٫۳. قالببندی دستی رشته
در اینجا همان جدول مربعها و مکعبها، بهصورت دستی قالببندی شده است:
>>> for x in range(1, 11):
... print(repr(x).rjust(2), repr(x*x).rjust(3), end=' ')
... # Note use of 'end' on previous line
... print(repr(x*x*x).rjust(4))
...
1 1 1
2 4 8
3 9 27
4 16 64
5 25 125
6 36 216
7 49 343
8 64 512
9 81 729
10 100 1000
(توجه کنید که یک فاصلهٔ بین هر ستون بهدلیل نحوهٔ کار print() اضافه شده است: همیشه بین آرگومانهایش فاصله میگذارد.)
متد str.rjust() اشیای رشته، یک رشته را در یک فیلد با عرض دادهشده با پر کردن آن با فاصله در سمت چپ، راستچین میکند. متدهای مشابهی str.ljust() و str.center() هم هست. این متدها چیزی نمینویسند، فقط یک رشتهٔ جدید برمیگردانند. اگر رشتهٔ ورودی خیلی بلند باشد، آن را کوتاه نمیکنند، بلکه بدون تغییر برمیگردانند؛ این چیدمان ستونهای شما را بههم میریزد، اما این معمولاً بهتر از جایگزین آن است که دروغ گفتن دربارهٔ یک مقدار بود. (اگر واقعاً کوتاهسازی میخواهید، همیشه میتوانید یک عملیات برش اضافه کنید، مانند x.ljust(n)[:n].)
متد دیگری هم هست، str.zfill()، که یک رشتهٔ عددی را از چپ با صفر پر میکند. علامتهای مثبت و منفی را میفهمد:
>>> '12'.zfill(5)
'00012'
>>> '-3.14'.zfill(7)
'-003.14'
>>> '3.14159265359'.zfill(5)
'3.14159265359'
۷٫۱٫۴. قالببندی قدیمی رشته
عملگر % (باقیمانده) را هم میتوان برای قالببندی رشته به کار برد. با داشتن format % values (که در آن format یک رشته است)، مشخصات تبدیل % در format با صفر یا چند عنصر از values جایگزین میشوند. این عملیات معمولاً با نام درونیابی رشته (string interpolation) شناخته میشود. برای مثال:
>>> import math
>>> print('The value of pi is approximately %5.3f.' % math.pi)
The value of pi is approximately 3.142.
اطلاعات بیشتر را میتوان در بخش قالببندی رشته به سبک printf یافت.
۷٫۲. خواندن و نوشتن فایلها
open() یک شیء فایل (file object) برمیگرداند و بیشتر با دو آرگومان موقعیتی به کار میرود: open(filename, mode)
>>> f = open('workfile', 'w')
آرگومان اول رشتهای است که نام فایل را در بر دارد. آرگومان دوم رشتهٔ دیگری است که چند نویسه را شامل میشود که نحوهٔ استفاده از فایل را توصیف میکنند. mode میتواند 'r' باشد وقتی فایل فقط خوانده خواهد شد، 'w' برای فقط نوشتن (فایل موجود با همین نام پاک میشود)، و 'a' فایل را برای افزودن (appending) باز میکند؛ هر دادهٔ نوشتهشده در فایل بهطور خودکار به انتها اضافه میشود. 'r+' فایل را هم برای خواندن و هم برای نوشتن باز میکند. آرگومان mode اختیاری است؛ اگر حذف شود 'r' فرض میشود.
بهطور معمول، فایلها در حالت متنی (text mode) باز میشوند، یعنی رشتهها را از فایل میخوانید و به آن مینویسید، که با یک کدگذاری (encoding) خاص رمزگذاری شدهاند. اگر encoding مشخص نشود، پیشفرض UTF-8 است (به open() نگاه کنید). افزودن 'b' به mode فایل را در حالت دودویی (binary mode) باز میکند. دادهٔ حالت دودویی بهصورت اشیای bytes خوانده و نوشته میشود. هنگام باز کردن فایل در حالت دودویی نمیتوانید encoding را مشخص کنید.
در حالت متنی، پیشفرض هنگام خواندن این است که پایانهای خط مخصوص پلتفرم (\n در یونیکس، \r\n در ویندوز) را فقط به \n تبدیل میکند. هنگام نوشتن در حالت متنی، پیشفرض این است که رخدادهای \n را به پایانهای خط مخصوص پلتفرم برمیگرداند. این تغییر پشتصحنه در دادهٔ فایل برای فایلهای متنی خوب است، اما دادهٔ دودویی مانند آنچه در فایلهای JPEG یا EXE هست را خراب میکند. هنگام خواندن و نوشتن چنین فایلهایی بسیار مراقب باشید که از حالت دودویی استفاده کنید.
هنگام کار با اشیای فایل، استفاده از کلیدواژهٔ with رویهٔ خوبی است. مزیت آن این است که فایل پس از پایان مجموعه دستورهایش بهدرستی بسته میشود، حتی اگر در نقطهای استثنایی پرتاب شود. استفاده از with همچنین بسیار کوتاهتر از نوشتن بلوکهای همارز try-finally است:
>>> with open('workfile') as f:
... read_data = f.read()
>>> # We can check that the file has been automatically closed.
>>> f.closed
True
اگر از کلیدواژهٔ with استفاده نمیکنید، باید f.close() را فراخوانی کنید تا فایل بسته شود و بیدرنگ هر منبع سیستمی که توسط آن استفاده میشود آزاد شود.
هشدار: فراخوانی
f.write()بدون استفاده از کلیدواژهٔwithیا فراخوانیf.close()ممکن است باعث شود آرگومانهایf.write()بهطور کامل روی دیسک نوشته نشوند، حتی اگر برنامه با موفقیت خارج شود.
پس از بسته شدن شیء فایل، چه با دستور with و چه با فراخوانی f.close()، تلاش برای استفاده از شیء فایل بهطور خودکار شکست میخورد.
>>> f.close()
>>> f.read()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: I/O operation on closed file.
۷٫۲٫۱. متدهای اشیای فایل
بقیهٔ مثالهای این بخش فرض میکنند که یک شیء فایل به نام f از قبل ساخته شده است.
برای خواندن محتوای یک فایل، f.read(size) را فراخوانی کنید که مقداری داده میخواند و آن را بهصورت یک رشته (در حالت متنی) یا شیء bytes (در حالت دودویی) برمیگرداند. size یک آرگومان عددی اختیاری است. وقتی size حذف شده یا منفی باشد، کل محتوای فایل خوانده و برگردانده میشود؛ اگر فایل دو برابر حافظهٔ ماشینتان باشد، مشکل خودتان است. در غیر این صورت، حداکثر size نویسه (در حالت متنی) یا size بایت (در حالت دودویی) خوانده و برگردانده میشود. اگر به پایان فایل رسیده باشد، f.read() یک رشتهٔ خالی ('') برمیگرداند.
>>> f.read()
'This is the entire file.\n'
>>> f.read()
''
f.readline() یک خط واحد را از فایل میخواند؛ یک نویسهٔ خط جدید (\n) در انتهای رشته باقی میماند و فقط در آخرین خط فایل در صورتی که فایل با خط جدید پایان نیابد حذف میشود. این مقدار بازگشتی را بدون ابهام میکند؛ اگر f.readline() یک رشتهٔ خالی برگرداند، به پایان فایل رسیدهایم، در حالی که یک خط خالی با '\n' نمایش داده میشود، رشتهای که فقط شامل یک خط جدید است.
>>> f.readline()
'This is the first line of the file.\n'
>>> f.readline()
'Second line of the file\n'
>>> f.readline()
''
برای خواندن خطوط از یک فایل، میتوانید روی شیء فایل حلقه بزنید. این از نظر حافظه کارآمد است، سریع است و به کد ساده منجر میشود:
>>> for line in f:
... print(line, end='')
...
This is the first line of the file.
Second line of the file
اگر میخواهید همهٔ خطوط یک فایل را در یک فهرست بخوانید، میتوانید از list(f) یا f.readlines() هم استفاده کنید.
f.write(string) محتوای string را در فایل مینویسد و تعداد نویسههای نوشتهشده را برمیگرداند.
>>> f.write('This is a test\n')
15
انواع دیگر اشیا باید پیش از نوشتن تبدیل شوند — یا به رشته (در حالت متنی) یا به شیء bytes (در حالت دودویی):
>>> value = ('the answer', 42)
>>> s = str(value) # convert the tuple to string
>>> f.write(s)
18
f.tell() یک عدد صحیح برمیگرداند که موقعیت جاری شیء فایل را در فایل نشان میدهد؛ در حالت دودویی بهصورت تعداد بایت از ابتدای فایل و در حالت متنی بهصورت یک عدد مبهم (opaque).
برای تغییر موقعیت شیء فایل، از f.seek(offset, whence) استفاده کنید. موقعیت با افزودن offset به یک نقطهٔ مرجع محاسبه میشود؛ نقطهٔ مرجع توسط آرگومان whence انتخاب میشود. مقدار whence برابر ۰ از ابتدای فایل اندازه میگیرد، ۱ از موقعیت جاری فایل استفاده میکند و ۲ از انتهای فایل بهعنوان نقطهٔ مرجع. whence را میتوان حذف کرد و پیشفرض آن ۰ است، که ابتدای فایل را بهعنوان نقطهٔ مرجع به کار میبرد.
>>> f = open('workfile', 'rb+')
>>> f.write(b'0123456789abcdef')
16
>>> f.seek(5) # Go to the 6th byte in the file
5
>>> f.read(1)
b'5'
>>> f.seek(-3, 2) # Go to the 3rd byte before the end
13
>>> f.read(1)
b'd'
در فایلهای متنی (آنهایی که بدون b در رشتهٔ mode باز شدهاند)، فقط seekهای نسبت به ابتدای فایل مجازند (استثنا seek به انتهای دقیق فایل با seek(0, 2) است) و تنها مقادیر معتبر offset آنهایی هستند که از f.tell() برگردانده شدهاند، یا صفر. هر مقدار offset دیگر رفتار تعریفنشده تولید میکند.
اشیای فایل چند متد اضافی دارند، مانند isatty() و truncate() که کمتر استفاده میشوند؛ برای راهنمای کامل اشیای فایل به مرجع کتابخانه مراجعه کنید.
۷٫۲٫۲. ذخیرهٔ دادهٔ ساختاریافته با json
رشتهها را میتوان بهآسانی در یک فایل نوشت و از آن خواند. اعداد کمی تلاش بیشتر میخواهند، چون متد read() فقط رشتهها را برمیگرداند، که باید به تابعی مانند int() داده شوند که رشتهای مانند '123' را میگیرد و مقدار عددی آن، ۱۲۳، را برمیگرداند. وقتی میخواهید انواع دادهٔ پیچیدهتری مانند فهرستها و دیکشنریهای تودرتو را ذخیره کنید، تجزیه و سریالسازی (serialization) دستی پیچیده میشود.
بهجای اینکه کاربران پیوسته کدی برای ذخیرهٔ انواع دادهٔ پیچیده در فایلها بنویسند و اشکالزدایی کنند، پایتون به شما اجازه میدهد از قالب تبادل دادهٔ محبوب JSON (JavaScript Object Notation) استفاده کنید. ماژول استاندارد json میتواند سلسلهمراتب دادههای پایتون را بگیرد و به نمایشهای رشتهای تبدیل کند؛ این فرایند سریالسازی (serializing) نامیده میشود. بازسازی داده از نمایش رشتهای ناسریالسازی (deserializing) نام دارد. بین سریالسازی و ناسریالسازی، رشتهای که شیء را نمایش میدهد ممکن است در یک فایل یا داده ذخیره شده باشد، یا از طریق یک اتصال شبکه به یک ماشین دوردست ارسال شده باشد.
توجه: قالب JSON معمولاً توسط برنامههای مدرن برای امکان تبادل داده به کار میرود. بسیاری از برنامهنویسان از قبل با آن آشنا هستند، که آن را برای قابلیت همکاری (interoperability) انتخاب خوبی میکند.
اگر شیئی به نام x دارید، میتوانید نمایش رشتهٔ JSON آن را با یک خط کد ساده ببینید:
>>> import json
>>> x = [1, 'simple', 'list']
>>> json.dumps(x)
'[1, "simple", "list"]'
گونهٔ دیگری از تابع dumps()، به نام dump()، شیء را بهسادگی در یک فایل متنی سریالسازی میکند. بنابراین اگر f یک شیء فایل متنی باز شده برای نوشتن باشد، میتوانیم این کار را انجام دهیم:
json.dump(x, f)
برای رمزگشایی دوبارهٔ شیء، اگر f یک شیء فایل دودویی یا فایل متنی باز شده برای خواندن باشد:
x = json.load(f)
توجه: فایلهای JSON باید با UTF-8 کدگذاری شوند، کدگذاری پیشفرض فایلهای متنی.
این تکنیک ساده سریالسازی میتواند فهرستها و دیکشنریها را مدیریت کند، اما سریالسازی نمونههای کلاس دلخواه در JSON کمی تلاش اضافی میطلبد. مرجع ماژول json توضیحی دربارهٔ این دارد.
همچنین ببینید
pickle— ماژول pickleبر خلاف JSON، pickle پروتکلی است که سریالسازی اشیای پایتون با پیچیدگی دلخواه را ممکن میکند. به همین دلیل، مختص پایتون است و نمیتوان از آن برای ارتباط با برنامههای نوشتهشده در زبانهای دیگر استفاده کرد. همچنین بهطور پیشفرض ناامن است: ناسریالسازی دادههای pickle که از یک منبع نامطمئن میآید میتواند کد دلخواه را اجرا کند، اگر داده توسط یک مهاجم ماهر ساخته شده باشد.