Hướng dẫn write better python scripts - viết các tập lệnh python tốt hơn

Cập nhật lần cuối vào ngày 21 tháng 6 năm 2022

Chúng tôi viết một chương trình để giải quyết vấn đề hoặc tạo một công cụ mà chúng tôi có thể liên tục giải quyết một vấn đề tương tự. Đối với cái sau, không thể tránh khỏi việc chúng tôi quay lại để xem lại chương trình chúng tôi đã viết, hoặc một người khác đang sử dụng lại chương trình chúng tôi viết. Cũng có một cơ hội mà chúng tôi sẽ gặp dữ liệu mà chúng tôi đã thấy trước khi chúng tôi viết chương trình của mình. Rốt cuộc, chúng tôi vẫn muốn chương trình của chúng tôi hoạt động. Có một số kỹ thuật và tâm lý chúng ta có thể sử dụng để viết chương trình của mình để làm cho mã của chúng ta mạnh mẽ hơn.work. There are some techniques and mentalities we can use in writing our program to make our code more robust.

Sau khi hoàn thành hướng dẫn này, bạn sẽ học

  • Cách chuẩn bị mã của bạn cho tình huống bất ngờ
  • Cách đưa ra một tín hiệu phù hợp cho các tình huống mà mã của bạn không thể xử lý
  • Các thực tiễn tốt để viết một chương trình mạnh mẽ hơn là gì

Bắt đầu dự án của bạn với cuốn sách mới Python for Machine Learning, bao gồm các hướng dẫn từng bước và các tệp mã nguồn Python cho tất cả các ví dụ. with my new book Python for Machine Learning, including step-by-step tutorials and the Python source code files for all examples.

Bắt đầu nào!

Kỹ thuật để viết Python Codephoto tốt hơn của Anna Shvets. Một số quyền được bảo lưu.
Photo by Anna Shvets. Some rights reserved.

Tổng quan

Hướng dẫn này được chia thành ba phần; họ đang:

  • Lập trình vệ sinh và quyết đoán
  • Bảo vệ đường ray và lập trình tấn công
  • Thực hành tốt để tránh lỗi

Lập trình vệ sinh và quyết đoán

Khi chúng ta viết một hàm trong Python, chúng ta thường đưa ra một số đối số và trả về một số giá trị. Rốt cuộc, đây là những gì một chức năng được cho là. Vì Python là một ngôn ngữ gõ vịt, thật dễ dàng để thấy một hàm chấp nhận các số được gọi với các chuỗi. Ví dụ:

def add [a, b]:add[a,b]:

    returna+breturna+b

c=add["one","two"]=add["one","two"]

Mã này hoạt động hoàn toàn tốt, vì toán tử & nbsp; ________ 0 trong chuỗi Python có nghĩa là nối. Do đó không có lỗi cú pháp; Nó không chỉ là những gì chúng tôi dự định làm với chức năng.

Đây không phải là một vấn đề lớn, nhưng nếu chức năng dài, chúng ta không nên học có điều gì đó không ổn ở giai đoạn sau. Ví dụ, chương trình của chúng tôi đã thất bại và chấm dứt vì một sai lầm như thế này chỉ sau khi dành hàng giờ để đào tạo mô hình học máy và lãng phí hàng giờ thời gian chờ đợi. Sẽ tốt hơn nếu chúng ta có thể chủ động xác minh những gì chúng ta giả định. Đó cũng là một thực tế tốt để giúp chúng tôi giao tiếp với những người khác đọc mã của chúng tôi những gì chúng tôi mong đợi trong mã.

Một điều phổ biến mà một mã khá dài sẽ làm là & nbsp; vệ sinh đầu vào. Ví dụ: chúng ta có thể viết lại chức năng của mình ở trên như sau:sanitize the input. For example, we may rewrite our function above as the following:

def add [a, b]:add[a,b]:

    ifnotisinstance[a,[int,float]]ornotisinstance[b,[int,float]]:ifnotisinstance[a,[int,float]]ornot isinstance[b,[int,float]]:

