Bối cảnh
Giả sử tôi có mã Python sau:
def example_function(numbers, n_iters):
sum_all = 0
for number in numbers:
for _ in range(n_iters):
number = halve(number)
sum_all += number
return sum_all
ns = [1, 3, 12]
print(example_function(ns, 3))
example_functionở đây chỉ đơn giản là đi qua từng yếu tố trong nsdanh sách và giảm một nửa chúng 3 lần, trong khi tích lũy kết quả. Đầu ra của việc chạy tập lệnh này chỉ đơn giản là:
2.0
Vì 1 / (2 ^ 3) * (1 + 3 + 12) = 2.
Bây giờ, giả sử rằng (vì bất kỳ lý do nào, có thể gỡ lỗi hoặc ghi nhật ký), tôi muốn hiển thị một số loại thông tin về các bước trung gian mà việc example_functionnày đang thực hiện. Có lẽ sau đó tôi sẽ viết lại hàm này thành một cái gì đó như thế này:
def example_function(numbers, n_iters):
sum_all = 0
for number in numbers:
print('Processing number', number)
for i_iter in range(n_iters):
number = number/2
print(number)
sum_all += number
print('sum_all:', sum_all)
return sum_all
mà bây giờ, khi được gọi với cùng các đối số như trước, sẽ đưa ra các kết quả sau:
Processing number 1
0.5
0.25
0.125
sum_all: 0.125
Processing number 3
1.5
0.75
0.375
sum_all: 0.5
Processing number 12
6.0
3.0
1.5
sum_all: 2.0
Điều này đạt được chính xác những gì tôi dự định. Tuy nhiên, điều này đi ngược lại một chút so với nguyên tắc rằng một hàm chỉ nên làm một việc, và bây giờ mã cho example_functiondài hơn và phức tạp hơn. Đối với một hàm đơn giản như vậy thì đây không phải là vấn đề, nhưng trong ngữ cảnh của tôi, tôi có các hàm khá phức tạp gọi nhau và các câu lệnh in thường bao gồm các bước phức tạp hơn được hiển thị ở đây, dẫn đến sự gia tăng đáng kể về độ phức tạp của mã của tôi (đối với một trong số các hàm của tôi có nhiều dòng mã liên quan đến ghi nhật ký hơn các dòng liên quan đến mục đích thực tế của nó!).
Hơn nữa, nếu sau này tôi quyết định rằng tôi không muốn bất kỳ câu lệnh in nào trong chức năng của mình nữa, tôi sẽ phải thực hiện example_functionvà xóa tất cả các printcâu lệnh theo cách thủ công, cùng với bất kỳ biến nào liên quan đến chức năng này, một quá trình vừa tẻ nhạt vừa có lỗi -dễ bị.
Tình hình thậm chí còn tồi tệ hơn nếu tôi luôn có khả năng in hoặc không in trong khi thực thi chức năng, dẫn đến tôi phải khai báo hai chức năng cực kỳ giống nhau (một có printcâu lệnh, một không có), rất tệ cho việc duy trì, hoặc để định nghĩa một cái gì đó như:
def example_function(numbers, n_iters, debug_mode=False):
sum_all = 0
for number in numbers:
if debug_mode:
print('Processing number', number)
for i_iter in range(n_iters):
number = number/2
if debug_mode:
print(number)
sum_all += number
if debug_mode:
print('sum_all:', sum_all)
return sum_all
dẫn đến một chức năng cồng kềnh và (hy vọng) phức tạp không cần thiết, ngay cả trong trường hợp đơn giản của chúng tôi example_function.
Câu hỏi
Có cách nào để "tách" chức năng in ra khỏi chức năng ban đầu example_functionkhông?
Tổng quát hơn, có một cách pythonic để tách rời chức năng tùy chọn khỏi mục đích chính của chức năng?
Những gì tôi đã cố gắng cho đến nay:
Giải pháp tôi đã tìm thấy tại thời điểm này là sử dụng các cuộc gọi lại cho việc tách rời. Chẳng hạn, người ta có thể viết lại example_functionnhư thế này:
def example_function(numbers, n_iters, callback=None):
sum_all = 0
for number in numbers:
for i_iter in range(n_iters):
number = number/2
if callback is not None:
callback(locals())
sum_all += number
return sum_all
và sau đó xác định chức năng gọi lại thực hiện bất kỳ chức năng in nào tôi muốn:
def print_callback(locals):
print(locals['number'])
và gọi example_functionnhư thế này:
ns = [1, 3, 12]
example_function(ns, 3, callback=print_callback)
mà sau đó đầu ra:
0.5
0.25
0.125
1.5
0.75
0.375
6.0
3.0
1.5
2.0
Điều này tách thành công chức năng in từ chức năng cơ bản của example_function. Tuy nhiên, vấn đề chính của phương pháp này là chức năng gọi lại chỉ có thể được chạy ở một phần cụ thể của example_function(trong trường hợp này ngay sau khi giảm một nửa số hiện tại) và tất cả việc in phải diễn ra chính xác ở đó. Điều này đôi khi buộc thiết kế chức năng gọi lại khá phức tạp (và làm cho một số hành vi không thể đạt được).
Chẳng hạn, nếu người ta muốn đạt được chính xác kiểu in giống như tôi đã làm trong phần trước của câu hỏi (cho biết số nào đang được xử lý, cùng với các phân nửa tương ứng của nó) thì cuộc gọi lại kết quả sẽ là:
def complicated_callback(locals):
i_iter = locals['i_iter']
number = locals['number']
if i_iter == 0:
print('Processing number', number*2)
print(number)
if i_iter == locals['n_iters']-1:
print('sum_all:', locals['sum_all']+number)
dẫn đến kết quả chính xác như trước:
Processing number 1.0
0.5
0.25
0.125
sum_all: 0.125
Processing number 3.0
1.5
0.75
0.375
sum_all: 0.5
Processing number 12.0
6.0
3.0
1.5
sum_all: 2.0
nhưng là một nỗi đau để viết, đọc và gỡ lỗi.
loggingmô-đun sẽ giúp gì ở đây. Mặc dù câu hỏi của tôi sử dụng các printcâu lệnh khi thiết lập bối cảnh, nhưng tôi thực sự đang tìm kiếm một giải pháp làm thế nào để tách rời bất kỳ loại chức năng tùy chọn nào khỏi mục đích chính của chức năng. Ví dụ, có thể tôi muốn một hàm để vẽ đồ thị khi nó chạy. Trong trường hợp đó tôi tin rằng loggingmô-đun thậm chí sẽ không được áp dụng.
loggingchứng minh), nhưng không thể phân tách mã tùy ý.
loggingmô-đun python