11. Chuyến tham quan ngắn gọn về Thư viện Tiêu chuẩn --- Phần II

Chuyến tham quan thứ hai này bao gồm các mô-đun nâng cao hơn hỗ trợ các nhu cầu lập trình chuyên nghiệp. Các mô-đun này hiếm khi xuất hiện trong các tập lệnh nhỏ.

11.1. Định dạng đầu ra

Mô-đun reprlib cung cấp phiên bản repr() được tùy chỉnh để hiển thị viết tắt của các vùng chứa lớn hoặc được lồng sâu:

>>> nhập khẩu reprlib
>>> reprlib.repr(set('supercalifragilisticexpialidocious'))
"{'a', 'c', 'd', 'e', 'f', 'g', ...}"

Mô-đun pprint cung cấp khả năng kiểm soát phức tạp hơn trong việc in cả đối tượng tích hợp và đối tượng do người dùng xác định theo cách mà trình thông dịch có thể đọc được. Khi kết quả dài hơn một dòng, "máy in đẹp" sẽ thêm ngắt dòng và thụt lề để hiển thị rõ hơn cấu trúc dữ liệu:

>>> nhập pprint
>>> t = [[[['đen', 'lục lam'], 'trắng', ['xanh', 'đỏ']], [['đỏ tươi',
... 'vàng'], 'xanh']]]
...
>>> pprint.pprint(t, width=30)
[[[['đen', 'lục lam'],
   'trắng',
   ['xanh', 'đỏ']],
  [['đỏ tươi', 'vàng'],
   'màu xanh']]]

Mô-đun textwrap định dạng các đoạn văn bản để vừa với chiều rộng màn hình nhất định

>>> nhập gói văn bản
>>> doc = """Phương thức quấn() cũng giống như fill() ngoại trừ việc nó trả về
... một danh sách các chuỗi thay vì một chuỗi lớn có dòng mới để phân tách
... những đường gói."""
...
>>> print(textwrap.fill(doc, width=40))
Phương thức quấn() giống như fill()
ngoại trừ việc nó trả về một danh sách các chuỗi
thay vì một chuỗi lớn với dòng mới
để tách các dòng được bọc.

Mô-đun locale truy cập cơ sở dữ liệu về các định dạng dữ liệu cụ thể về văn hóa. Thuộc tính nhóm của hàm định dạng miền địa phương cung cấp cách định dạng số trực tiếp bằng dấu phân cách nhóm

>>> nhập ngôn ngữ
>>> locale.setlocale(locale.LC_ALL, 'English_United States.1252')
'Tiếng Anh_Hoa Kỳ.1252'
>>> conv = locale.localeconv() # get ánh xạ các quy ước
>>> x = 1234567.8
>>> locale.format_string("%d", x, grouping=True)
'1.234.567'
>>> locale.format_string("%s%.*f", (conv['currency_symbol'],
... conv['frac_digits'], x), grouping=True)
'$1.234.567,80'

11.2. tạo khuôn mẫu

Mô-đun string bao gồm lớp Template linh hoạt với cú pháp đơn giản hóa phù hợp để người dùng cuối chỉnh sửa. Điều này cho phép người dùng tùy chỉnh ứng dụng của họ mà không cần phải thay đổi ứng dụng.

Định dạng này sử dụng tên giữ chỗ được tạo bởi $ với mã định danh Python hợp lệ (ký tự chữ và số và dấu gạch dưới). Bao quanh phần giữ chỗ bằng các dấu ngoặc nhọn cho phép nó được theo sau bởi nhiều chữ cái chữ và số hơn mà không có khoảng trắng ở giữa. Viết $$ sẽ tạo ra một $ thoát duy nhất:

>>> từ Mẫu nhập chuỗi
>>> t = Mẫu('${village}dân gian gửi $$10 tới $nguyên nhân.')
>>> t.substitute(village='Nottingham', Cause='quỹ mương')
'Nottinghamfolk gửi 10 đô la vào quỹ mương.'

Phương thức substitute() tăng KeyError khi trình giữ chỗ không được cung cấp trong từ điển hoặc đối số từ khóa. Đối với các ứng dụng kiểu trộn thư, dữ liệu do người dùng cung cấp có thể không đầy đủ và phương pháp safe_substitute() có thể phù hợp hơn --- nó sẽ giữ nguyên phần giữ chỗ nếu dữ liệu bị thiếu:

>>> t = Mẫu('Trả lại $item cho $owner.')
>>> d = dict(item='con én không mang hàng')
>>> t.substitute(d)
Traceback (cuộc gọi gần đây nhất):
  ...
KeyError: 'chủ sở hữu'
>>> t.safe_substitute(d)
'Trả lại con én không mang hàng cho chủ sở hữu $.'