Mã này hoạt động hoàn toàn tốt, vì toán tử & nbsp; ________ 0 trong chuỗi Python có nghĩa là nối. Do đó không có lỗi cú pháp; Nó không chỉ là những gì chúng tôi dự định làm với chức năng.raise ValueError["Input must be numbers"]

    returna+breturna+ b

Đây không phải là một vấn đề lớn, nhưng nếu chức năng dài, chúng ta không nên học có điều gì đó không ổn ở giai đoạn sau. Ví dụ, chương trình của chúng tôi đã thất bại và chấm dứt vì một sai lầm như thế này chỉ sau khi dành hàng giờ để đào tạo mô hình học máy và lãng phí hàng giờ thời gian chờ đợi. Sẽ tốt hơn nếu chúng ta có thể chủ động xác minh những gì chúng ta giả định. Đó cũng là một thực tế tốt để giúp chúng tôi giao tiếp với những người khác đọc mã của chúng tôi những gì chúng tôi mong đợi trong mã.

def add [a, b]:add[a,b]:

    try:try:

        a=float[a]a=float[a]

        b=float[b]b =float[b]

Mã này hoạt động hoàn toàn tốt, vì toán tử & nbsp; ________ 0 trong chuỗi Python có nghĩa là nối. Do đó không có lỗi cú pháp; Nó không chỉ là những gì chúng tôi dự định làm với chức năng.except ValueError:

Mã này hoạt động hoàn toàn tốt, vì toán tử & nbsp; ________ 0 trong chuỗi Python có nghĩa là nối. Do đó không có lỗi cú pháp; Nó không chỉ là những gì chúng tôi dự định làm với chức năng.raise ValueError["Input must be numbers"]

    returna+breturna+ b

Đây không phải là một vấn đề lớn, nhưng nếu chức năng dài, chúng ta không nên học có điều gì đó không ổn ở giai đoạn sau. Ví dụ, chương trình của chúng tôi đã thất bại và chấm dứt vì một sai lầm như thế này chỉ sau khi dành hàng giờ để đào tạo mô hình học máy và lãng phí hàng giờ thời gian chờ đợi. Sẽ tốt hơn nếu chúng ta có thể chủ động xác minh những gì chúng ta giả định. Đó cũng là một thực tế tốt để giúp chúng tôi giao tiếp với những người khác đọc mã của chúng tôi những gì chúng tôi mong đợi trong mã.

Một điều phổ biến mà một mã khá dài sẽ làm là & nbsp; vệ sinh đầu vào. Ví dụ: chúng ta có thể viết lại chức năng của mình ở trên như sau:range[a,b=None,c=None]:

    ifcisNone:ifcisNone:

        c=1c =1

    ifbisNone:ifbisNone:

        b=ab=a

        a=0a=0

    values=[]values =[]

    n=an=a

    whilenwhilen=len[arr]assert len[newarr]*2>=len[arr]

    returnnewarrreturnnewarr

Mặc dù chúng tôi phát triển chức năng này, chúng tôi không chắc chắn thuật toán của chúng tôi là chính xác. Có rất nhiều thứ cần kiểm tra, nhưng ở đây chúng tôi muốn chắc chắn rằng nếu chúng tôi trích xuất mọi mục được chỉ số từ đầu vào, thì nó sẽ ít nhất một nửa chiều dài của mảng đầu vào. Khi chúng tôi cố gắng tối ưu hóa thuật toán hoặc đánh bóng mã, điều kiện này không được vô hiệu. Chúng tôi giữ tuyên bố range[]7 tại các vị trí chiến lược để đảm bảo rằng chúng tôi đã phá vỡ mã của chúng tôi sau khi sửa đổi. Bạn có thể coi đây là một cách khác nhau để kiểm tra đơn vị. Nhưng thông thường, chúng tôi gọi đó là thử nghiệm đơn vị khi chúng tôi kiểm tra các chức năng của chúng tôi, đầu vào và đầu ra phù hợp với những gì chúng tôi mong đợi. Sử dụng ________ 17 & nbsp; theo cách này là kiểm tra các bước bên trong một hàm.

