روشهای متعددی برای ارائهٔ خروجی یک برنامه وجود دارد؛ دادهها میتوانند بهصورت قابلخواندن برای انسان چاپ شوند، یا برای استفاده در آینده در یک فایل نوشته شوند. این فصل به بحث در مورد برخی از این امکانات میپردازد.
۷.۱. قالببندی خروجی پیشرفتهتر (Fancier Output Formatting)
تا کنون با دو روش برای نوشتن مقادیر مواجه شدهایم: دستورات عبارت (expression statements) و تابع print(). (روش سوم استفاده از متد write() اشیاء فایل است؛ فایل خروجی استاندارد را میتوان بهعنوان sys.stdout ارجاع داد. برای اطلاعات بیشتر در این مورد به Library Reference مراجعه کنید.)
اغلب میخواهید کنترل بیشتری بر قالببندی خروجی خود داشته باشید تا صرفاً چاپ مقادیر جدا شده با فاصله. روشهای متعددی برای قالببندی خروجی وجود دارد.
برای استفاده از formatted string literals، یک رشته را با f یا F قبل از علامت نقلقول باز یا سه نقلقول شروع کنید. در داخل این رشته، میتوانید یک عبارت پایتون را بین کاراکترهای { و } بنویسید که میتواند به متغیرها یا مقادیر لفظی (literal values) ارجاع دهد.
>>> 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 با فاصله پر میشود و علامت منفی فقط برای اعداد منفی است. مثال همچنین درصد را ضربدر ۱۰۰، با ۲ رقم اعشار و به دنبال آن یک علامت درصد چاپ میکند (برای جزئیات به Format specification mini-language مراجعه کنید).
در نهایت، میتوانید تمام پردازش رشته را خودتان با استفاده از عملیات برش (slicing) و الحاق (concatenation) رشته انجام دهید تا هر طرحبندی که میتوانید تصور کنید ایجاد کنید. نوع رشته (string type) دارای متدهایی است که عملیات مفیدی برای پر کردن رشتهها تا یک عرض ستون مشخص انجام میدهند.
وقتی به خروجی پیشرفته نیاز ندارید اما فقط میخواهید نمایش سریعی از برخی متغیرها برای اهداف اشکالزدایی (debugging) داشته باشید، میتوانید هر مقداری را با توابع 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 شامل پشتیبانی از یک رویکرد قالبی ساده بر اساس عبارات منظم (regular expressions) از طریق string.Template است. این روش دیگری را برای جایگزینی مقادیر در رشتهها ارائه میدهد، با استفاده از placeholders مانند $x و جایگزینی آنها با مقادیر از یک دیکشنری. این نحو (syntax) آسان برای استفاده است، اگرچه کنترل بسیار کمتری برای قالببندی ارائه میدهد.
۷.۱.۱. Formatted String Literals
Formatted string literals (که بهاختصار f-strings نیز نامیده میشوند) به شما امکان میدهند مقدار عبارات پایتون را در داخل یک رشته با پیشوند f یا F به رشته و نوشتن عبارات بهصورت {expression} قرار دهید.
یک مشخصکنندهٔ قالب اختیاری (optional 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'
برای اطلاعات بیشتر در مورد مشخصکنندهٔ = به self-documenting expressions مراجعه کنید. برای مرجعی در مورد این مشخصات قالببندی، به راهنمای مرجع برای Format specification mini-language مراجعه کنید.
۷.۱.۲. متد format() رشته
استفادهٔ اساسی از متد str.format() به این شکل است:
>>> print('We are the {} who say "{}!"'.format('knights', 'Ni'))
We are the knights who say "Ni!"
براکتها و کاراکترهای داخل آنها (که format fields نامیده میشوند) با اشیایی که به متد str.format() منتقل میشوند جایگزین میگردند. یک عدد در براکتها میتواند برای اشاره به موقعیت شیء ارسالشده به متد str.format() استفاده شود.
>>> print('{0} and {1}'.format('spam', 'eggs'))
spam and eggs
>>> print('{1} and {0}'.format('spam', 'eggs'))
eggs and spam
اگر از آرگومانهای کلیدواژهای (keyword arguments) در متد str.format() استفاده شود، مقادیر آنها با استفاده از نام آرگومان ارجاع داده میشوند.
>>> print('This {food} is {adjective}.'.format(food='spam', adjective='absolutely horrible'))
This spam is absolutely horrible.
آرگومانهای موقعیتی (positional) و کلیدواژهای را میتوان بهطور دلخواه ترکیب کرد:
>>> print('The story of {0}, {1}, and {other}.'.format('Bill', 'Manfred', other='Georg'))
The story of Bill, Manfred, and Georg.
اگر یک رشتهٔ قالببندی بسیار طولانی دارید که نمیخواهید آن را تقسیم کنید، خوب است اگر بتوانید متغیرهایی را که قرار است قالببندی شوند با نام به جای موقعیت ارجاع دهید. این کار را میتوان با عبور دادن دیکشنری و استفاده از براکتهای مربع '[]' برای دسترسی به کلیدها انجام داد.
>>> 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() که یک دیکشنری حاوی تمام متغیرهای محلی (local variables) را برمیگرداند، مفید است:
>>> 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()، به Format string syntax مراجعه کنید.
۷.۱.۳. قالببندی دستی رشته (Manual String Formatting)
در اینجا همان جدول مربعها و مکعبها، بهصورت دستی قالببندی شده است:
>>> 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() از اشیاء رشته، یک رشته را در یک فیلد با عرض معین با پر کردن آن با فاصله در سمت چپ، به سمت راست (right-justify) میکند. متدهای مشابه 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'
۷.۱.۴. قالببندی قدیمی رشته (Old string formatting)
عملگر % (modulo) همچنین میتواند برای قالببندی رشته استفاده شود. با توجه به 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-style String Formatting یافت.
۷.۲. خواندن و نوشتن فایلها
open() یک شیء فایل (file object) را برمیگرداند و معمولاً با دو آرگومان موقعیتی و یک آرگومان کلیدواژهای استفاده میشود: open(filename, mode, encoding=None)
>>> f = open('workfile', 'w', encoding="utf-8")
اولین آرگومان رشتهای حاوی نام فایل است. آرگومان دوم رشتهٔ دیگری است که شامل چند کاراکتر است که نحوهٔ استفاده از فایل را توصیف میکند. mode میتواند 'r' باشد زمانی که فایل فقط خوانده میشود، 'w' برای فقط نوشتن (یک فایل موجود با همان نام پاک میشود)، و 'a' فایل را برای افزودن (appending) باز میکند؛ هر دادهای که به فایل نوشته شود بهطور خودکار به انتها اضافه میشود. 'r+' فایل را برای خواندن و نوشتن باز میکند. آرگومان mode اختیاری است؛ اگر حذف شود، 'r' فرض میشود.
بهطور معمول، فایلها در حالت متنی (text mode) باز میشوند، یعنی شما رشتهها را از فایل میخوانید و به فایل مینویسید، که در یک رمزگذاری خاص (specific encoding) کدگذاری شدهاند. اگر encoding مشخص نشود، پیشفرض وابسته به پلتفرم است (به open() مراجعه کنید). از آنجا که UTF-8 استاندارد مدرن د-فکتو (de-facto) است، encoding="utf-8" توصیه میشود مگر اینکه بدانید که باید از رمزگذاری دیگری استفاده کنید. افزودن 'b' به mode فایل را در حالت باینری (binary mode) باز میکند. دادههای حالت باینری بهعنوان اشیاء bytes خوانده و نوشته میشوند. هنگام باز کردن فایل در حالت باینری نمیتوانید encoding را مشخص کنید.
در حالت متنی، پیشفرض هنگام خواندن، تبدیل انتهای خطوط وابسته به پلتفرم (\n در یونیکس، \r\n در ویندوز) به صرفاً \n است. هنگام نوشتن در حالت متنی، پیشفرض تبدیل موارد \n به انتهای خطوط وابسته به پلتفرم است. این تغییر پشتصحنه در دادههای فایل برای فایلهای متنی خوب است، اما دادههای باینری مانند فایلهای JPEG یا EXE را خراب میکند. در هنگام خواندن و نوشتن چنین فایلهایی بسیار مراقب باشید که از حالت باینری استفاده کنید.
روش خوب این است که هنگام کار با اشیاء فایل از کلیدواژهٔ with استفاده کنید. مزیت این است که فایل به درستی پس از پایان بلوک (suite) آن بسته میشود، حتی اگر در برخی نقاط استثنا (exception) ایجاد شود. استفاده از with همچنین بسیار کوتاهتر از نوشتن بلوکهای try-finally معادل است:
>>> with open('workfile', encoding="utf-8") 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() یک خط واحد از فایل میخواند؛ یک کاراکتر newline (\n) در انتهای رشته باقی میماند، و فقط در آخرین خط فایل اگر فایل با newline به پایان نرسد، حذف میشود. این باعث میشود مقدار بازگشتی بدون ابهام باشد؛ اگر f.readline() یک رشتهٔ خالی برگرداند، به انتهای فایل رسیدهایم، در حالی که یک خط خالی با '\n'، یک رشتهٔ حاوی فقط یک newline، نشان داده میشود.
>>> 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(0, 2)) و تنها مقادیر offset معتبر آنهایی هستند که از f.tell() برگردانده شدهاند، یا صفر. هر مقدار offset دیگری رفتار تعریفنشده (undefined behaviour) ایجاد میکند.
اشیاء فایل دارای متدهای اضافی دیگری مانند isatty() و truncate() هستند که کمتر مورد استفاده قرار میگیرند؛ برای راهنمای کامل اشیاء فایل به Library Reference مراجعه کنید.
۷.۲.۲. ذخیرهٔ دادههای ساختاریافته با json
رشتهها را میتوان بهراحتی در یک فایل نوشت و از آن خواند. اعداد کمی تلاش بیشتری میطلبند، زیرا متد read() فقط رشتهها را برمیگرداند، که باید به تابعی مانند int()، که رشتهای مانند '123' را گرفته و مقدار عددی ۱۲۳ را برمیگرداند، منتقل شوند. وقتی میخواهید انواع دادههای پیچیدهتر مانند لیستها و دیکشنریهای تو در تو را ذخیره کنید، تجزیه (parsing) و سریالسازی (serializing) دستی پیچیده میشود.
به جای اینکه کاربران دائماً کد بنویسند و اشکالزدایی کنند تا انواع دادههای پیچیده را در فایلها ذخیره کنند، پایتون به شما امکان میدهد از فرمت محبوب تبادل داده به نام JSON (JavaScript Object Notation) استفاده کنید. ماژول استاندارد به نام json میتواند سلسلهمراتب دادههای پایتون را گرفته و آنها را به نمایشهای رشتهای تبدیل کند؛ این فرآیند سریالسازی (serializing) نامیده میشود. بازسازی دادهها از نمایش رشتهای deserializing نامیده میشود. بین سریالسازی و deserializing، رشتهای که شیء را نشان میدهد ممکن است در یک فایل یا دادهٔ ذخیرهشده باشد، یا از طریق یک اتصال شبکه به ماشینی دور فرستاده شده باشد.
توجه: فرمت JSON معمولاً توسط برنامههای مدرن برای تبادل داده استفاده میشود. بسیاری از برنامهنویسان قبلاً با آن آشنا هستند، که این امر آن را به انتخاب خوبی برای قابلیت همکاری (interoperability) تبدیل میکند.
اگر شیء x را داشته باشید، میتوانید نمایش رشتهٔ JSON آن را با یک خط کد ساده مشاهده کنید:
>>> import json
>>> x = [1, 'simple', 'list']
>>> json.dumps(x)
'[1, "simple", "list"]'
نوع دیگری از تابع dumps()، به نام dump()، بهسادگی شیء را در یک فایل متنی سریالسازی میکند. بنابراین اگر f یک شیء فایل متنی است که برای نوشتن باز شده است، میتوانیم این کار را انجام دهیم:
>>> json.dump(x, f)
برای رمزگشایی (decode) مجدد شیء، اگر f یک فایل باینری یا یک شیء فایل متنی است که برای خواندن باز شده است:
>>> x = json.load(f)
توجه: فایلهای JSON باید با UTF-8 کدگذاری شوند. هنگام باز کردن فایل JSON بهعنوان یک فایل متنی برای خواندن و نوشتن، از encoding="utf-8" استفاده کنید.
این تکنیک سریالسازی ساده میتواند لیستها و دیکشنریها را مدیریت کند، اما سریالسازی نمونههای کلاس دلخواه در JSON نیاز به تلاش بیشتری دارد. مرجع ماژول json حاوی توضیحی در این مورد است.
همچنین ببینید: ماژول pickle
برخلاف JSON، pickle یک پروتکل است که امکان سریالسازی اشیاء پایتون با پیچیدگی دلخواه را فراهم میکند. به این ترتیب، مختص پایتون است و نمیتوان از آن برای ارتباط با برنامههای نوشتهشده به زبانهای دیگر استفاده کرد. همچنین بهطور پیشفرض ناامن است: deserializing دادههای pickle که از یک منبع غیرقابلاعتماد میآید، میتواند کد دلخواه را اجرا کند، اگر دادهها توسط یک مهاجم ماهر ساخته شده باشند.