Hướng dẫn print bit in python

I want to print the bit representation of numbers onto console, so that I can see all operations that are being done on bits itself.

How can I possibly do it in python?

Hướng dẫn print bit in python

Serenity

33.2k19 gold badges111 silver badges111 bronze badges

asked Jun 28, 2009 at 2:55

This kind of thing?

>>> ord('a')
97
>>> hex(ord('a'))
'0x61'
>>> bin(ord('a'))
'0b1100001'

answered Jun 28, 2009 at 3:02

akentakent

3,97728 silver badges27 bronze badges

1

From Python 2.6 - with the string.format method:

"{0:b}".format(0x1234)

in particular, you might like to use padding, so that multiple prints of different numbers still line up:

"{0:16b}".format(0x1234)

and to have left padding with leading 0s rather than spaces:

"{0:016b}".format(0x1234)

From Python 3.6 - with f-strings:

The same three examples, with f-strings, would be:

f"{0x1234:b}"
f"{0x1234:16b}"
f"{0x1234:016b}"

answered Sep 22, 2013 at 16:26

Finlay McWalterFinlay McWalter

1,1321 gold badge9 silver badges12 bronze badges

answered Jun 28, 2009 at 3:02

Slightly off-topic, but might be helpful. For better user-friendly printing I would use custom print function, define representation characters and group spacing for better readability. Here is an example function, it takes a list/array and the group width:

def bprint(A, grp):
    for x in A:
        brp = "{:08b}".format(x)
        L=[]
        for i,b in enumerate(brp):
            if b=="1":
                L.append("k")
            else: 
                L.append("-")
            if (i+1)%grp ==0 :
                L.append(" ")

        print "".join(L) 

#run
A = [0,1,2,127,128,255]
bprint (A,4)

Output:

---- ----
---- ---k
---- --k-
-kkk kkkk
k--- ----
kkkk kkkk

answered May 12, 2015 at 16:38

Hướng dẫn print bit in python

Mikhail VMikhail V

1,35513 silver badges23 bronze badges



Các khóa học miễn phí qua video:
Lập trình C Java C# SQL Server PHP HTML5-CSS3-JavaScript

Mục lục bài viết:

  • print trong Nutshell
    • Gọi hàm print()
    • Tách nhiều đối số
    • Ngăn chặn ngắt dòng
    • In ra tệp
    • Đệm các lời gọi print()
    • In các loại dữ liệu tùy chỉnh
  • Tìm hiểu về print()
    • print là một hàm trong Python 3
    • print là một câu lệnh trong Python 2
  • print với style
    • Cấu trúc dữ liệu lồng nhau để in đẹp
    • Thêm màu sắc với chuỗi thoát ANSI
    • Giao diện người dùng của bảng điều khiển build
    • Living It Up With Cool Animations
    • Tạo âm thanh bằng print()
  • Chế nhạo bản in Python () trong Bài kiểm tra đơn vị
  • print () gỡ lỗi
    • Truy tìm
    • Ghi nhật ký
    • Gỡ lỗi
  • In an toàn bằng sợi chỉ
  • Bản đối chiếu của Python Print
    • Được xây dựng trong
    • Bên thứ ba
  • Phần kết luận


Nếu bạn giống như hầu hết người dùng Python, bao gồm cả tôi, thì bạn có thể bắt đầu hành trình Python của mình bằng cách tìm hiểu về print(). Nó đã giúp bạn viết hello worldmột lớp lót của riêng bạn. Bạn có thể sử dụng nó để hiển thị các thông báo đã định dạng trên màn hình và có thể tìm thấy một số lỗi. Nhưng nếu bạn nghĩ rằng đó là tất cả những gì cần biết về print()chức năng của Python , thì bạn đang bỏ lỡ rất nhiều thứ!

Hãy tiếp tục đọc để tận dụng tối đa chức năng nhỏ có vẻ nhàm chán và không được đánh giá cao này. Hướng dẫn này sẽ giúp bạn bắt kịp tốc độ sử dụng Python print()một cách hiệu quả. Tuy nhiên, hãy chuẩn bị cho một cuộc lặn sâu khi bạn đi qua các phần. Bạn có thể ngạc nhiên về số tiền print()phải cung cấp!

Đến cuối hướng dẫn này, bạn sẽ biết cách:

  • Tránh những lỗi thường gặp với Python print()
  • Xử lý các dòng mới, mã hóa ký tự và bộ đệm
  • Viết văn bản vào tệp
  • Mô phỏng print()trong các bài kiểm tra đơn vị
  • Xây dựng giao diện người dùng nâng cao trong thiết bị đầu cuối

Nếu bạn là người mới bắt đầu học Python, thì bạn sẽ được hưởng lợi nhiều nhất khi đọc phần đầu tiên của hướng dẫn này, phần này minh họa các yếu tố cần thiết của việc in bằng Python.

Lưu ý: print() là một bổ sung chính cho Python 3, trong đó nó thay thế câu lệnh print có sẵn trong Python 2.

In trong Nutshell

Hãy bắt đầu bằng cách xem xét một vài ví dụ thực tế về in bằng Python. Đến cuối phần này, bạn sẽ biết mọi cách gọi có thể print(). Hoặc, trong biệt ngữ của lập trình viên, bạn nói rằng bạn sẽ quen thuộc với cú pháp hàm.

Lời gọi print()

Ví dụ đơn giản nhất về việc sử dụng Python print() như sau:

>>> print()

Bạn không truyền bất kỳ đối số nào, nhưng bạn vẫn cần đặt cặp ngoặc tròn () ở cuối, điều này cho biết Python thực sự thực thi hàm thay vì chỉ tham chiếu đến nó bằng tên.

Điều này sẽ tạo ra một ký tự dòng mới vô hình, do đó sẽ khiến một dòng trống xuất hiện trên màn hình của bạn. Bạn có thể gọi print() nhiều lần như vậy để thêm không gian theo chiều dọc. Nó giống như thể bạn đang nhấn Enter trên bàn phím của mình trong một trình xử lý văn bản.

Như bạn vừa thấy, việc gọi print() mà không có đối số dẫn đến một dòng trống, là một dòng chỉ bao gồm ký tự dòng mới. Đừng nhầm lẫn điều này với một dòng trống, không chứa bất kỳ ký tự nào, thậm chí không phải dòng mới!

Bạn có thể sử dụng các ký tự chuỗi của Python để hình dung hai điều sau:

'\n'  # Blank line
''    # Empty line

Điều đầu tiên là một ký tự dài, điều thứ hai không có nội dung.

Lưu ý: Để xóa ký tự dòng mới khỏi một chuỗi trong Python, hãy sử dụng phương thức rstrip() như sau:

>>> 'A line of text.\n'.rstrip()
'A line of text.'

Thao tác này loại bỏ bất kỳ khoảng trắng nào ở cuối từ cạnh bên phải của chuỗi ký tự.

Trong một tình huống phổ biến hơn, bạn muốn truyền một số thông điệp đến người dùng cuối. Có một số cách để đạt được điều này.

Đầu tiên, bạn có thể truyền trực tiếp một chuỗi ký tự đến print():

>>> print('Please wait while the program is loading...')

Thao tác này sẽ in nguyên văn thông báo ra màn hình.

Thứ hai, bạn có thể trích xuất thông báo đó thành biến riêng của nó với một cái tên có ý nghĩa để nâng cao khả năng đọc và thúc đẩy việc sử dụng lại mã:

>>> message = 'Please wait while the program is loading...'
>>> print(message)

Cuối cùng, bạn có thể chuyển một biểu thức, như nối chuỗi, để được đánh giá trước khi in kết quả:

>>> import os
>>> print('Hello, ' + os.getlogin() + '! How are you?')
Hello, jdoe! How are you?

zzzTrên thực tế, có nhiều cách để định dạng thư trong Python. Tôi thực sự khuyến khích bạn xem qua f-string , được giới thiệu trong Python 3.6, vì chúng cung cấp cú pháp ngắn gọn nhất trong số tất cả:

>>>

>>> import os
>>> print(f'Hello, {os.getlogin()}! How are you?')

Hơn nữa, f-string sẽ giúp bạn không mắc phải một lỗi phổ biến, đó là quên nhập các toán hạng nối kiểu cast. Python là một ngôn ngữ được đánh máy mạnh, có nghĩa là nó sẽ không cho phép bạn làm điều này:

>>>

>>> 'My age is ' + 42
Traceback (most recent call last):
  File "", line 1, in 
    'My age is ' + 42
TypeError: can only concatenate str (not "int") to str

Điều đó sai vì thêm số vào chuỗi không có ý nghĩa. Trước tiên, bạn cần chuyển đổi số thành chuỗi một cách rõ ràng, để kết hợp chúng với nhau:

>>>

>>> 'My age is ' + str(42)
'My age is 42'

Trừ khi bạn tự xử lý các lỗi như vậy , trình thông dịch Python sẽ cho bạn biết về sự cố bằng cách hiển thị một dấu vết .

Lưu ý: str() là một hàm tích hợp toàn cục có chức năng chuyển đổi một đối tượng thành biểu diễn chuỗi của nó.

Bạn có thể gọi nó trực tiếp trên bất kỳ đối tượng nào, ví dụ: một số:

Các kiểu dữ liệu dựng sẵn có biểu diễn chuỗi được xác định trước ngoài hộp, nhưng ở phần sau của bài viết này, bạn sẽ tìm hiểu cách cung cấp một biểu diễn chuỗi cho các lớp tùy chỉnh của mình.

Như với bất kỳ hàm nào, không quan trọng bạn truyền một ký tự, một biến hay một biểu thức. Tuy nhiên, không giống như nhiều hàm khác, print()sẽ chấp nhận bất kỳ thứ gì bất kể loại của nó.

Từ trước đến nay, bạn chỉ nhìn vào chuỗi, còn các kiểu dữ liệu khác thì sao? Hãy thử các nghĩa đen của các loại tích hợp sẵn khác nhau và xem điều gì xuất hiện:

>>>

>>> print(42)                            # 
42
>>> print(3.14)                          # 
3.14
>>> print(1 + 2j)                        # 
(1+2j)
>>> print(True)                          # 
True
>>> print([1, 2, 3])                     # 
[1, 2, 3]
>>> print((1, 2, 3))                     # 
(1, 2, 3)
>>> print({'red', 'green', 'blue'})      # 
{'red', 'green', 'blue'}
>>> print({'name': 'Alice', 'age': 42})  # 
{'name': 'Alice', 'age': 42}
>>> print('hello')                       # 
hello

NoneMặc dù vậy, hãy để ý đến hằng số. Mặc dù được sử dụng để chỉ ra sự vắng mặt của một giá trị, nhưng nó sẽ hiển thị dưới dạng 'None'một chuỗi rỗng:

Làm thế nào không print()biết làm thế nào để làm việc với tất cả các loại khác nhau? Chà, câu trả lời ngắn gọn là nó không. Nó ngầm gọi str()đằng sau hậu trường để nhập bất kỳ đối tượng nào vào một chuỗi. Sau đó, nó xử lý các chuỗi theo một cách thống nhất.

Phần sau của hướng dẫn này, bạn sẽ học cách sử dụng cơ chế này để in các kiểu dữ liệu tùy chỉnh, chẳng hạn như các lớp của bạn.