Nếu chúng ta viết một thuật toán phức tạp, rất hữu ích khi thêm ____ 17 & nbsp; để kiểm tra & nbsp; vòng lặp bất biến, cụ thể là các điều kiện mà một vòng lặp nên duy trì. Xem xét mã tìm kiếm nhị phân sau đây trong một mảng được sắp xếp:loop invariants, namely, the conditions that a loop should uphold. Consider the following code of binary search in a sorted array:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

def Binary_Search [mảng, mục tiêu]:binary_search[array,target]:

& nbsp; & nbsp; & nbsp; & nbsp; "" "" Tìm kiếm nhị phân trên mảng cho mục tiêu"""Binary search on array for target

    Args:

& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; mảng

& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbs

    Returns:

& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; index n trên mảng sao

& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp;

    """""

    s,e=0,len[array]s,e=0, len[array]

    whileswhiles target:

            e=me=m

& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp;elif array[m]0else0return xifx>0else0

@register["sigmoid"]register["sigmoid"]

def sigmoid [x]:sigmoid[x]:

    return1/[1+math.exp[-x]]return 1/[1+math.exp[-x]]

def activate [x, funcName]:activate[x,funcname]:

& nbsp; & nbsp; & nbsp; & nbsp; iffuncname notinregistry:iffuncname not inREGISTRY:

& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp;raise NotImplementedError[f"Function {funcname} is not implemented"]

    else:else:

        func=REGISTRY[funcname]func= REGISTRY[funcname]

        returnfunc[x]returnfunc[x]

print[activate[1.23,"relu"]][activate[1.23,"relu"]]

print[activate[1.23,"sigmoid"]][activate[1.23, "sigmoid"]]

print[activate[1.23,"tanh"]][activate[1.23,"tanh"]]

Chúng tôi đã nêu ra & nbsp; ________ 39 & nbsp; với thông báo lỗi tùy chỉnh trong chức năng của chúng tôi & nbsp; ________ 41. Chạy mã này sẽ in cho bạn kết quả cho hai cuộc gọi đầu tiên nhưng không thành công vào ngày thứ ba vì chúng tôi đã định nghĩa chức năng ________ 42 & nbsp;

1.23

0.7738185742694538

Traceback [cuộc gọi gần đây nhất cuối cùng]:

Tệp "/Users/mlm/offivess.py", dòng 28, trong

in [kích hoạt [1.23, "tanh"]]]

Tệp "/Users

Nâng cao notimementedError [f "function {funcName} không được triển khai"]

NotMementedError: Chức năng Tanh không được triển khai

Như bạn có thể tưởng tượng, chúng ta có thể nâng cao & nbsp; ____ 39 ở những nơi mà điều kiện không hoàn toàn không hợp lệ, nhưng nó chỉ là chúng ta chưa sẵn sàng xử lý những trường hợp đó. Điều này rất hữu ích khi chúng tôi dần dần phát triển chương trình của mình, chúng tôi thực hiện một trường hợp tại một thời điểm và giải quyết một số trường hợp góc sau. Có các đường ray bảo vệ này tại chỗ sẽ đảm bảo mã nửa nướng của chúng tôi không bao giờ được sử dụng theo cách mà nó không được cho là. Đó cũng là một thực tế tốt để làm cho mã của chúng tôi khó bị lạm dụng hơn, tức là, không để các biến ra khỏi phạm vi dự định của chúng tôi mà không cần thông báo trước.

Trên thực tế, hệ thống xử lý ngoại lệ trong Python là trưởng thành và chúng ta nên sử dụng nó. Khi bạn không bao giờ mong đợi đầu vào sẽ âm, hãy nâng range[]1 với một thông điệp thích hợp. Tương tự, khi một cái gì đó bất ngờ xảy ra, ví dụ, một tệp tạm thời mà bạn tạo đã biến mất tại điểm giữa, tăng a5. Mã của bạn đã giành được công việc trong những trường hợp này, và việc tăng một ngoại lệ thích hợp có thể giúp tái sử dụng trong tương lai. Từ góc độ hiệu suất, bạn cũng sẽ thấy rằng việc nâng cao các ngoại lệ nhanh hơn so với sử dụng if-statements để kiểm tra. Đó là lý do tại sao trong Python, chúng tôi thích, nó dễ dàng yêu cầu sự tha thứ hơn là sự cho phép [EAFP] hơn so với bạn nhìn trước khi bạn nhảy vọt [LBYL].