Các lớp con mẫu có thể chỉ định một dấu phân cách tùy chỉnh. Ví dụ: tiện ích đổi tên hàng loạt cho trình duyệt ảnh có thể chọn sử dụng ký hiệu phần trăm cho phần giữ chỗ như ngày hiện tại, số thứ tự hình ảnh hoặc định dạng tệp:

>>> nhập thời gian, os.path
>>> file ảnh = ['img_1074.jpg', 'img_1076.jpg', 'img_1077.jpg']
>>> lớp BatchRename(Mẫu):
... dấu phân cách = '%'
...
>>> fmt = input('Nhập kiểu đổi tên (%d-date %n-seqnum %f-format): ')
Nhập kiểu đổi tên (%d-date %n-seqnum %f-format): Ashley_%n%f

>>> t = BatchRename(fmt)
>>> ngày = time.strftime('%d%b%y')
>>> đối với i, tên tệp trong liệt  (tệp ảnh):
... base, ext = os.path.splitext(filename)
... newname = t.substitute(d=date, n=i, f=ext)
... print('{0} --> {1}'.format(filename, newname))

img_1074.jpg --> Ashley_0.jpg
img_1076.jpg --> Ashley_1.jpg
img_1077.jpg --> Ashley_2.jpg

Một ứng dụng khác để tạo khuôn mẫu là tách logic chương trình khỏi các chi tiết của nhiều định dạng đầu ra. Điều này cho phép thay thế các mẫu tùy chỉnh cho các tệp XML, báo cáo văn bản thuần túy và báo cáo web HTML.

11.3. Làm việc với bố cục bản ghi dữ liệu nhị phân

Mô-đun struct cung cấp các hàm pack()unpack() để làm việc với các định dạng bản ghi nhị phân có độ dài thay đổi. Ví dụ sau đây cho thấy cách lặp qua thông tin tiêu đề trong tệp ZIP mà không cần sử dụng mô-đun zipfile. Mã gói "H""I" tương ứng đại diện cho các số không dấu hai và bốn byte. Zz006zz chỉ ra rằng chúng có kích thước tiêu chuẩn và theo thứ tự byte cuối nhỏ

nhập cấu trúc

với open('myfile.zip', 'rb')  f:
    dữ liệu = f.read()

bắt đầu = 0
cho i trong phạm vi (3): # show 3 tiêu đề tệp đầu tiên
    bắt đầu += 14
    field = struct.unpack('<IIIHH', data[start:start+16])
    crc32, comp_size, uncomp_size, tên tệp, extra_size = trường

    bắt đầu += 16
    tên tệp = dữ liệu [bắt đầu:bắt đầu+tên tệp]
    bắt đầu += kích thước tên tập tin
    thêm = dữ liệu[bắt đầu:bắt đầu+extra_size]
    print(tên file, hex(crc32), comp_size, uncomp_size)

    bắt đầu += extra_size + comp_size # skip vào tiêu đề tiếp theo

11.4. Đa luồng

Phân luồng là một kỹ thuật để tách các tác vụ không phụ thuộc tuần tự. Các luồng có thể được sử dụng để cải thiện khả năng phản hồi của các ứng dụng chấp nhận đầu vào của người dùng trong khi các tác vụ khác chạy ở chế độ nền. Một trường hợp sử dụng liên quan đang chạy I/O song song với các tính toán trong một luồng khác.

Đoạn mã sau cho thấy mô-đun threading cấp cao có thể chạy các tác vụ ở chế độ nền như thế nào trong khi chương trình chính tiếp tục chạy:

nhập luồng, zipfile

lớp AsyncZip(luồng.Thread):
    def __init__(self, infile, outfile):
        threading.Thread.__init__(self)
        self.infile = infile
        self.outfile = outfile

    chắc chắn chạy (tự):
        f = zipfile.ZipFile(self.outfile, 'w', zipfile.ZIP_DEFLATED)
        f.write(self.infile)
        f.close()
        print('Đã hoàn thành zip nền của:', self.infile)

nền = AsyncZip('mydata.txt', 'myarchive.zip')
nền.start()
print('Chương trình chính tiếp tục chạy ở nền trước.')

Background.join() # Wait để hoàn thành tác vụ nền
print('Chương trình chính đợi cho đến khi chạy xong nền.')

Thách thức chính của các ứng dụng đa luồng là phối hợp các luồng chia sẻ dữ liệu hoặc các tài nguyên khác. Để đạt được mục đích đó, mô-đun luồng cung cấp một số nguyên tắc đồng bộ hóa bao gồm khóa, sự kiện, biến điều kiện và ngữ nghĩa.