Được rồi, bây giờ bạn có thể gọi print()với một đối số duy nhất hoặc không có bất kỳ đối số nào. Bạn biết cách in các tin nhắn cố định hoặc đã định dạng lên màn hình. Phần phụ tiếp theo sẽ mở rộng về định dạng thư một chút.

Tách nhiều đối số

Bạn đã thấy print()được gọi mà không có bất kỳ đối số nào để tạo ra một dòng trống và sau đó được gọi với một đối số duy nhất để hiển thị một thông báo cố định hoặc một thông báo được định dạng.

Tuy nhiên, nó chỉ ra rằng hàm này có thể chấp nhận bất kỳ số lượng đối số vị trí nào , bao gồm không, một hoặc nhiều đối số. Điều đó rất hữu ích trong trường hợp định dạng thư phổ biến, khi bạn muốn kết hợp một vài phần tử lại với nhau.

Hãy xem ví dụ này:

>>>

>>> import os
>>> print('My name is', os.getlogin(), 'and I am', 42)
My name is jdoe and I am 42

print()nối tất cả bốn đối số được truyền cho nó và nó chèn một khoảng trắng giữa chúng để bạn không kết thúc với một thông báo bị bóp méo như thế 'My name isjdoeand I am42'.

Lưu ý rằng nó cũng đã quan tâm đến việc ép kiểu thích hợp bằng cách gọi ngầm str()vào mỗi đối số trước khi kết hợp chúng lại với nhau. Nếu bạn nhớ lại từ tiểu mục trước, một phép ghép đơn giản có thể dễ dàng dẫn đến lỗi do các kiểu không tương thích:

>>>

>>> print('My age is: ' + 42)
Traceback (most recent call last):
  File "", line 1, in 
    print('My age is: ' + 42)
TypeError: can only concatenate str (not "int") to str

Apart from accepting a variable number of positional arguments, print() defines four named or keyword arguments, which are optional since they all have default values. You can view their brief documentation by calling help(print) from the interactive interpreter.

Let’s focus on sep just for now. It stands for separator and is assigned a single space (' ') by default. It determines the value to join elements with.

It has to be either a string or None, but the latter has the same effect as the default space:

>>>

>>> print('hello', 'world', sep=None)
hello world
>>> print('hello', 'world', sep=' ')
hello world
>>> print('hello', 'world')
hello world

If you wanted to suppress the separator completely, you’d have to pass an empty string ('') instead:

>>>

>>> print('hello', 'world', sep='')
helloworld

Bạn có thể muốn print()nối các đối số của nó thành các dòng riêng biệt. Trong trường hợp đó, chỉ cần chuyển ký tự dòng mới thoát được mô tả trước đó:

>>>

>>> print('hello', 'world', sep='\n')
hello
world

Một ví dụ hữu ích hơn về septham số sẽ là in một cái gì đó giống như đường dẫn tệp:

>>>

>>> print('home', 'user', 'documents', sep='/')
home/user/documents

Hãy nhớ rằng dấu phân cách nằm giữa các phần tử, không phải xung quanh chúng, vì vậy bạn cần tính toán điều đó theo cách này hay cách khác:

>>>

>>> print('/home', 'user', 'documents', sep='/')
/home/user/documents
>>> print('', 'home', 'user', 'documents', sep='/')
/home/user/documents

Cụ thể, bạn có thể chèn một ký tự gạch chéo ( /) vào đối số vị trí đầu tiên hoặc sử dụng một chuỗi rỗng làm đối số đầu tiên để thực thi dấu gạch chéo đầu tiên.

Lưu ý: Hãy cẩn thận về việc nối các phần tử của một danh sách hoặc bộ dữ liệu.

Thực hiện theo cách thủ công sẽ dẫn đến kết quả nổi tiếng TypeErrornếu ít nhất một trong các phần tử không phải là chuỗi:

>>>

>>> print(' '.join(['jdoe is', 42, 'years old']))
Traceback (most recent call last):
  File "", line 1, in 
    print(','.join(['jdoe is', 42, 'years old']))
TypeError: sequence item 1: expected str instance, int found

Sẽ an toàn hơn nếu chỉ giải nén chuỗi bằng toán tử star ( *) và để print()xử lý kiểu truyền:

>>>

>>> print(*['jdoe is', 42, 'years old'])
jdoe is 42 years old

Việc giải nén cũng giống như gọi print()với các phần tử riêng lẻ của danh sách.

Một ví dụ thú vị khác có thể là xuất dữ liệu sang định dạng giá trị được phân tách bằng dấu phẩy (CSV):

>>>

>>> print(1, 'Python Tricks', 'Dan Bader', sep=',')
1,Python Tricks,Dan Bader

Điều này sẽ không xử lý các trường hợp cạnh chẳng hạn như thoát dấu phẩy một cách chính xác, nhưng đối với các trường hợp sử dụng đơn giản, nó sẽ làm được. Dòng trên sẽ hiển thị trong cửa sổ đầu cuối của bạn. Để lưu nó vào một tệp, bạn phải chuyển hướng đầu ra. Ở phần sau của phần này, bạn sẽ thấy cách sử dụng print()để ghi văn bản vào tệp trực tiếp từ Python.

Cuối cùng, septham số không bị giới hạn chỉ trong một ký tự. Bạn có thể nối các phần tử với các chuỗi có độ dài bất kỳ:

>>>

>>> print('node', 'child', 'child', sep=' -> ')
node -> child -> child

In the upcoming subsections, you’ll explore the remaining keyword arguments of the print() function.

Preventing Line Breaks

Sometimes you don’t want to end your message with a trailing newline so that subsequent calls to print() will continue on the same line. Classic examples include updating the progress of a long-running operation or prompting the user for input. In the latter case, you want the user to type in the answer on the same line:

Are you sure you want to do this? [y/n] y

Nhiều ngôn ngữ lập trình hiển thị các chức năng tương tự như print()thông qua các thư viện tiêu chuẩn của chúng, nhưng chúng cho phép bạn quyết định có thêm một dòng mới hay không. Ví dụ: trong Java và C #, bạn có hai hàm riêng biệt, trong khi các ngôn ngữ khác yêu cầu bạn phải nối rõ ràng \nvào cuối một chuỗi ký tự.

Dưới đây là một số ví dụ về cú pháp trong các ngôn ngữ như vậy:

Ngôn ngữThí dụ
Perl print "hello world\n"
C printf("hello world\n");
C ++ std::cout << "hello world" << std::endl;

Ngược lại, print()hàm của Python luôn thêm \nmà không cần hỏi, vì đó là những gì bạn muốn trong hầu hết các trường hợp. Để vô hiệu hóa nó, bạn có thể tận dụng lợi thế của một đối số từ khóa khác, đối số endnày cho biết phải kết thúc dòng bằng gì.

Về ngữ nghĩa, endtham số gần như giống với tham số sepmà bạn đã thấy trước đó:

  • Nó phải là một chuỗi hoặc None.
  • Nó có thể dài tùy ý.
  • Nó có giá trị mặc định là '\n'.
  • Nếu bằng None, nó sẽ có cùng tác dụng với giá trị mặc định.
  • Nếu bằng một chuỗi rỗng ( ''), nó sẽ ngăn dòng mới.

Bây giờ bạn hiểu điều gì đang xảy ra khi bạn đang gọi print()mà không có đối số. Vì bạn không cung cấp bất kỳ đối số vị trí nào cho hàm nên không có gì được nối và do đó dấu phân tách mặc định hoàn toàn không được sử dụng. Tuy nhiên, giá trị mặc định của endvẫn được áp dụng và một dòng trống sẽ hiển thị.

Lưu ý: Bạn có thể tự hỏi tại sao endtham số có giá trị mặc định cố định thay vì bất cứ điều gì có ý nghĩa trên hệ điều hành của bạn.

Chà, bạn không phải lo lắng về biểu diễn dòng mới trên các hệ điều hành khác nhau khi in, vì print()sẽ tự động xử lý chuyển đổi. Chỉ cần nhớ luôn sử dụng \ntrình tự thoát trong chuỗi ký tự.

Đây hiện là cách di động nhất để in một ký tự dòng mới trong Python:

>>>

>>> print('line1\nline2\nline3')
line1
line2
line3

Ví dụ: nếu bạn cố gắng in một ký tự dòng mới dành riêng cho Windows trên máy Linux, bạn sẽ nhận được kết quả đầu ra bị hỏng:

>>>

>>> print('line1\r\nline2\r\nline3')


line3

Ở bên flip, khi bạn mở một file để đọc với open(), bạn không cần phải quan tâm đến đại diện xuống dòng trong hai. Hàm sẽ chuyển bất kỳ dòng mới nào của hệ thống cụ thể mà nó gặp phải thành một phổ quát '\n'. Đồng thời, bạn có quyền kiểm soát cách các dòng mới nên được xử lý như thế nào cả về đầu vào và đầu ra nếu bạn thực sự cần điều đó.

Để tắt dòng mới, bạn phải chỉ định một chuỗi trống thông qua endđối số từ khóa:

print('Checking file integrity...', end='')
# (...)
print('ok')

Mặc dù đây là hai print()cuộc gọi riêng biệt , có thể thực hiện cách nhau một thời gian dài, nhưng cuối cùng bạn sẽ chỉ thấy một dòng. Đầu tiên, nó sẽ trông như thế này:

Checking file integrity...

Tuy nhiên, sau cuộc gọi thứ hai đến print(), dòng tương tự sẽ xuất hiện trên màn hình như:

Checking file integrity...ok

Tương tự như vậy sep, bạn có thể sử dụng endđể nối các phần riêng lẻ thành một khối văn bản lớn bằng dấu phân tách tùy chỉnh. Tuy nhiên, thay vì nối nhiều đối số, nó sẽ nối văn bản từ mỗi lệnh gọi hàm vào cùng một dòng:

print('The first sentence', end='. ')
print('The second sentence', end='. ')
print('The last sentence.')

Ba hướng dẫn này sẽ xuất ra một dòng văn bản:

The first sentence. The second sentence. The last sentence.

Bạn có thể kết hợp hai đối số từ khóa:

print('Mercury', 'Venus', 'Earth', sep=', ', end=', ')
print('Mars', 'Jupiter', 'Saturn', sep=', ', end=', ')
print('Uranus', 'Neptune', 'Pluto', sep=', ')

Bạn không chỉ nhận được một dòng văn bản mà tất cả các mục đều được phân tách bằng dấu phẩy:

Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune, Pluto

Không có gì ngăn bạn sử dụng ký tự dòng mới với một số phần đệm bổ sung xung quanh nó:

print('Printing in a Nutshell', end='\n * ')
print('Calling Print', end='\n * ')
print('Separating Multiple Arguments', end='\n * ')
print('Preventing Line Breaks')

Nó sẽ in ra đoạn văn bản sau:

Printing in a Nutshell
 * Calling Print
 * Separating Multiple Arguments
 * Preventing Line Breaks

Như bạn có thể thấy, endđối số từ khóa sẽ chấp nhận các chuỗi tùy ý.

Lưu ý: Việc lặp qua các dòng trong tệp văn bản giữ nguyên các ký tự dòng mới của riêng chúng, điều này kết hợp với print()hành vi mặc định của hàm sẽ dẫn đến một ký tự dòng mới dư thừa:

>>>

>>> with open('file.txt') as file_object:
...     for line in file_object:
...         print(line)
...
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod

tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,

quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo

Có hai dòng mới sau mỗi dòng văn bản. Bạn muốn loại bỏ một trong số chúng, như được hiển thị trước đó trong bài viết này, trước khi in dòng:

Ngoài ra, bạn có thể giữ dòng mới trong nội dung nhưng loại bỏ dòng được thêm vào bằng cách print()tự động. Bạn sẽ sử dụng endđối số từ khóa để làm điều đó:

>>>

>>> with open('file.txt') as file_object:
...     for line in file_object:
...         print(line, end='')
...
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo

Bằng cách kết thúc một dòng bằng một chuỗi trống, bạn vô hiệu hóa một cách hiệu quả một trong các dòng mới.

Bạn đang làm quen nhiều hơn với việc in bằng Python, nhưng vẫn còn rất nhiều thông tin hữu ích ở phía trước. Trong phần phụ sắp tới, bạn sẽ học cách chặn và chuyển hướng print()đầu ra của hàm.

In ra tệp

Tin hay không thì tùy, print()bạn không biết cách biến tin nhắn thành văn bản trên màn hình của bạn và nói thẳng ra là không cần. Đó là công việc dành cho các lớp mã cấp thấp hơn, những lớp này hiểu các byte và biết cách đẩy chúng xung quanh.

print()là một sự trừu tượng hóa trên các lớp này, cung cấp một giao diện thuận tiện chỉ ủy thác việc in thực tế cho một luồng hoặc đối tượng giống tệp . Luồng có thể là bất kỳ tệp nào trên đĩa của bạn, ổ cắm mạng hoặc có thể là bộ đệm trong bộ nhớ.

Ngoài ra, có ba luồng tiêu chuẩn được cung cấp bởi hệ điều hành:

  1. stdin: đầu vào tiêu chuẩn
  2. stdout: đầu ra tiêu chuẩn
  3. stderr: lỗi tiêu chuẩn

Trong Python, bạn có thể truy cập tất cả các luồng tiêu chuẩn thông qua sysmô-đun tích hợp:

>>>

>>> import sys
>>> sys.stdin
<_io.TextIOWrapper name='' mode='r' encoding='UTF-8'>
>>> sys.stdin.fileno()
0
>>> sys.stdout
<_io.TextIOWrapper name='' mode='w' encoding='UTF-8'>
>>> sys.stdout.fileno()
1
>>> sys.stderr
<_io.TextIOWrapper name='' mode='w' encoding='UTF-8'>
>>> sys.stderr.fileno()
2

Như bạn có thể thấy, các giá trị được xác định trước này giống với các đối tượng giống tệp với modevà encodingthuộc tính cũng như .read()và .write()phương thức trong số nhiều giá trị khác.

Theo mặc định, print()được ràng buộc sys.stdoutthông qua fileđối số của nó , nhưng bạn có thể thay đổi điều đó. Sử dụng đối số từ khóa đó để chỉ ra một tệp được mở ở chế độ ghi hoặc nối thêm, để các thông báo đi thẳng đến tệp đó:

with open('file.txt', mode='w') as file_object:
    print('hello world', file=file_object)

Điều này sẽ làm cho mã của bạn miễn nhiễm với chuyển hướng luồng ở cấp hệ điều hành, điều này có thể mong muốn hoặc có thể không.

Để biết thêm thông tin về cách làm việc với tệp bằng Python , bạn có thể xem Đọc và Viết tệp bằng Python (Hướng dẫn) .

Note: Don’t try using print() for writing binary data as it’s only well suited for text.

Just call the binary file’s .write() directly:

with open('file.dat', 'wb') as file_object:
    file_object.write(bytes(4))
    file_object.write(b'\xff')

If you wanted to write raw bytes on the standard output, then this will fail too because sys.stdout is a character stream:

>>>

>>> import sys
>>> sys.stdout.write(bytes(4))
Traceback (most recent call last):
  File "", line 1, in 
TypeError: write() argument must be str, not bytes

You must dig deeper to get a handle of the underlying byte stream instead:

>>>

>>> import sys
>>> num_bytes_written = sys.stdout.buffer.write(b'\x41\x0a')
A

This prints an uppercase letter A and a newline character, which correspond to decimal values of 65 and 10 in ASCII. However, they’re encoded using hexadecimal notation in the bytes literal.

Note that print() has no control over character encoding. It’s the stream’s responsibility to encode received Unicode strings into bytes correctly. In most cases, you won’t set the encoding yourself, because the default UTF-8 is what you want. If you really need to, perhaps for legacy systems, you can use the encoding argument of open():

with open('file.txt', mode='w', encoding='iso-8859-1') as file_object:
    print('über naïve café', file=file_object)

Instead of a real file existing somewhere in your file system, you can provide a fake one, which would reside in your computer’s memory. You’ll use this technique later for mocking print() in unit tests:

>>>

>>> import io
>>> fake_file = io.StringIO()
>>> print('hello world', file=fake_file)
>>> fake_file.getvalue()
'hello world\n'

If you got to this point, then you’re left with only one keyword argument in print(), which you’ll see in the next subsection. It’s probably the least used of them all. Nevertheless, there are times when it’s absolutely necessary.

Buffering print() Calls

In the previous subsection, you learned that print() delegates printing to a file-like object such as sys.stdout. Some streams, however, buffer certain I/O operations to enhance performance, which can get in the way. Let’s take a look at an example.

Imagine you were writing a countdown timer, which should append the remaining time to the same line every second:

Your first attempt may look something like this:

import time

num_seconds = 3
for countdown in reversed(range(num_seconds + 1)):
    if countdown > 0:
        print(countdown, end='...')
        time.sleep(1)
    else:
        print('Go!')

As long as the countdown variable is greater than zero, the code keeps appending text without a trailing newline and then goes to sleep for one second. Finally, when the countdown is finished, it prints Go! and terminates the line.

Unexpectedly, instead of counting down every second, the program idles wastefully for three seconds, and then suddenly prints the entire line at once:

That’s because the operating system buffers subsequent writes to the standard output in this case. You need to know that there are three kinds of streams with respect to buffering:

  1. Unbuffered
  2. Line-buffered
  3. Block-buffered

Unbuffered is self-explanatory, that is, no buffering is taking place, and all writes have immediate effect. A line-buffered stream waits before firing any I/O calls until a line break appears somewhere in the buffer, whereas a block-buffered one simply allows the buffer to fill up to a certain size regardless of its content. Standard output is both line-buffered and block-buffered, depending on which event comes first.

Buffering helps to reduce the number of expensive I/O calls. Think about sending messages over a high-latency network, for example. When you connect to a remote server to execute commands over the SSH protocol, each of your keystrokes may actually produce an individual data packet, which is orders of magnitude bigger than its payload. What an overhead! It would make sense to wait until at least a few characters are typed and then send them together. That’s where buffering steps in.

On the other hand, buffering can sometimes have undesired effects as you just saw with the countdown example. To fix it, you can simply tell print() to forcefully flush the stream without waiting for a newline character in the buffer using its flush flag:

print(countdown, end='...', flush=True)

That’s all. Your countdown should work as expected now, but don’t take my word for it. Go ahead and test it to see the difference.

Congratulations! At this point, you’ve seen examples of calling print() that cover all of its parameters. You know their purpose and when to use them. Understanding the signature is only the beginning, however. In the upcoming sections, you’ll see why.

Printing Custom Data Types

Up until now, you only dealt with built-in data types such as strings and numbers, but you’ll often want to print your own abstract data types. Let’s have a look at different ways of defining them.

For simple objects without any logic, whose purpose is to carry data, you’ll typically take advantage of namedtuple, which is available in the standard library. Named tuples have a neat textual representation out of the box:

>>>

>>> from collections import namedtuple
>>> Person = namedtuple('Person', 'name age')
>>> jdoe = Person('John Doe', 42)
>>> print(jdoe)
Person(name='John Doe', age=42)

That’s great as long as holding data is enough, but in order to add behaviors to the Person type, you’ll eventually need to define a class. Take a look at this example:

class Person:
    def __init__(self, name, age):
        self.name, self.age = name, age

If you now create an instance of the Person class and try to print it, you’ll get this bizarre output, which is quite different from the equivalent namedtuple:

>>>

>>> jdoe = Person('John Doe', 42)
>>> print(jdoe)
<__main__.Person object at 0x7fcac3fed1d0>

Đó là đại diện mặc định của các đối tượng, bao gồm địa chỉ của chúng trong bộ nhớ, tên lớp tương ứng và mô-đun mà chúng được định nghĩa. Bạn sẽ khắc phục điều đó một chút, nhưng chỉ đối với bản ghi, như một giải pháp nhanh chóng mà bạn có thể kết hợp namedtuplevà một lớp tùy chỉnh thông qua kế thừa :

from collections import namedtuple

class Person(namedtuple('Person', 'name age')):
    pass

PersonLớp của bạn vừa trở thành một loại chuyên biệt namedtuplevới hai thuộc tính, bạn có thể tùy chỉnh.

Lưu ý: Trong Python 3, passcâu lệnh có thể được thay thế bằng dấu chấm lửng ( ...) để chỉ ra một trình giữ chỗ:

Điều này ngăn trình thông dịch nâng lên IndentationErrordo thiếu khối mã được thụt lề.

Điều đó tốt hơn so với thông thường namedtuple, bởi vì bạn không chỉ được in miễn phí mà còn có thể thêm các phương thức và thuộc tính tùy chỉnh vào lớp. Tuy nhiên, nó giải quyết một vấn đề trong khi giới thiệu một vấn đề khác. Hãy nhớ rằng các bộ giá trị, bao gồm các bộ giá trị được đặt tên, là bất biến trong Python, vì vậy chúng không thể thay đổi giá trị của chúng sau khi được tạo.

Đúng là việc thiết kế các kiểu dữ liệu bất biến là điều mong muốn, nhưng trong nhiều trường hợp, bạn sẽ muốn chúng cho phép thay đổi, vì vậy bạn quay lại với các lớp bình thường một lần nữa.

Lưu ý: Tiếp theo các ngôn ngữ và khung công tác khác, Python 3.7 đã giới thiệu các lớp dữ liệu mà bạn có thể coi là các bộ dữ liệu có thể thay đổi. Bằng cách này, bạn sẽ có được những điều tốt nhất của cả hai thế giới:

>>>

>>> from dataclasses import dataclass
>>> @dataclass
... class Person:
...     name: str
...     age: int
...     
...     def celebrate_birthday(self):
...         self.age += 1
... 
>>> jdoe = Person('John Doe', 42)
>>> jdoe.celebrate_birthday()
>>> print(jdoe)
Person(name='John Doe', age=43)

Cú pháp cho các chú thích biến , được yêu cầu để chỉ định các trường lớp với các kiểu tương ứng của chúng, đã được định nghĩa trong Python 3.6.

Từ các phần con trước đó, bạn đã biết rằng print()hàm tích hợp ngầm gọi str()hàm tích hợp để chuyển đổi các đối số vị trí của nó thành chuỗi. Thật vậy, việc gọi str()thủ công đối với một thể hiện của Personlớp thông thường sẽ mang lại kết quả giống như in nó:

>>>

>>> jdoe = Person('John Doe', 42)
>>> str(jdoe)
'<__main__.Person object at 0x7fcac3fed1d0>'