Nguyên tắc ở đây là bạn không bao giờ nên để sự bất thường tiến hành âm thầm vì thuật toán của bạn sẽ không hoạt động chính xác và đôi khi có các hiệu ứng nguy hiểm [ví dụ: xóa các tệp sai hoặc tạo các vấn đề về an ninh mạng].

Bạn muốn bắt đầu với Python để học máy?

Tham gia khóa học gặp sự cố email 7 ngày miễn phí của tôi ngay bây giờ [với mã mẫu].

Nhấp để đăng ký và cũng nhận được phiên bản Ebook PDF miễn phí của khóa học.

Thực hành tốt để tránh lỗi

Không thể nói rằng một đoạn mã chúng tôi đã viết không có lỗi. Nó tốt như chúng tôi đã thử nghiệm nó, nhưng chúng tôi không biết những gì chúng tôi không biết. Luôn có những cách tiềm năng để phá vỡ mã bất ngờ. Tuy nhiên, có một số thực tiễn có thể thúc đẩy mã tốt với ít lỗi hơn.

Đầu tiên là việc sử dụng mô hình chức năng. Mặc dù chúng ta biết Python có các cấu trúc cho phép chúng ta viết một thuật toán trong cú pháp chức năng, nhưng nguyên tắc đằng sau lập trình chức năng là không có tác dụng phụ trong các cuộc gọi chức năng. Chúng tôi không bao giờ đột biến một cái gì đó và chúng tôi không sử dụng các biến được khai báo bên ngoài hàm. Nguyên tắc không có tác dụng phụ của người Viking là mạnh mẽ trong việc tránh rất nhiều lỗi vì chúng ta không bao giờ có thể thay đổi một cái gì đó một cách nhầm lẫn.

Khi chúng tôi viết bằng Python, có một số bất ngờ phổ biến rằng chúng tôi thấy đột biến một cấu trúc dữ liệu một cách vô tình. Xem xét những điều sau:

def func [a = []]:func[a=[]]:

    a.append[1]a.append[1]

    returnareturna

Đó là tầm thường để xem chức năng này làm gì. Tuy nhiên, khi chúng tôi gọi chức năng này mà không có bất kỳ đối số nào, mặc định được sử dụng và trả lại cho chúng tôi a6. Khi chúng tôi gọi lại, một mặc định khác được sử dụng và trả lại cho chúng tôi a7. Đó là bởi vì danh sách & nbsp; ________ 48 & nbsp; chúng tôi đã tạo tại khai báo hàm là giá trị mặc định cho đối số & nbsp; ____ 4 & nbsp; là một đối tượng được bắt đầu. Khi chúng ta nối một giá trị vào nó, đối tượng này sẽ bị đột biến. Lần tới chúng tôi gọi hàm sẽ thấy đối tượng bị đột biến.

Trừ khi chúng tôi rõ ràng muốn làm điều này [ví dụ: thuật toán sắp xếp tại chỗ], chúng tôi không nên sử dụng các đối số hàm làm biến nhưng nên sử dụng chúng như chỉ đọc. Và trong trường hợp nó phù hợp, chúng ta nên tạo một bản sao của nó. Ví dụ,

LOGS=[]=[]

log def [hành động]:log[action]:

    LOGS.append[action]LOGS.append[action]

data={"name":None}={"name":None}

fornin["Alice","Bob","Charlie"]:nin["Alice","Bob","Charlie"]:

    data["name"]=ndata["name"]=n

& nbsp; & nbsp; & nbsp; & nbsp; ... & nbsp; & nbsp;...  # do something with `data`

& nbsp; & nbsp; & nbsp; & nbsp; log [dữ liệu] & nbsp; & nbsp;#log[data]  # keep a record of what we did