Mặc dù những công cụ đó rất mạnh mẽ nhưng những lỗi thiết kế nhỏ có thể dẫn đến những vấn đề khó tái hiện. Vì vậy, cách tiếp cận ưa thích để phối hợp tác vụ là tập trung tất cả quyền truy cập vào tài nguyên trong một luồng duy nhất và sau đó sử dụng mô-đun queue để cung cấp cho luồng đó các yêu cầu từ các luồng khác. Các ứng dụng sử dụng đối tượng Queue để liên lạc và phối hợp giữa các luồng sẽ dễ thiết kế hơn, dễ đọc hơn và đáng tin cậy hơn.

11.5. Ghi nhật ký

Mô-đun logging cung cấp hệ thống ghi nhật ký linh hoạt và đầy đủ tính năng. Đơn giản nhất, thông điệp tường trình được gửi đến một tệp hoặc tới sys.stderr:

nhập nhật 
logging.debug('Thông tin gỡ lỗi')
logging.info('Thông báo thông tin')
logging.warning('Cảnh báo:không tìm thấy tập tin cấu hình %s', 'server.conf')
logging.error('Đã xảy ra lỗi')
logging.cript('Lỗi nghiêm trọng -- đang tắt')

Điều này tạo ra đầu ra sau:

WARNING:root:Cảnh báo:không tìm thấy tệp cấu hình server.conf
ERROR:root:Đã xảy ra lỗi
CRITICAL:root:Lỗi nghiêm trọng -- đang tắt

Theo mặc định, các thông báo thông tin và gỡ lỗi bị chặn và đầu ra được gửi tới lỗi tiêu chuẩn. Các tùy chọn đầu ra khác bao gồm định tuyến tin nhắn qua email, datagram, ổ cắm hoặc tới Máy chủ HTTP. Các bộ lọc mới có thể chọn định tuyến khác nhau dựa trên mức độ ưu tiên của tin nhắn: DEBUG, INFO, WARNING, ERRORCRITICAL.

Hệ thống ghi nhật ký có thể được cấu hình trực tiếp từ Python hoặc có thể được tải từ tệp cấu hình có thể chỉnh sửa của người dùng để ghi nhật ký tùy chỉnh mà không làm thay đổi ứng dụng.

11.6. Tài liệu tham khảo yếu

Python thực hiện quản lý bộ nhớ tự động (đếm tham chiếu cho hầu hết các đối tượng và garbage collection để loại bỏ chu kỳ). Bộ nhớ được giải phóng ngay sau khi tham chiếu cuối cùng đến nó bị loại bỏ.

Cách tiếp cận này hoạt động tốt với hầu hết các ứng dụng nhưng đôi khi chỉ cần theo dõi các đối tượng miễn là chúng đang được sử dụng bởi thứ khác. Thật không may, chỉ cần theo dõi chúng sẽ tạo ra một tham chiếu khiến chúng tồn tại vĩnh viễn. Mô-đun weakref cung cấp các công cụ để theo dõi đối tượng mà không cần tạo tham chiếu. Khi đối tượng không còn cần thiết nữa, nó sẽ tự động bị xóa khỏi bảng yếu và một cuộc gọi lại được kích hoạt cho các đối tượng yếu. Các ứng dụng điển hình bao gồm các đối tượng bộ nhớ đệm tốn nhiều chi phí để tạo:

>>> nhập điểm yếu, gc
>>> lớp A:
... def __init__(tự, giá trị):
... self.value = giá trị
... def __repr__(self):
... trả về str(self.value)
...
>>> a = A(10) # create một tài liệu tham khảo
>>> d = yếuref.WeakValueDictionary()
>>> d['primary'] = a # does không tạo tham chiếu
>>> d['primary'] # fetch đối tượng nếu nó vẫn còn sống
10
>>> del a # remove một tài liệu tham khảo
>>> gc.collect() # run thu gom rác ngay
0
>>> d['primary'] # entry đã tự động bị xóa
Traceback (cuộc gọi gần đây nhất):
  Tệp "<stdin>", dòng 1, trong <module>
    d['primary'] # entry đã tự động bị xóa
  Tệp "C:/python314/lib/weakref.py", dòng 46, trong __getitem__
    o = self.data[key]()
KeyError: 'chính'

11.7. Công cụ làm việc với danh sách

Nhiều nhu cầu về cấu trúc dữ liệu có thể được đáp ứng với kiểu danh sách tích hợp sẵn. Tuy nhiên, đôi khi cần có những triển khai thay thế với những đánh đổi về hiệu suất khác nhau.

Mô-đun array cung cấp một đối tượng array giống như một danh sách chỉ lưu trữ dữ liệu đồng nhất và lưu trữ nó gọn gàng hơn. Ví dụ sau đây cho thấy một dãy số được lưu trữ dưới dạng số nhị phân không dấu hai byte (mã kiểu "H") thay vì 16 byte thông thường cho mỗi mục nhập đối với danh sách thông thường của các đối tượng int Python:

>>> từ mảng nhập mảng
>>> a = mảng('H', [4000, 10, 700, 22222])
>>> tổng(a)
26932
>>> a[1:3]
mảng('H', [10, 700])

Mô-đun collections cung cấp một đối tượng deque giống như một danh sách với phần bổ sung và bật lên nhanh hơn từ phía bên trái nhưng tra cứu chậm hơn ở giữa. Các đối tượng này rất phù hợp để triển khai hàng đợi và tìm kiếm cây đầu tiên theo chiều rộng:

>>> từ deque nhập bộ sưu tập
>>> d = deque(["task1", "task2", "task3"])
>>> d.append("task4")
>>> print("Xử lý", d.popleft())
Xử lý task1
chưa được tìm kiếm = deque([starting_node])
def breadth_first_search(chưa được tìm kiếm):
    nút = unsearched.popleft()
    cho m trong gen_moves(nút):
        nếu là_mục tiêu (m):
            trả lại tôi
        unsearched.append(m)

Ngoài việc triển khai danh sách thay thế, thư viện còn cung cấp các công cụ khác như mô-đun bisect với các chức năng thao tác danh sách được sắp xếp

>>> nhập chia đôi
>>> điểm = [(100, 'perl'), (200, 'tcl'), (400, 'lua'), (500, 'python')]
>>> bisect.insort(điểm, (300, 'ruby'))
>>> điểm số
[(100, 'perl'), (200, 'tcl'), (300, 'ruby'), (400, 'lua'), (500, 'python')]

Mô-đun heapq cung cấp các chức năng để triển khai các vùng dữ liệu dựa trên danh sách thông thường. Mục nhập có giá trị thấp nhất luôn được giữ ở vị trí 0. Điều này hữu ích cho các ứng dụng truy cập nhiều lần vào phần tử nhỏ nhất nhưng không muốn chạy sắp xếp danh sách đầy đủ

>>> từ nhập heapq heapify, heappop, heapush
>>> dữ liệu = [1, 3, 5, 7, 9, 2, 4, 6, 8, 0]
>>> heapify(data) # rearrange danh sách theo thứ tự heap
>>> heappush(data, -5) # add một mục mới
>>> [heappop(data) for i in range(3)] # fetch ba mục nhỏ nhất
[-5, 0, 1]

11.8. Số học dấu phẩy động thập phân

Mô-đun decimal cung cấp kiểu dữ liệu Decimal cho số học dấu phẩy động thập phân. So với việc triển khai dấu phẩy động nhị phân tích hợp sẵn trong float, lớp này đặc biệt hữu ích cho

  • các ứng dụng tài chính và các mục đích sử dụng khác yêu cầu biểu diễn số thập phân chính xác,

  • kiểm soát độ chính xác,

  • kiểm soát việc làm tròn để đáp ứng các yêu cầu pháp lý hoặc quy định,

  • theo dõi các vị trí thập phân quan trọng, hoặc

  • các ứng dụng mà người dùng mong đợi kết quả khớp với các phép tính được thực hiện bằng tay.

Ví dụ: tính thuế 5% đối với khoản phí điện thoại 70 xu sẽ cho kết quả khác nhau về dấu phẩy động thập phân và dấu phẩy động nhị phân. Sự khác biệt trở nên đáng kể nếu kết quả được làm tròn đến xu gần nhất:

>>> từ nhập thập phân *
>>> round(Thập phân('0.70') * Thập phân('1.05'), 2)
Thập phân('0.74')
>>> vòng(.70 * 1.05, 2)
0,73

Kết quả Decimal giữ một số 0 ở cuối, tự động suy ra ý nghĩa bốn vị trí từ các số bị nhân có ý nghĩa hai vị trí. Số thập phân tái tạo toán học được thực hiện bằng tay và tránh các vấn đề có thể phát sinh khi dấu phẩy động nhị phân không thể biểu diễn chính xác số lượng thập phân.

Biểu diễn chính xác cho phép lớp Decimal thực hiện các phép tính modulo và kiểm tra đẳng thức không phù hợp với dấu phẩy động nhị phân:

>>> Số thập phân('1.00') % Số thập phân('.10')
Thập phân('0.00')
>>> 1,00 % 0,10
0,09999999999999995

>>> sum([Decimal('0.1')]*10) == Decimal('1.0')
đúng
>>> 0,1 + 0,1 + 0,1 + 0,1 + 0,1 + 0,1 + 0,1 + 0,1 + 0,1 + 0,1 == 1,0
sai

Mô-đun decimal cung cấp số học với độ chính xác cao nhất có thể nếu cần:

>>> getcontext().prec = 36
>>> Thập phân(1) / Thập phân(7)
Thập phân('0.142857142857142857142857142857142857')