str()ngược lại, tìm kiếm một trong hai phương thức ma thuật trong phần thân lớp, mà bạn thường triển khai. Nếu nó không tìm thấy một cái, thì nó sẽ trở lại biểu diễn mặc định xấu xí. Những phương pháp kỳ diệu đó, theo thứ tự tìm kiếm:

  1. def __str__(self)
  2. def __repr__(self)

The first one is recommended to return a short, human-readable text, which includes information from the most relevant attributes. After all, you don’t want to expose sensitive data, such as user passwords, when printing objects.

However, the other one should provide complete information about an object, to allow for restoring its state from a string. Ideally, it should return valid Python code, so that you can pass it directly to eval():

>>>

>>> repr(jdoe)
"Person(name='John Doe', age=42)"
>>> type(eval(repr(jdoe)))

Notice the use of another built-in function, repr(), which always tries to call .__repr__() in an object, but falls back to the default representation if it doesn’t find that method.

Note: Even though print() itself uses str() for type casting, some compound data types delegate that call to repr() on their members. This happens to lists and tuples, for example.

Consider this class with both magic methods, which return alternative string representations of the same object:

class User:
    def __init__(self, login, password):
        self.login = login
        self.password = password

    def __str__(self):
        return self.login

    def __repr__(self):
        return f"User('{self.login}', '{self.password}')"

If you print a single object of the User class, then you won’t see the password, because print(user) will call str(user), which eventually will invoke user.__str__():

>>>

>>> user = User('jdoe', 's3cret')
>>> print(user)
jdoe

However, if you put the same user variable inside a list by wrapping it in square brackets, then the password will become clearly visible:

>>>

>>> print([user])
[User('jdoe', 's3cret')]

That’s because sequences, such as lists and tuples, implement their .__str__() method so that all of their elements are first converted with repr().

Python gives you a lot of freedom when it comes to defining your own data types if none of the built-in ones meet your needs. Some of them, such as named tuples and data classes, offer string representations that look good without requiring any work on your part. Still, for the most flexibility, you’ll have to define a class and override its magic methods described above.

Understanding Python print()

You know how to use print() quite well at this point, but knowing what it is will allow you to use it even more effectively and consciously. After reading this section, you’ll understand how printing in Python has improved over the years.

You’ve seen that print() is a function in Python 3. More specifically, it’s a built-in function, which means that you don’t need to import it from anywhere:

>>>

>>> print

It’s always available in the global namespace so that you can call it directly, but you can also access it through a module from the standard library:

>>>

>>> import builtins
>>> builtins.print

This way, you can avoid name collisions with custom functions. Let’s say you wanted to redefine print() so that it doesn’t append a trailing newline. At the same time, you wanted to rename the original function to something like println():

>>>

>>> import builtins
>>> println = builtins.print
>>> def print(*args, **kwargs):
...     builtins.print(*args, **kwargs, end='')
...
>>> println('hello')
hello
>>> print('hello\n')
hello

Now you have two separate printing functions just like in the Java programming language. You’ll define custom print() functions in the mocking section later as well. Also, note that you wouldn’t be able to overwrite print() in the first place if it wasn’t a function.

On the other hand, print() isn’t a function in the mathematical sense, because it doesn’t return any meaningful value other than the implicit None:

>>>

>>> value = print('hello world')
hello world
>>> print(value)
None

Such functions are, in fact, procedures or subroutines that you call to achieve some kind of side-effect, which ultimately is a change of a global state. In the case of print(), that side-effect is showing a message on the standard output or writing to a file.

Because print() is a function, it has a well-defined signature with known attributes. You can quickly find its documentation using the editor of your choice, without having to remember some weird syntax for performing a certain task.

Besides, functions are easier to extend. Adding a new feature to a function is as easy as adding another keyword argument, whereas changing the language to support that new feature is much more cumbersome. Think of stream redirection or buffer flushing, for example.

Another benefit of print() being a function is composability. Functions are so-called first-class objects or first-class citizens in Python, which is a fancy way of saying they’re values just like strings or numbers. This way, you can assign a function to a variable, pass it to another function, or even return one from another. print() isn’t different in this regard. For instance, you can take advantage of it for dependency injection:

def download(url, log=print):
    log(f'Downloading {url}')
    # ...

def custom_print(*args):
    pass  # Do not print anything

download('/js/app.js', log=custom_print)

Here, the log parameter lets you inject a callback function, which defaults to print() but can be any callable. In this example, printing is completely disabled by substituting print() with a dummy function that does nothing.

Note: A dependency is any piece of code required by another bit of code.

Dependency injection is a technique used in code design to make it more testable, reusable, and open for extension. You can achieve it by referring to dependencies indirectly through abstract interfaces and by providing them in a push rather than pull fashion.

There’s a funny explanation of dependency injection circulating on the Internet:

Dependency injection for five-year-olds

When you go and get things out of the refrigerator for yourself, you can cause problems. You might leave the door open, you might get something Mommy or Daddy doesn’t want you to have. You might even be looking for something we don’t even have or which has expired.

What you should be doing is stating a need, “I need something to drink with lunch,” and then we will make sure you have something when you sit down to eat.

— John Munsch, 28 October 2009. (Source)

Composition allows you to combine a few functions into a new one of the same kind. Let’s see this in action by specifying a custom error() function that prints to the standard error stream and prefixes all messages with a given log level:

>>>

>>> from functools import partial
>>> import sys
>>> redirect = lambda function, stream: partial(function, file=stream)
>>> prefix = lambda function, prefix: partial(function, prefix)
>>> error = prefix(redirect(print, sys.stderr), '[ERROR]')
>>> error('Something went wrong')
[ERROR] Something went wrong

This custom function uses partial functions to achieve the desired effect. It’s an advanced concept borrowed from the functional programming paradigm, so you don’t need to go too deep into that topic for now. However, if you’re interested in this topic, I recommend taking a look at the functools module.

Unlike statements, functions are values. That means you can mix them with expressions, in particular, lambda expressions. Instead of defining a full-blown function to replace print() with, you can make an anonymous lambda expression that calls it:

>>>

>>> download('/js/app.js', lambda msg: print('[INFO]', msg))
[INFO] Downloading /js/app.js

However, because a lambda expression is defined in place, there’s no way of referring to it elsewhere in the code.

Note: In Python, you can’t put statements, such as assignments, conditional statements, loops, and so on, in an anonymous lambda function. It has to be a single expression!

Another kind of expression is a ternary conditional expression:

>>>

>>> user = 'jdoe'
>>> print('Hi!') if user is None else print(f'Hi, {user}.')
Hi, jdoe.

Python has both conditional statements and conditional expressions. The latter is evaluated to a single value that can be assigned to a variable or passed to a function. In the example above, you’re interested in the side-effect rather than the value, which evaluates to None, so you simply ignore it.

As you can see, functions allow for an elegant and extensible solution, which is consistent with the rest of the language. In the next subsection, you’ll discover how not having print() as a function caused a lot of headaches.

A statement is an instruction that may evoke a side-effect when executed but never evaluates to a value. In other words, you wouldn’t be able to print a statement or assign it to a variable like this:

result = print 'hello world'

That’s a syntax error in Python 2.

Here are a few more examples of statements in Python:

  • assignment: =
  • conditional: if
  • loop: while
  • assertion: assert

Note: Python 3.8 brings a controversial walrus operator (:=), which is an assignment expression. With it, you can evaluate an expression and assign the result to a variable at the same time, even within another expression!

Take a look at this example, which calls an expensive function once and then reuses the result for further computation:

# Python 3.8+
values = [y := f(x), y**2, y**3]

This is useful for simplifying the code without losing its efficiency. Typically, performant code tends to be more verbose:

y = f(x)
values = [y, y**2, y**3]

The controversy behind this new piece of syntax caused a lot of argument. An abundance of negative comments and heated debates eventually led Guido van Rossum to step down from the Benevolent Dictator For Life or BDFL position.

Statements are usually comprised of reserved keywords such as iffor, or print that have fixed meaning in the language. You can’t use them to name your variables or other symbols. That’s why redefining or mocking the print statement isn’t possible in Python 2. You’re stuck with what you get.

Furthermore, you can’t print from anonymous functions, because statements aren’t accepted in lambda expressions:

>>>

>>> lambda: print 'hello world'
  File "", line 1
    lambda: print 'hello world'
                ^
SyntaxError: invalid syntax

The syntax of the print statement is ambiguous. Sometimes you can add parentheses around the message, and they’re completely optional:

>>>

>>> print 'Please wait...'
Please wait...
>>> print('Please wait...')
Please wait...

At other times they change how the message is printed:

>>>

>>> print 'My name is', 'John'
My name is John
>>> print('My name is', 'John')
('My name is', 'John')

String concatenation can raise a TypeError due to incompatible types, which you have to handle manually, for example:

>>>

>>> values = ['jdoe', 'is', 42, 'years old']
>>> print ' '.join(map(str, values))
jdoe is 42 years old

Compare this with similar code in Python 3, which leverages sequence unpacking:

>>>

>>> values = ['jdoe', 'is', 42, 'years old']
>>> print(*values)  # Python 3
jdoe is 42 years old

There aren’t any keyword arguments for common tasks such as flushing the buffer or stream redirection. You need to remember the quirky syntax instead. Even the built-in help() function isn’t that helpful with regards to the print statement:

>>>

>>> help(print)
  File "", line 1
    help(print)
             ^
SyntaxError: invalid syntax

Trailing newline removal doesn’t work quite right, because it adds an unwanted space. You can’t compose multiple print statements together, and, on top of that, you have to be extra diligent about character encoding.

The list of problems goes on and on. If you’re curious, you can jump back to the previous section and look for more detailed explanations of the syntax in Python 2.

However, you can mitigate some of those problems with a much simpler approach. It turns out the print() function was backported to ease the migration to Python 3. You can import it from a special __future__ module, which exposes a selection of language features released in later Python versions.

Note: You may import future functions as well as baked-in language constructs such as the with statement.

To find out exactly what features are available to you, inspect the module:

>>>

>>> import __future__
>>> __future__.all_feature_names
['nested_scopes',
 'generators',
 'division',
 'absolute_import',
 'with_statement',
 'print_function',
 'unicode_literals']

You could also call dir(__future__), but that would show a lot of uninteresting internal details of the module.

To enable the print() function in Python 2, you need to add this import statement at the beginning of your source code:

from __future__ import print_function

Kể từ bây giờ printcâu lệnh không còn nữa, nhưng bạn có print()chức năng theo ý của bạn. Lưu ý rằng nó không giống hàm như trong Python 3, vì nó thiếu flushđối số từ khóa, nhưng phần còn lại của các đối số đều giống nhau.

Ngoài ra, nó không giúp bạn quản lý mã hóa ký tự đúng cách.

Đây là một ví dụ về cách gọi print()hàm trong Python 2:

>>>

>>> from __future__ import print_function
>>> import sys
>>> print('I am a function in Python', sys.version_info.major)
I am a function in Python 2

Bây giờ bạn có ý tưởng về việc in bằng Python đã phát triển như thế nào và quan trọng nhất là hiểu tại sao những thay đổi không tương thích ngược này lại cần thiết. Biết được điều này chắc chắn sẽ giúp bạn trở thành một lập trình viên Python tốt hơn.
 

In ấn với phong cách