Mã này nhằm giữ một bản ghi về những gì chúng tôi đã làm trong danh sách & nbsp; ____ 50, nhưng không có. Trong khi chúng tôi làm việc trên những cái tên của Alice Alice, Hồi giáo Bob, và sau đó là Charlie Charlie, ba bản ghi trong b0 đều sẽ là Charlie Charlie vì chúng tôi giữ cho đối tượng từ điển có thể thay đổi ở đó. Nó nên được sửa chữa như sau:

Nhập bản saocopy

log def [hành động]:log[action]:

    copied_action=copy.deepcopy[action]copied_action=copy.deepcopy[action]

    LOGS.append[copied_action]LOGS.append[copied_action]

& nbsp; & nbsp; & nbsp; & nbsp; ... & nbsp; & nbsp;

& nbsp; & nbsp; & nbsp; & nbsp; log [dữ liệu] & nbsp; & nbsp;#

Mã này nhằm giữ một bản ghi về những gì chúng tôi đã làm trong danh sách & nbsp; ____ 50, nhưng không có. Trong khi chúng tôi làm việc trên những cái tên của Alice Alice, Hồi giáo Bob, và sau đó là Charlie Charlie, ba bản ghi trong b0 đều sẽ là Charlie Charlie vì chúng tôi giữ cho đối tượng từ điển có thể thay đổi ở đó. Nó nên được sửa chữa như sau:

Nhập bản saoneg_in_upper_tri[matrix]:

    n_rows=len[matrix]n_rows=len[matrix]

    n_cols=len[matrix[0]]n_cols=len[matrix[0]]

    foriinrange[n_rows]:foriinrange[n_rows]:

        forjinrange[n_cols]:forjinrange[n_cols]:

            ifi>j:if i>j:

Sau đó, chúng ta sẽ thấy ba tên riêng biệt trong nhật ký. Tóm lại, chúng ta nên cẩn thận nếu đối số cho chức năng của chúng ta là một đối tượng có thể thay đổi.continue  # we are not in upper triangular

            ifmatrix[i][j]if matrix[i][j]j:if i>j:

Sau đó, chúng ta sẽ thấy ba tên riêng biệt trong nhật ký. Tóm lại, chúng ta nên cẩn thận nếu đối số cho chức năng của chúng ta là một đối tượng có thể thay đổi.continue  # we are not in upper triangular

Kỹ thuật khác để tránh lỗi không phải là để phát minh lại bánh xe. Trong Python, chúng tôi có rất nhiều container đẹp và các hoạt động được tối ưu hóa. Bạn không bao giờ nên cố gắng tự tạo cấu trúc dữ liệu ngăn xếp vì danh sách hỗ trợ & nbsp; ________ 52 & nbsp; và & nbsp; ________ 53. Việc thực hiện của bạn sẽ không nhanh hơn. Tương tự, nếu bạn cần hàng đợi, chúng tôi có & nbsp; ________ 54 & nbsp; trong mô -đun & nbsp; ________ 55 từ thư viện tiêu chuẩn. Python không đi kèm với một cây tìm kiếm cân bằng hoặc danh sách được liên kết. Nhưng từ điển được tối ưu hóa cao và chúng ta nên xem xét sử dụng từ điển bất cứ khi nào có thể. Thái độ tương tự áp dụng cho các chức năng quá. Chúng tôi có một thư viện JSON, và chúng tôi không nên viết của riêng mình. Nếu chúng ta cần một số thuật toán số, hãy kiểm tra xem bạn có thể nhận được một từ không.yield matrix[i][j]

Nhập bản saoneg_in_upper_tri[matrix]:

Sau đó, chúng ta sẽ thấy ba tên riêng biệt trong nhật ký. Tóm lại, chúng ta nên cẩn thận nếu đối số cho chức năng của chúng ta là một đối tượng có thể thay đổi.forelement inget_upper_tri[matrix]:

        ifelement[i][j]if element[i][j]

Bài Viết Liên Quan

Chủ Đề