Nếu bạn nghĩ rằng việc in ấn chỉ là làm sáng các pixel trên màn hình, thì về mặt kỹ thuật, bạn đã đúng. Tuy nhiên, có nhiều cách để làm cho nó trông bắt mắt. Trong phần này, bạn sẽ tìm hiểu cách định dạng cấu trúc dữ liệu phức tạp, thêm màu sắc và các trang trí khác, xây dựng giao diện, sử dụng hoạt ảnh và thậm chí phát âm thanh với văn bản!

Cấu trúc dữ liệu lồng nhau in ấn đẹp

Ngôn ngữ máy tính cho phép bạn biểu diễn dữ liệu cũng như mã thực thi theo cách có cấu trúc. Tuy nhiên, không giống như Python, hầu hết các ngôn ngữ đều cho bạn nhiều quyền tự do trong việc sử dụng khoảng trắng và định dạng. Điều này có thể hữu ích, chẳng hạn như trong quá trình nén, nhưng đôi khi nó dẫn đến mã khó đọc hơn.

In đẹp là làm cho một phần dữ liệu hoặc mã trông hấp dẫn hơn đối với mắt người để có thể hiểu dễ dàng hơn. Điều này được thực hiện bằng cách thụt lề các dòng nhất định, chèn các dòng mới, sắp xếp lại thứ tự các phần tử, v.v.

Python đi kèm với pprintmô-đun trong thư viện tiêu chuẩn của nó, điều này sẽ giúp bạn in ấn các cấu trúc dữ liệu lớn không vừa trên một dòng. Bởi vì nó in theo cách thân thiện với con người hơn, nhiều công cụ REPL phổ biến , bao gồm JupyterLab và IPython , sử dụng nó theo mặc định thay cho print()chức năng thông thường .

Lưu ý: Để chuyển đổi chế độ in đẹp trong IPython, hãy sử dụng lệnh sau:

>>>

In [1]: %pprint
Pretty printing has been turned OFF
In [2]: %pprint
Pretty printing has been turned ON

Đây là một ví dụ về Magic trong IPython. Có rất nhiều lệnh tích hợp bắt đầu bằng dấu phần trăm ( %), nhưng bạn có thể tìm thêm trên PyPI hoặc thậm chí tạo lệnh của riêng bạn.

Nếu bạn không quan tâm đến việc không có quyền truy cập vào print()chức năng ban đầu , thì bạn có thể thay thế nó bằng pprint()trong mã của mình bằng cách sử dụng đổi tên nhập:

>>>

>>> from pprint import pprint as print
>>> print

Cá nhân tôi muốn có cả hai chức năng trong tầm tay của mình, vì vậy tôi muốn sử dụng một cái gì đó như ppmột bí danh ngắn:

from pprint import pprint as pp

Thoạt nhìn, hầu như không có bất kỳ sự khác biệt nào giữa hai chức năng và trong một số trường hợp, hầu như không có:

>>>

>>> print(42)
42
>>> pp(42)
42
>>> print('hello')
hello
>>> pp('hello')
'hello'  # Did you spot the difference?

That’s because pprint() calls repr() instead of the usual str() for type casting, so that you may evaluate its output as Python code if you want to. The differences become apparent as you start feeding it more complex data structures:

>>>

>>> data = {'powers': [x**10 for x in range(10)]}
>>> pp(data)
{'powers': [0,
            1,
            1024,
            59049,
            1048576,
            9765625,
            60466176,
            282475249,
            1073741824,
            3486784401]}

The function applies reasonable formatting to improve readability, but you can customize it even further with a couple of parameters. For example, you may limit a deeply nested hierarchy by showing an ellipsis below a given level:

>>>

>>> cities = {'USA': {'Texas': {'Dallas': ['Irving']}}}
>>> pp(cities, depth=3)
{'USA': {'Texas': {'Dallas': [...]}}}

The ordinary print() also uses ellipses but for displaying recursive data structures, which form a cycle, to avoid stack overflow error:

>>>

>>> items = [1, 2, 3]
>>> items.append(items)
>>> print(items)
[1, 2, 3, [...]]

However, pprint() is more explicit about it by including the unique identity of a self-referencing object:

>>>

>>> pp(items)
[1, 2, 3, ]
>>> id(items)
140635757287688

The last element in the list is the same object as the entire list.

Note: Recursive or very large data sets can be dealt with using the reprlib module as well:

>>>

>>> import reprlib
>>> reprlib.repr([x**10 for x in range(10)])
'[0, 1, 1024, 59049, 1048576, 9765625, ...]'

This module supports most of the built-in types and is used by the Python debugger.

pprint() automatically sorts dictionary keys for you before printing, which allows for consistent comparison. When you’re comparing strings, you often don’t care about a particular order of serialized attributes. Anyways, it’s always best to compare actual dictionaries before serialization.

Dictionaries often represent JSON data, which is widely used on the Internet. To correctly serialize a dictionary into a valid JSON-formatted string, you can take advantage of the json module. It too has pretty-printing capabilities:

>>>

>>> import json
>>> data = {'username': 'jdoe', 'password': 's3cret'}
>>> ugly = json.dumps(data)
>>> pretty = json.dumps(data, indent=4, sort_keys=True)
>>> print(ugly)
{"username": "jdoe", "password": "s3cret"}
>>> print(pretty)
{
    "password": "s3cret",
    "username": "jdoe"
}

Notice, however, that you need to handle printing yourself, because it’s not something you’d typically want to do. Similarly, the pprint module has an additional pformat() function that returns a string, in case you had to do something other than printing it.

Surprisingly, the signature of pprint() is nothing like the print() function’s one. You can’t even pass more than one positional argument, which shows how much it focuses on printing data structures.

Adding Colors With ANSI Escape Sequences

As personal computers got more sophisticated, they had better graphics and could display more colors. However, different vendors had their own idea about the API design for controlling it. That changed a few decades ago when people at the American National Standards Institute decided to unify it by defining ANSI escape codes.

Most of today’s terminal emulators support this standard to some degree. Until recently, the Windows operating system was a notable exception. Therefore, if you want the best portability, use the colorama library in Python. It translates ANSI codes to their appropriate counterparts in Windows while keeping them intact in other operating systems.

To check if your terminal understands a subset of the ANSI escape sequences, for example, related to colors, you can try using the following command:

My default terminal on Linux says it can display 256 distinct colors, while xterm gives me only 8. The command would return a negative number if colors were unsupported.

ANSI escape sequences are like a markup language for the terminal. In HTML you work with tags, such as  or , to change how elements look in the document. These tags are mixed with your content, but they’re not visible themselves. Similarly, escape codes won’t show up in the terminal as long as it recognizes them. Otherwise, they’ll appear in the literal form as if you were viewing the source of a website.

As its name implies, a sequence must begin with the non-printable Esc character, whose ASCII value is 27, sometimes denoted as 0x1b in hexadecimal or 033 in octal. You may use Python number literals to quickly verify it’s indeed the same number:

>>>

>>> 27 == 0x1b == 0o33
True

Additionally, you can obtain it with the \e escape sequence in the shell:

The most common ANSI escape sequences take the following form:

ElementDescriptionExample
Esc non-printable escape character \033
[ opening square bracket [
numeric code one or more numbers separated with ; 0
character code uppercase or lowercase letter m

The numeric code can be one or more numbers separated with a semicolon, while the character code is just one letter. Their specific meaning is defined by the ANSI standard. For example, to reset all formatting, you would type one of the following commands, which use the code zero and the letter m:

$ echo -e "\e[0m"
$ echo -e "\x1b[0m"
$ echo -e "\033[0m"

At the other end of the spectrum, you have compound code values. To set foreground and background with RGB channels, given that your terminal supports 24-bit depth, you could provide multiple numbers:

$ echo -e "\e[38;2;0;0;0m\e[48;2;255;255;255mBlack on white\e[0m"

It’s not just text color that you can set with the ANSI escape codes. You can, for example, clear and scroll the terminal window, change its background, move the cursor around, make the text blink or decorate it with an underline.

In Python, you’d probably write a helper function to allow for wrapping arbitrary codes into a sequence:

>>>

>>> def esc(code):
...     return f'\033[{code}m'
...
>>> print(esc('31;1;4') + 'really' + esc(0) + ' important')

This would make the word really appear in red, bold, and underlined font:

Hướng dẫn print bit in python

However, there are higher-level abstractions over ANSI escape codes, such as the mentioned colorama library, as well as tools for building user interfaces in the console.

Building Console User Interfaces

While playing with ANSI escape codes is undeniably a ton of fun, in the real world you’d rather have more abstract building blocks to put together a user interface. There are a few libraries that provide such a high level of control over the terminal, but curses seems to be the most popular choice.

Note: To use the curses library in Windows, you need to install a third-party package:

C:\> pip install windows-curses

That’s because curses isn’t available in the standard library of the Python distribution for Windows.

Primarily, it allows you to think in terms of independent graphical widgets instead of a blob of text. Besides, you get a lot of freedom in expressing your inner artist, because it’s really like painting a blank canvas. The library hides the complexities of having to deal with different terminals. Other than that, it has great support for keyboard events, which might be useful for writing video games.

How about making a retro snake game? Let’s create a Python snake simulator:

Đầu tiên, bạn cần nhập cursesmô-đun. Vì nó sửa đổi trạng thái của một thiết bị đầu cuối đang chạy, điều quan trọng là phải xử lý lỗi và khôi phục trạng thái trước đó một cách duyên dáng. Bạn có thể thực hiện việc này theo cách thủ công, nhưng thư viện đi kèm với một trình bao bọc thuận tiện cho chức năng chính của bạn:

import curses

def main(screen):
    pass

if __name__ == '__main__':
    curses.wrapper(main)

Lưu ý, hàm phải chấp nhận một tham chiếu đến đối tượng màn hình, còn được gọi là stdscr, mà bạn sẽ sử dụng sau này để thiết lập bổ sung.

Nếu bạn chạy chương trình này ngay bây giờ, bạn sẽ không thấy bất kỳ hiệu ứng nào, vì nó sẽ kết thúc ngay lập tức. Tuy nhiên, bạn có thể thêm một khoảng thời gian trễ nhỏ để xem trước:

import time, curses

def main(screen):
    time.sleep(1)

if __name__ == '__main__':
    curses.wrapper(main)

Lần này màn hình hoàn toàn trống trong một giây, nhưng con trỏ vẫn nhấp nháy. Để ẩn nó, chỉ cần gọi một trong các hàm cấu hình được xác định trong mô-đun:

import time, curses

def main(screen):
    curses.curs_set(0)  # Hide the cursor
    time.sleep(1)

if __name__ == '__main__':
    curses.wrapper(main)

Hãy xác định con rắn dưới dạng danh sách các điểm trong tọa độ màn hình:

snake = [(0, i) for i in reversed(range(20))]

Đầu của con rắn luôn là yếu tố đầu tiên trong danh sách, trong khi đuôi là yếu tố cuối cùng. Hình dạng ban đầu của con rắn là nằm ngang, bắt đầu từ góc trên bên trái của màn hình và hướng về bên phải. Trong khi tọa độ y của nó vẫn bằng 0, tọa độ x của nó giảm dần từ đầu đến đuôi.

Để vẽ con rắn, bạn sẽ bắt đầu với phần đầu và sau đó làm theo các đoạn còn lại. Mỗi phân đoạn mang (y, x)tọa độ, vì vậy bạn có thể giải nén chúng:

# Draw the snake
screen.addstr(*snake[0], '@')
for segment in snake[1:]:
    screen.addstr(*segment, '*')

Một lần nữa, nếu bạn chạy mã này ngay bây giờ, nó sẽ không hiển thị bất kỳ thứ gì, vì bạn phải làm mới màn hình một cách rõ ràng sau đó:

import time, curses

def main(screen):
    curses.curs_set(0)  # Hide the cursor

    snake = [(0, i) for i in reversed(range(20))]

    # Draw the snake
    screen.addstr(*snake[0], '@')
    for segment in snake[1:]:
        screen.addstr(*segment, '*')

    screen.refresh()
    time.sleep(1)

if __name__ == '__main__':
    curses.wrapper(main)

Bạn muốn di chuyển con rắn theo một trong bốn hướng, có thể được định nghĩa là vectơ. Cuối cùng, hướng sẽ thay đổi theo cách nhấn phím mũi tên, vì vậy bạn có thể kết nối nó với mã khóa của thư viện:

directions = {
    curses.KEY_UP: (-1, 0),
    curses.KEY_DOWN: (1, 0),
    curses.KEY_LEFT: (0, -1),
    curses.KEY_RIGHT: (0, 1),
}

direction = directions[curses.KEY_RIGHT]

How does a snake move? It turns out that only its head really moves to a new location, while all other segments shift towards it. In each step, almost all segments remain the same, except for the head and the tail. Assuming the snake isn’t growing, you can remove the tail and insert a new head at the beginning of the list:

# Move the snake
snake.pop()
snake.insert(0, tuple(map(sum, zip(snake[0], direction))))

To get the new coordinates of the head, you need to add the direction vector to it. However, adding tuples in Python results in a bigger tuple instead of the algebraic sum of the corresponding vector components. One way to fix this is by using the built-in zip()sum(), and map() functions.

The direction will change on a keystroke, so you need to call .getch() to obtain the pressed key code. However, if the pressed key doesn’t correspond to the arrow keys defined earlier as dictionary keys, the direction won’t change:

# Change direction on arrow keystroke
direction = directions.get(screen.getch(), direction)

By default, however, .getch() is a blocking call that would prevent the snake from moving unless there was a keystroke. Therefore, you need to make the call non-blocking by adding yet another configuration:

def main(screen):
    curses.curs_set(0)    # Hide the cursor
    screen.nodelay(True)  # Don't block I/O calls

You’re almost done, but there’s just one last thing left. If you now loop this code, the snake will appear to be growing instead of moving. That’s because you have to erase the screen explicitly before each iteration.

Finally, this is all you need to play the snake game in Python:

import time, curses

def main(screen):
    curses.curs_set(0)    # Hide the cursor
    screen.nodelay(True)  # Don't block I/O calls

    directions = {
        curses.KEY_UP: (-1, 0),
        curses.KEY_DOWN: (1, 0),
        curses.KEY_LEFT: (0, -1),
        curses.KEY_RIGHT: (0, 1),
    }

    direction = directions[curses.KEY_RIGHT]
    snake = [(0, i) for i in reversed(range(20))]

    while True:
        screen.erase()

        # Draw the snake
        screen.addstr(*snake[0], '@')
        for segment in snake[1:]:
            screen.addstr(*segment, '*')

        # Move the snake
        snake.pop()
        snake.insert(0, tuple(map(sum, zip(snake[0], direction))))

        # Change direction on arrow keystroke
        direction = directions.get(screen.getch(), direction)

        screen.refresh()
        time.sleep(0.1)

if __name__ == '__main__':
    curses.wrapper(main)

This is merely scratching the surface of the possibilities that the curses module opens up. You may use it for game development like this or more business-oriented applications.

Living It Up With Cool Animations

Not only can animations make the user interface more appealing to the eye, but they also improve the overall user experience. When you provide early feedback to the user, for example, they’ll know if your program’s still working or if it’s time to kill it.

To animate text in the terminal, you have to be able to freely move the cursor around. You can do this with one of the tools mentioned previously, that is ANSI escape codes or the curses library. However, I’d like to show you an even simpler way.

If the animation can be constrained to a single line of text, then you might be interested in two special escape character sequences:

  • Carriage return: \r
  • Backspace: \b

The first one moves the cursor to the beginning of the line, whereas the second one moves it only one character to the left. They both work in a non-destructive way without overwriting text that’s already been written.

Let’s take a look at a few examples.

You’ll often want to display some kind of a spinning wheel to indicate a work in progress without knowing exactly how much time’s left to finish:

Many command line tools use this trick while downloading data over the network. You can make a really simple stop motion animation from a sequence of characters that will cycle in a round-robin fashion:

from itertools import cycle
from time import sleep

for frame in cycle(r'-\|/-\|/'):
    print('\r', frame, sep='', end='', flush=True)
    sleep(0.2)

Vòng lặp nhận ký tự tiếp theo để in, sau đó di chuyển con trỏ đến đầu dòng và ghi đè lên bất kỳ ký tự nào trước đó mà không cần thêm dòng mới. Bạn không muốn có thêm khoảng cách giữa các đối số vị trí, vì vậy đối số dấu phân cách phải để trống. Ngoài ra, hãy lưu ý việc sử dụng các chuỗi thô của Python do các ký tự gạch chéo ngược có trong chữ.

Khi bạn biết thời gian còn lại hoặc phần trăm hoàn thành nhiệm vụ, thì bạn có thể hiển thị thanh tiến trình hoạt hình:

Đầu tiên, bạn cần tính toán xem sẽ hiển thị bao nhiêu hashtag và bao nhiêu khoảng trống để chèn. Tiếp theo, bạn xóa dòng và xây dựng thanh từ đầu:

from time import sleep

def progress(percent=0, width=30):
    left = width * percent // 100
    right = width - left
    print('\r[', '#' * left, ' ' * right, ']',
          f' {percent:.0f}%',
          sep='', end='', flush=True)

for i in range(101):
    progress(i)
    sleep(0.1)

Như trước đây, mỗi yêu cầu cập nhật sẽ sửa lại toàn bộ dòng.

Lưu ý: Có một progressbar2thư viện giàu tính năng , cùng với một số công cụ tương tự khác, có thể hiển thị tiến trình theo cách toàn diện hơn nhiều.

Tạo âm thanh với print()

Nếu bạn đủ lớn để nhớ máy tính có loa PC, thì bạn cũng phải nhớ âm thanh bíp đặc biệt của chúng , thường được sử dụng để chỉ ra các vấn đề phần cứng. Họ hầu như không thể tạo ra bất kỳ tiếng động nào hơn thế, nhưng trò chơi điện tử dường như tốt hơn nhiều với nó.

Ngày nay, bạn vẫn có thể tận dụng chiếc loa nhỏ này, nhưng rất có thể máy tính xách tay của bạn không có loa ngoài. Trong trường hợp như vậy, bạn có thể bật tính năng giả lập chuông đầu cuối trong trình bao của mình, để thay vào đó, âm thanh cảnh báo hệ thống sẽ được phát.

Hãy tiếp tục và nhập lệnh này để xem liệu thiết bị đầu cuối của bạn có thể phát âm thanh hay không:

Điều này thường sẽ in văn bản, nhưng -ecờ cho phép giải thích các dấu gạch chéo ngược thoát ra. Như bạn có thể thấy, có một chuỗi thoát chuyên dụng \a, viết tắt của "alert", xuất ra một ký tự chuông đặc biệt . Một số thiết bị đầu cuối phát ra âm thanh bất cứ khi nào họ nhìn thấy nó.

Tương tự, bạn có thể in ký tự này bằng Python. Có lẽ trong một vòng lặp để tạo thành một số loại giai điệu. Mặc dù chỉ là một nốt nhạc, nhưng bạn vẫn có thể thay đổi thời lượng tạm dừng giữa các trường hợp liên tiếp. Đó có vẻ như là một món đồ chơi hoàn hảo để phát lại mã Morse!

Các quy tắc như sau:

  • Các chữ cái được mã hóa bằng một chuỗi các ký hiệu dấu chấm (·) và dấu gạch ngang (-).
  • Một dấu chấm là một đơn vị thời gian.
  • Một dấu gạch ngang là ba đơn vị thời gian.
  • Individual symbols in a letter are spaced one unit of time apart.
  • Symbols of two adjacent letters are spaced three units of time apart.
  • Symbols of two adjacent words are spaced seven units of time apart.

According to those rules, you could be “printing” an SOS signal indefinitely in the following way:

while True:
    dot()
    symbol_space()
    dot()
    symbol_space()
    dot()
    letter_space()
    dash()
    symbol_space()
    dash()
    symbol_space()
    dash()
    letter_space()
    dot()
    symbol_space()
    dot()
    symbol_space()
    dot()
    word_space()

In Python, you can implement it in merely ten lines of code:

from time import sleep

speed = 0.1

def signal(duration, symbol):
    sleep(duration)
    print(symbol, end='', flush=True)

dot = lambda: signal(speed, \a')
dash = lambda: signal(3*speed, '−\a')
symbol_space = lambda: signal(speed, '')
letter_space = lambda: signal(3*speed, '')
word_space = lambda: signal(7*speed, ' ')

Maybe you could even take it one step further and make a command line tool for translating text into Morse code? Either way, I hope you’re having fun with this!

Mocking Python print() in Unit Tests

Nowadays, it’s expected that you ship code that meets high quality standards. If you aspire to become a professional, you must learn how to test your code.

Software testing is especially important in dynamically typed languages, such as Python, which don’t have a compiler to warn you about obvious mistakes. Defects can make their way to the production environment and remain dormant for a long time, until that one day when a branch of code finally gets executed.

Sure, you have linters, type checkers, and other tools for static code analysis to assist you. But they won’t tell you whether your program does what it’s supposed to do on the business level.

So, should you be testing print()? No. After all, it’s a built-in function that must have already gone through a comprehensive suite of tests. What you want to test, though, is whether your code is calling print() at the right time with the expected parameters. That’s known as a behavior.

You can test behaviors by mocking real objects or functions. In this case, you want to mock print() to record and verify its invocations.

Note: You might have heard the terms: dummy, fake, stub, spy, or mock used interchangeably. Some people make a distinction between them, while others don’t.

Martin Fowler explains their differences in a short glossary and collectively calls them test doubles.

Mocking in Python can be done twofold. First, you can take the traditional path of statically-typed languages by employing dependency injection. This may sometimes require you to change the code under test, which isn’t always possible if the code is defined in an external library:

def download(url, log=print):
    log(f'Downloading {url}')
    # ...

This is the same example I used in an earlier section to talk about function composition. It basically allows for substituting print() with a custom function of the same interface. To check if it prints the right message, you have to intercept it by injecting a mocked function:

>>>

>>> def mock_print(message):
...     mock_print.last_message = message
...
>>> download('resource', mock_print)
>>> assert 'Downloading resource' == mock_print.last_message

Calling this mock makes it save the last message in an attribute, which you can inspect later, for example in an assert statement.

In a slightly alternative solution, instead of replacing the entire print() function with a custom wrapper, you could redirect the standard output to an in-memory file-like stream of characters:

>>>

>>> def download(url, stream=None):
...     print(f'Downloading {url}', file=stream)
...     # ...
...
>>> import io
>>> memory_buffer = io.StringIO()
>>> download('app.js', memory_buffer)
>>> download('style.css', memory_buffer)
>>> memory_buffer.getvalue()
'Downloading app.js\nDownloading style.css\n'

This time the function explicitly calls print(), but it exposes its file parameter to the outside world.

However, a more Pythonic way of mocking objects takes advantage of the built-in mock module, which uses a technique called monkey patching. This derogatory name stems from it being a “dirty hack” that you can easily shoot yourself in the foot with. It’s less elegant than dependency injection but definitely quick and convenient.

Note: The mock module got absorbed by the standard library in Python 3, but before that, it was a third-party package. You had to install it separately:

Other than that, you referred to it as mock, whereas in Python 3 it’s part of the unit testing module, so you must import from unittest.mock.

What monkey patching does is alter implementation dynamically at runtime. Such a change is visible globally, so it may have unwanted consequences. In practice, however, patching only affects the code for the duration of test execution.

To mock print() in a test case, you’ll typically use the @patch decorator and specify a target for patching by referring to it with a fully qualified name, that is including the module name:

from unittest.mock import patch

@patch('builtins.print')
def test_print(mock_print):
    print('not a real print')
    mock_print.assert_called_with('not a real print')

This will automatically create the mock for you and inject it to the test function. However, you need to declare that your test function accepts a mock now. The underlying mock object has lots of useful methods and attributes for verifying behavior.

Did you notice anything peculiar about that code snippet?

Despite injecting a mock to the function, you’re not calling it directly, although you could. That injected mock is only used to make assertions afterward and maybe to prepare the context before running the test.

In real life, mocking helps to isolate the code under test by removing dependencies such as a database connection. You rarely call mocks in a test, because that doesn’t make much sense. Rather, it’s other pieces of code that call your mock indirectly without knowing it.

Here’s what that means:

from unittest.mock import patch

def greet(name):
    print(f'Hello, {name}!')

@patch('builtins.print')
def test_greet(mock_print):
    greet('John')
    mock_print.assert_called_with('Hello, John!')

The code under test is a function that prints a greeting. Even though it’s a fairly simple function, you can’t test it easily because it doesn’t return a value. It has a side-effect.

To eliminate that side-effect, you need to mock the dependency out. Patching lets you avoid making changes to the original function, which can remain agnostic about print(). It thinks it’s calling print(), but in reality, it’s calling a mock you’re in total control of.

There are many reasons for testing software. One of them is looking for bugs. When you write tests, you often want to get rid of the print() function, for example, by mocking it away. Paradoxically, however, that same function can help you find bugs during a related process of debugging you’ll read about in the next section.

In this section, you’ll take a look at the available tools for debugging in Python, starting from a humble print() function, through the logging module, to a fully fledged debugger. After reading it, you’ll be able to make an educated decision about which of them is the most suitable in a given situation.

Note: Debugging is the process of looking for the root causes of bugs or defects in software after they’ve been discovered, as well as taking steps to fix them.

The term bug has an amusing story about the origin of its name.

Tracing

Also known as print debugging or caveman debugging, it’s the most basic form of debugging. While a little bit old-fashioned, it’s still powerful and has its uses.

The idea is to follow the path of program execution until it stops abruptly, or gives incorrect results, to identify the exact instruction with a problem. You do that by inserting print statements with words that stand out in carefully chosen places.

Take a look at this example, which manifests a rounding error:

>>>

>>> def average(numbers):
...     print('debug1:', numbers)
...     if len(numbers) > 0:
...         print('debug2:', sum(numbers))
...         return sum(numbers) / len(numbers)
...
>>> 0.1 == average(3*[0.1])
debug1: [0.1, 0.1, 0.1]
debug2: 0.30000000000000004
False

As you can see, the function doesn’t return the expected value of 0.1, but now you know it’s because the sum is a little off. Tracing the state of variables at different steps of the algorithm can give you a hint where the issue is.

This method is simple and intuitive and will work in pretty much every programming language out there. Not to mention, it’s a great exercise in the learning process.

On the other hand, once you master more advanced techniques, it’s hard to go back, because they allow you to find bugs much quicker. Tracing is a laborious manual process, which can let even more errors slip through. The build and deploy cycle takes time. Afterward, you need to remember to meticulously remove all the print() calls you made without accidentally touching the genuine ones.

Besides, it requires you to make changes in the code, which isn’t always possible. Maybe you’re debugging an application running in a remote web server or want to diagnose a problem in a post-mortem fashion. Sometimes you simply don’t have access to the standard output.

That’s precisely where logging shines.

Logging

Let’s pretend for a minute that you’re running an e-commerce website. One day, an angry customer makes a phone call complaining about a failed transaction and saying he lost his money. He claims to have tried purchasing a few items, but in the end, there was some cryptic error that prevented him from finishing that order. Yet, when he checked his bank account, the money was gone.

Bạn thành thật xin lỗi và hoàn tiền, nhưng cũng không muốn điều này tái diễn trong tương lai. Làm thế nào để bạn gỡ lỗi đó? Giá như bạn có một số dấu vết về những gì đã xảy ra, lý tưởng nhất là dưới dạng danh sách các sự kiện theo thứ tự thời gian với bối cảnh của chúng.

Bất cứ khi nào bạn thấy mình đang thực hiện gỡ lỗi in, hãy xem xét chuyển nó thành các thông báo nhật ký vĩnh viễn. Điều này có thể hữu ích trong những tình huống như thế này, khi bạn cần phân tích một vấn đề sau khi nó xảy ra, trong một môi trường mà bạn không có quyền truy cập.

Có những công cụ phức tạp để tổng hợp và tìm kiếm nhật ký, nhưng ở cấp độ cơ bản nhất, bạn có thể coi nhật ký là tệp văn bản. Mỗi dòng chuyển tải thông tin chi tiết về một sự kiện trong hệ thống của bạn. Thông thường, nó sẽ không chứa thông tin nhận dạng cá nhân, tuy nhiên, trong một số trường hợp, nó có thể được pháp luật yêu cầu.

Dưới đây là bảng phân tích của một bản ghi nhật ký điển hình:

[2019-06-14 15:18:34,517][DEBUG][root][MainThread] Customer(id=123) logged out

Như bạn có thể thấy, nó có một dạng cấu trúc. Ngoài thông báo mô tả, có một số trường có thể tùy chỉnh, cung cấp bối cảnh của một sự kiện. Tại đây, bạn có ngày và giờ chính xác, mức nhật ký, tên bộ ghi và tên chuỗi.

Các cấp độ nhật ký cho phép bạn lọc tin nhắn nhanh chóng để giảm nhiễu. Ví dụ: nếu bạn đang tìm kiếm lỗi, bạn không muốn thấy tất cả các cảnh báo hoặc thông báo gỡ lỗi. Việc vô hiệu hóa hoặc kích hoạt các thông báo ở các cấp nhật ký nhất định thông qua cấu hình là điều vô cùng đơn giản mà không cần chạm vào mã.

Với việc ghi nhật ký, bạn có thể giữ các thông báo gỡ lỗi của mình tách biệt với đầu ra tiêu chuẩn. Theo mặc định, tất cả các thông báo nhật ký được chuyển đến luồng lỗi tiêu chuẩn, có thể hiển thị bằng các màu khác nhau một cách thuận tiện. Tuy nhiên, bạn có thể chuyển hướng các thông báo nhật ký đến các tệp riêng biệt, ngay cả đối với các mô-đun riêng lẻ!

Thông thường, việc ghi nhật ký được định cấu hình sai có thể dẫn đến hết dung lượng trên đĩa của máy chủ. Để tránh điều đó, bạn có thể thiết lập xoay vòng nhật ký , điều này sẽ giữ các tệp nhật ký trong một khoảng thời gian cụ thể, chẳng hạn như một tuần hoặc khi chúng đạt đến một kích thước nhất định. Tuy nhiên, luôn luôn là một phương pháp hay để lưu trữ các bản ghi cũ hơn. Một số quy định bắt buộc dữ liệu khách hàng phải được lưu giữ lâu nhất là năm năm!

So với các ngôn ngữ lập trình khác, đăng nhập bằng Python đơn giản hơn, vì loggingmô-đun được đóng gói với thư viện chuẩn. Bạn chỉ cần nhập và định cấu hình nó trong ít nhất hai dòng mã:

import logging
logging.basicConfig(level=logging.DEBUG)

You can call functions defined at the module level, which are hooked to the root logger, but more the common practice is to obtain a dedicated logger for each of your source files:

logging.debug('hello')  # Module-level function

logger = logging.getLogger(__name__)
logger.debug('hello')   # Logger's method

The advantage of using custom loggers is more fine-grain control. They’re usually named after the module they were defined in through the __name__ variable.

Note: There’s a somewhat related warnings module in Python, which can also log messages to the standard error stream. However, it has a narrower spectrum of applications, mostly in library code, whereas client applications should use the logging module.

That said, you can make them work together by calling logging.captureWarnings(True).

One last reason to switch from the print() function to logging is thread safety. In the upcoming section, you’ll see that the former doesn’t play well with multiple threads of execution.

Debugging

The truth is that neither tracing nor logging can be considered real debugging. To do actual debugging, you need a debugger tool, which allows you to do the following:

  • Step through the code interactively.
  • Set breakpoints, including conditional breakpoints.
  • Introspect variables in memory.
  • Evaluate custom expressions at runtime.

A crude debugger that runs in the terminal, unsurprisingly named pdb for “The Python Debugger,” is distributed as part of the standard library. This makes it always available, so it may be your only choice for performing remote debugging. Perhaps that’s a good reason to get familiar with it.

However, it doesn’t come with a graphical interface, so using pdb may be a bit tricky. If you can’t edit the code, you have to run it as a module and pass your script’s location:

$ python -m pdb my_script.py

Otherwise, you can set up a breakpoint directly in the code, which will pause the execution of your script and drop you into the debugger. The old way of doing this required two steps:

>>>

>>> import pdb
>>> pdb.set_trace()
--Return--
> (1)()->None
(Pdb)

This shows up an interactive prompt, which might look intimidating at first. However, you can still type native Python at this point to examine or modify the state of local variables. Apart from that, there’s really only a handful of debugger-specific commands that you want to use for stepping through the code.

Note: It’s customary to put the two instructions for spinning up a debugger on a single line. This requires the use of a semicolon, which is rarely found in Python programs:

import pdb; pdb.set_trace()

While certainly not Pythonic, it stands out as a reminder to remove it after you’re done with debugging.

Since Python 3.7, you can also call the built-in breakpoint() function, which does the same thing, but in a more compact way and with some additional bells and whistles:

def average(numbers):
    if len(numbers) > 0:
        breakpoint()  # Python 3.7+
        return sum(numbers) / len(numbers)

You’re probably going to use a visual debugger integrated with a code editor for the most part. PyCharm has an excellent debugger, which boasts high performance, but you’ll find plenty of alternative IDEs with debuggers, both paid and free of charge.

Debugging isn’t the proverbial silver bullet. Sometimes logging or tracing will be a better solution. For example, defects that are hard to reproduce, such as race conditions, often result from temporal coupling. When you stop at a breakpoint, that little pause in program execution may mask the problem. It’s kind of like the Heisenberg principle: you can’t measure and observe a bug at the same time.

These methods aren’t mutually exclusive. They complement each other.

Thread-Safe Printing

I briefly touched upon the thread safety issue before, recommending logging over the print() function. If you’re still reading this, then you must be comfortable with the concept of threads.

Thread safety means that a piece of code can be safely shared between multiple threads of execution. The simplest strategy for ensuring thread-safety is by sharing immutable objects only. If threads can’t modify an object’s state, then there’s no risk of breaking its consistency.

Another method takes advantage of local memory, which makes each thread receive its own copy of the same object. That way, other threads can’t see the changes made to it in the current thread.

But that doesn’t solve the problem, does it? You often want your threads to cooperate by being able to mutate a shared resource. The most common way of synchronizing concurrent access to such a resource is by locking it. This gives exclusive write access to one or sometimes a few threads at a time.

However, locking is expensive and reduces concurrent throughput, so other means for controlling access have been invented, such as atomic variables or the compare-and-swap algorithm.

Printing isn’t thread-safe in Python. The print() function holds a reference to the standard output, which is a shared global variable. In theory, because there’s no locking, a context switch could happen during a call to sys.stdout.write(), intertwining bits of text from multiple print() calls.

Note: A context switch means that one thread halts its execution, either voluntarily or not, so that another one can take over. This might happen at any moment, even in the middle of a function call.

In practice, however, that doesn’t happen. No matter how hard you try, writing to the standard output seems to be atomic. The only problem that you may sometimes observe is with messed up line breaks:

[Thread-3 A][Thread-2 A][Thread-1 A]

[Thread-3 B][Thread-1 B]


[Thread-1 C][Thread-3 C]

[Thread-2 B]
[Thread-2 C]

To simulate this, you can increase the likelihood of a context switch by making the underlying .write() method go to sleep for a random amount of time. How? By mocking it, which you already know about from an earlier section:

import sys

from time import sleep
from random import random
from threading import current_thread, Thread
from unittest.mock import patch

write = sys.stdout.write

def slow_write(text):
    sleep(random())
    write(text)

def task():
    thread_name = current_thread().name
    for letter in 'ABC':
        print(f'[{thread_name} {letter}]')

with patch('sys.stdout') as mock_stdout:
    mock_stdout.write = slow_write
    for _ in range(3):
        Thread(target=task).start()

First, you need to store the original .write() method in a variable, which you’ll delegate to later. Then you provide your fake implementation, which will take up to one second to execute. Each thread will make a few print() calls with its name and a letter: A, B, and C.

If you read the mocking section before, then you may already have an idea of why printing misbehaves like that. Nonetheless, to make it crystal clear, you can capture values fed into your slow_write() function. You’ll notice that you get a slightly different sequence each time:

[
    '[Thread-3 A]',
    '[Thread-2 A]',
    '[Thread-1 A]',
    '\n',
    '\n',
    '[Thread-3 B]',
    (...)
]

Even though sys.stdout.write() itself is an atomic operation, a single call to the print() function can yield more than one write. For example, line breaks are written separately from the rest of the text, and context switching takes place between those writes.

Note: The atomic nature of the standard output in Python is a byproduct of the Global Interpreter Lock, which applies locking around bytecode instructions. Be aware, however, that many interpreter flavors don’t have the GIL, where multi-threaded printing requires explicit locking.

You can make the newline character become an integral part of the message by handling it manually:

print(f'[{thread_name} {letter}]\n', end='')

This will fix the output:

[Thread-2 A]
[Thread-1 A]
[Thread-3 A]
[Thread-1 B]
[Thread-3 B]
[Thread-2 B]
[Thread-1 C]
[Thread-2 C]
[Thread-3 C]

Notice, however, that the print() function still keeps making a separate call for the empty suffix, which translates to useless sys.stdout.write('') instruction:

[
    '[Thread-2 A]\n',
    '[Thread-1 A]\n',
    '[Thread-3 A]\n',
    '',
    '',
    '',
    '[Thread-1 B]\n',
    (...)
]

A truly thread-safe version of the print() function could look like this:

import threading

lock = threading.Lock()

def thread_safe_print(*args, **kwargs):
    with lock:
        print(*args, **kwargs)

You can put that function in a module and import it elsewhere:

from thread_safe_print import thread_safe_print

def task():
    thread_name = current_thread().name
    for letter in 'ABC':
        thread_safe_print(f'[{thread_name} {letter}]')

Now, despite making two writes per each print() request, only one thread is allowed to interact with the stream, while the rest must wait:

[
    # Lock acquired by Thread-3 
    '[Thread-3 A]',
    '\n',
    # Lock released by Thread-3
    # Lock acquired by Thread-1
    '[Thread-1 B]',
    '\n',
    # Lock released by Thread-1
    (...)
]

I added comments to indicate how the lock is limiting access to the shared resource.

Note: Even in single-threaded code, you might get caught up in a similar situation. Specifically, when you’re printing to the standard output and the standard error streams at the same time. Unless you redirect one or both of them to separate files, they’ll both share a single terminal window.

Conversely, the logging module is thread-safe by design, which is reflected by its ability to display thread names in the formatted message:

>>>

>>> import logging
>>> logging.basicConfig(format='%(threadName)s %(message)s')
>>> logging.error('hello')
MainThread hello

It’s another reason why you might not want to use the print() function all the time.

Python Print Counterparts

By now, you know a lot of what there is to know about print()! The subject, however, wouldn’t be complete without talking about its counterparts a little bit. While print() is about the output, there are functions and libraries for the input.

Built-In

Python comes with a built-in function for accepting input from the user, predictably called input(). It accepts data from the standard input stream, which is usually the keyboard:

>>>

>>> name = input('Enter your name: ')
Enter your name: jdoe
>>> print(name)
jdoe

The function always returns a string, so you might need to parse it accordingly:

try:
    age = int(input('How old are you? '))
except ValueError:
    pass

The prompt parameter is completely optional, so nothing will show if you skip it, but the function will still work:

>>>

>>> x = input()
hello world
>>> print(x)
hello world

Nevertheless, throwing in a descriptive call to action makes the user experience so much better.

Note: To read from the standard input in Python 2, you have to call raw_input() instead, which is yet another built-in. Unfortunately, there’s also a misleadingly named input() function, which does a slightly different thing.

In fact, it also takes the input from the standard stream, but then it tries to evaluate it as if it was Python code. Because that’s a potential security vulnerability, this function was completely removed from Python 3, while raw_input() got renamed to input().

Here’s a quick comparison of the available functions and what they do:

Python 2Python 3
raw_input() input()
input() eval(input())

As you can tell, it’s still possible to simulate the old behavior in Python 3.

Asking the user for a password with input() is a bad idea because it’ll show up in plaintext as they’re typing it. In this case, you should be using the getpass() function instead, which masks typed characters. This function is defined in a module under the same name, which is also available in the standard library:

>>>

>>> from getpass import getpass
>>> password = getpass()
Password: 
>>> print(password)
s3cret

The getpass module has another function for getting the user’s name from an environment variable:

>>>

>>> from getpass import getuser
>>> getuser()
'jdoe'

Python’s built-in functions for handling the standard input are quite limited. At the same time, there are plenty of third-party packages, which offer much more sophisticated tools.

Third-Party

There are external Python packages out there that allow for building complex graphical interfaces specifically to collect data from the user. Some of their features include:

  • Advanced formatting and styling
  • Automated parsing, validation, and sanitization of user data
  • A declarative style of defining layouts
  • Interactive autocompletion
  • Mouse support
  • Predefined widgets such as checklists or menus
  • Searchable history of typed commands
  • Syntax highlighting

Việc chứng minh các công cụ như vậy nằm ngoài phạm vi của bài viết này, nhưng bạn có thể muốn dùng thử chúng. Cá nhân tôi đã biết về một số trong số đó thông qua Python Bytes Podcast . Họ đây rồi:

  • bullet
  • cooked-input
  • prompt_toolkit
  • questionnaire

Tuy nhiên, cần nhắc đến một công cụ dòng lệnh được gọi là công cụ rlwrapbổ sung khả năng chỉnh sửa dòng mạnh mẽ miễn phí cho các tập lệnh Python của bạn. Bạn không phải làm bất cứ điều gì để nó hoạt động!

Giả sử bạn đã viết một giao diện dòng lệnh hiểu ba hướng dẫn, bao gồm một hướng dẫn để thêm số:

print('Type "help", "exit", "add a [b [c ...]]"')
while True:
    command, *arguments = input('~ ').split(' ')
    if len(command) > 0:
        if command.lower() == 'exit':
            break
        elif command.lower() == 'help':
            print('This is help.')
        elif command.lower() == 'add':
            print(sum(map(int, arguments)))
        else:
            print('Unknown command')

Thoạt nhìn, nó có vẻ giống như một lời nhắc điển hình khi bạn chạy nó:

$ python calculator.py
Type "help", "exit", "add a [b [c ...]]"
~ add 1 2 3 4
10
~ aad 2 3
Unknown command
~ exit
$

Nhưng ngay khi bạn mắc lỗi và muốn sửa nó, bạn sẽ thấy không có phím chức năng nào hoạt động như mong đợi. LeftVí dụ: nhấn vào mũi tên, dẫn đến điều này thay vì di chuyển con trỏ trở lại:

$ python calculator.py
Type "help", "exit", "add a [b [c ...]]"
~ aad^[[D

Bây giờ, bạn có thể bọc cùng một tập lệnh bằng rlwraplệnh. Bạn không chỉ làm cho các phím mũi tên hoạt động mà còn có thể tìm kiếm trong lịch sử liên tục của các lệnh tùy chỉnh của mình, sử dụng tính năng tự động hoàn thành và chỉnh sửa dòng bằng các phím tắt:

$ rlwrap python calculator.py
Type "help", "exit", "add a [b [c ...]]"
(reverse-i-search)`a': add 1 2 3 4

Điều đó thật tuyệt phải không?

Phần kết luận

Giờ đây, bạn đã được trang bị một lượng kiến ​​thức về print()hàm trong Python, cũng như nhiều chủ đề xung quanh. Bạn có hiểu biết sâu sắc về nó là gì và nó hoạt động như thế nào, liên quan đến tất cả các yếu tố chính của nó. Nhiều ví dụ đã cung cấp cho bạn cái nhìn sâu sắc về sự phát triển của nó từ Python 2.

Ngoài ra, bạn đã học được cách:

  • Tránh những lỗi thường gặp print()trong Python
  • Xử lý các dòng mới, mã hóa ký tự và bộ đệm
  • Viết văn bản vào tệp
  • Mô phỏng print()chức năng trong các bài kiểm tra đơn vị
  • Xây dựng giao diện người dùng nâng cao trong thiết bị đầu cuối

Bây giờ bạn đã biết tất cả những điều này, bạn có thể tạo các chương trình tương tác giao tiếp với người dùng hoặc sản xuất dữ liệu ở các định dạng tệp phổ biến. Bạn có thể nhanh chóng chẩn đoán các vấn đề trong mã của mình và tự bảo vệ mình khỏi chúng. Cuối cùng nhưng không kém phần quan trọng, bạn biết cách thực hiện trò chơi rắn cổ điển.

Nếu bạn vẫn muốn biết thêm thông tin, có câu hỏi hoặc đơn giản là muốn chia sẻ suy nghĩ của mình, vui lòng liên hệ với chúng tôi trong phần nhận xét bên dưới.


Các khóa học miễn phí qua video:
Lập trình C Java C# SQL Server PHP HTML5-CSS3-JavaScript
« Prev: Python: Dự án Python cho người mới bắt đầu: Thông báo giá Bitcoin
» Next: Python: Sử dụng PyInstaller để dễ dàng phân phối các ứng dụng Python

Copied !!!