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!

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

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<b:

        values.append(n)values.append(n)

        n=n+cn=n+c

    returnvaluesreturnvalues

& nbsp; & nbsp;

Hoặc, tốt hơn, chuyển đổi đầu vào thành một điểm nổi bất cứ khi nào có thể:canonicalization. This means we should make the input in a standardized format. For example, a URL should start with “http://,” and a file path should always be a full absolute path like range()3 instead of something like range()4. Canonicalized input is easier to check for conformation (e.g., we know range()3 contains sensitive system data, but we’re not so sure about range()4).

Bạn có thể tự hỏi nếu cần thiết để làm cho mã của chúng tôi dài hơn bằng cách thêm các vệ sinh này. Chắc chắn, đó là một sự cân bằng bạn cần phải quyết định. Thông thường, chúng tôi không làm điều này trên mọi chức năng để tiết kiệm nỗ lực của chúng tôi cũng như không thỏa hiệp hiệu quả tính toán. Chúng tôi chỉ làm điều này khi nó có thể sai, cụ thể là trên các chức năng giao diện mà chúng tôi hiển thị là API cho người dùng khác hoặc trên chức năng chính nơi chúng tôi lấy đầu vào từ dòng lệnh của người dùng.

Tuy nhiên, chúng tôi muốn chỉ ra rằng những điều sau đây là một cách sai nhưng phổ biến để làm vệ sinh:

def add (a, b):add(a,b):

& nbsp; & nbsp; & nbsp; & nbsp; assertisInstance (a, (int, float)), "` a` phải là một số "assertisinstance(a,(int,float)),"`a` must be a number"

& nbsp; & nbsp; & nbsp; & nbsp; assertisInstance (b, (int, float)), "` b` phải là một số "assert isinstance(b,(int,float)),"`b` must be a number"

    returna+breturna+b

Tuyên bố & nbsp; Mặc dù không có nhiều sự khác biệt thực tế giữa việc tăng ____ 18 & nbsp; và nâng & nbsp; ____ ____ 21 & nbsp; trên đầu vào bất ngờ, sử dụng & nbsp;

Tất cả & nbsp; ________ 17 & nbsp; trong mã & nbsp; ________ 25 & nbsp; sẽ bị bỏ qua trong trường hợp này. Do đó, nếu ý định của chúng tôi là ngăn mã thực thi (bao gồm cả bạn muốn nắm bắt ngoại lệ ở cấp độ cao hơn), bạn nên sử dụng & nbsp; ________ 3 & nbsp; và rõ ràng tăng một ngoại lệ thay vì sử dụng & nbsp; ________ 17.

Cách chính xác của việc sử dụng & nbsp; ________ 17 là giúp chúng tôi gỡ lỗi trong khi phát triển mã của chúng tôi. Ví dụ,

def chẵn ra (mảng):evenitems(arr):

    newarr=[]newarr=[]

    foriinrange(len(arr)):foriinrange(len(arr)):

        ifi%2==0:if i%2==0:

            newarr.append(arr[i])newarr.append(arr[i])

    assertlen(newarr)*2>=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<e:

        m=(s+e)//2m=(s+e)//2

        ifarray[m]==target:if array[m]==target:

            returnmreturnm

& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp;elif array[m]> target:

            e=me=m

& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp;elif array[m]<target:

            s=m+1s =m+1

& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; ASPassertm!=(s+e)//2, "we didn't move our midpoint"

    return-1return-1

Tuyên bố cuối cùng & nbsp; ____ ____ 17 & nbsp; là để duy trì các bất biến vòng lặp của chúng tôi. Điều này là để đảm bảo rằng chúng tôi đã không phạm sai lầm về logic để cập nhật con trỏ bắt đầu & nbsp; ________ 33 & nbsp; và cuối con trỏ & nbsp; Nếu chúng tôi đã thay thế ____ 36 & nbsp; với & nbsp; ________ 37 & nbsp; trong chi nhánh cuối cùng & nbsp; ________ 38 và sử dụng chức năng trên các mục tiêu nhất định không tồn tại trong mảng, câu lệnh ASPER sẽ cảnh báo chúng tôi về lỗi này. Đó là lý do tại sao kỹ thuật này có thể giúp chúng tôi viết mã tốt hơn.

Bảo vệ đường ray và lập trình tấn công

Thật tuyệt vời khi thấy Python đi kèm với A & NBSP; ________ 39 ngoại lệ tích hợp. Điều này rất hữu ích cho những gì chúng tôi gọi là & nbsp; & nbsp; lập trình tấn công.offensive programming.

Mặc dù vệ sinh đầu vào là giúp sắp xếp đầu vào theo định dạng mà mã của chúng tôi mong đợi, đôi khi không dễ để vệ sinh mọi thứ hoặc bất tiện cho sự phát triển trong tương lai của chúng tôi. Một ví dụ là sau đây, trong đó chúng tôi xác định một trình trang trí đăng ký và một số chức năng:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

Nhập toánmath

REGISTRY={}={}

Đăng ký DEF (Tên):register(name):

& nbsp; & nbsp; & nbsp; & nbsp; def _decorator (fn):def _decorator(fn):

        REGISTRY[name]=fnREGISTRY[name] =fn

        returnfnreturnfn

    return_decoratorreturn_decorator

@register("relu")register("relu")

xác định chính xác (x):rectified(x):

    returnxifx>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]<0:

                returnTruereturnTrue

    returnFalsereturnFalse

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.

Một cách khác để tránh lỗi là sử dụng logic tốt hơn. Một thuật toán với rất nhiều vòng và cành sẽ khó theo dõi và thậm chí có thể nhầm lẫn chính chúng ta. Sẽ dễ dàng hơn để phát hiện lỗi nếu chúng ta có thể làm cho mã của chúng ta rõ ràng hơn. Ví dụ, tạo một hàm kiểm tra xem phần hình tam giác trên của ma trận chứa bất kỳ âm nào sẽ như thế này:get_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

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]<0:

            returnTruereturnTrue

    returnFalsereturnFalse

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.

Một cách khác để tránh lỗi là sử dụng logic tốt hơn. Một thuật toán với rất nhiều vòng và cành sẽ khó theo dõi và thậm chí có thể nhầm lẫn chính chúng ta. Sẽ dễ dàng hơn để phát hiện lỗi nếu chúng ta có thể làm cho mã của chúng ta rõ ràng hơn. Ví dụ, tạo một hàm kiểm tra xem phần hình tam giác trên của ma trận chứa bất kỳ âm nào sẽ như thế này:

AFT NEG_IN_UPPER_TRI (ma trận):

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

def isFloat (floatString):isfloat(floatstring):

    ifnotisinstance(floatstring,str):ifnotisinstance(floatstring,str):

& nbsp; & nbsp;raise ValueError("Expects a string input")

    seen_integer=Falseseen_integer=False

    seen_dot=Falseseen_dot=False

    seen_decimal=Falseseen_decimal=False

    forcharinfloatstring:forchar infloatstring:

        ifchar.isdigit():ifchar.isdigit():

            ifnotseen_integer:ifnotseen_integer:

                seen_integer=Trueseen_integer =True

& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp;elif seen_dot andnotseen_decimal:

                seen_decimal=Trueseen_decimal=True

& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp;elif char==".":

            ifnotseen_integer:ifnotseen_integer:

& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp;returnFalse  # e.g., ".3456"

& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp;elif notseen_dot:

                seen_dot=Trueseen_dot=True

            else:else:

& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp;returnFalse  # e.g., "1..23"

        else:else:

& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; "Foo"returnFalse  # e.g. "foo"

    ifnotseen_integer:ifnotseen_integer:

& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; # ví dụ., ""returnFalse   # e.g., ""

& nbsp; & nbsp; & nbsp; & nbsp;ifseen_dot andnot seen_decimal:

& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp;returnFalse  # e.g., "2."

    returnTruereturnTrue

in (isFloat ("foo")) & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; # Sai(isfloat("foo"))       # False

in (isFloat (". 3456")) & nbsp; & nbsp; & nbsp; & nbsp; # Sai(isfloat(".3456"))     # False

in (isFloat ("1.23")) & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp;# true(isfloat("1.23"))      # True

in (isFloat ("1..23")) & nbsp; & nbsp; & nbsp; & nbsp; # Sai(isfloat("1..23"))     # False

in (isFloat ("2")) # ĐÚNG VẬY(isfloat("2"))         # True

in (isFloat ("2."))(isfloat("2."))        # False

in (isFloat ("2.345,67")) & nbsp; & nbsp;# false(isfloat("2,345.67"))  # False

Hàm & nbsp; ________ 64 ở trên lộn xộn với rất nhiều nhánh lồng nhau bên trong vòng lặp. Ngay cả sau vòng lặp, logic không hoàn toàn rõ ràng về cách chúng tôi xác định giá trị boolean. Thật vậy, chúng ta có thể sử dụng một cách khác để viết mã của mình để làm cho nó ít dễ bị lỗi hơn, chẳng hạn như sử dụng mô hình máy trạng thái:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

def isFloat (floatString):isfloat(floatstring):

    ifnotisinstance(floatstring,str):ifnotisinstance(floatstring,str):

& nbsp; & nbsp;raise ValueError("Expects a string input")

& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp;# States: "start", "integer", "dot", "decimal"

    state="start"state="start"

    forcharinfloatstring:forcharinfloatstring:

        ifstate=="start":if state=="start":

            ifchar.isdigit():ifchar.isdigit():

                state="integer"state= "integer"

            else:else:

& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp;returnFalse  # bad transition, can't continue

& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp;elif state =="integer":

            ifchar.isdigit():ifchar.isdigit():

& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp;pass  # stay in the same state

& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp;elif char==".":

                state="dot"state="dot"

            else:else:

& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp;return False  # bad transition, can't continue

& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp;elif state=="dot":

            ifchar.isdigit():ifchar.isdigit():

                state="decimal"state ="decimal"

            else:else:

& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp;returnFalse  # bad transition, can't continue

& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp;elif state=="decimal":

            ifnotchar.isdigit():ifnotchar.isdigit():

& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp;return False  # bad transition, can't continue

& NBSP; & nbsp; & nbsp; & nbsp;ifstate in["integer","decimal"]:

        returnTruereturnTrue

    else:else:

        returnFalsereturnFalse

in (isFloat ("foo")) & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; # Sai(isfloat("foo"))       # False

in (isFloat (". 3456")) & nbsp; & nbsp; & nbsp; & nbsp; # Sai(isfloat(".3456"))     # False

in (isFloat ("1.23")) & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp;# true(isfloat("1.23"))      # True

in (isFloat ("1..23")) & nbsp; & nbsp; & nbsp; & nbsp; # Sai(isfloat("1..23"))     # False

in (isFloat ("2")) # ĐÚNG VẬY(isfloat("2"))         # True

in (isFloat ("2."))(isfloat("2."))        # False

in (isFloat ("2.345,67")) & nbsp; & nbsp;# false(isfloat("2,345.67"))  # False

Trực quan, chúng tôi thực hiện sơ đồ dưới đây thành mã. Chúng tôi duy trì một biến trạng thái cho đến khi chúng tôi hoàn thành quét chuỗi đầu vào. Trạng thái sẽ quyết định chấp nhận một ký tự trong đầu vào và chuyển sang trạng thái khác hoặc từ chối nhân vật và chấm dứt. Chức năng này chỉ trả về đúng nếu nó dừng ở các trạng thái chấp nhận được, cụ thể là, số nguyên của Hồi giáo hoặc thập phân. Mã này dễ hiểu hơn và có cấu trúc hơn.

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

Trên thực tế, cách tiếp cận tốt hơn là sử dụng một biểu thức chính quy để khớp với chuỗi đầu vào, cụ thể là

Nhập REre

def isFloat (floatString):isfloat(floatstring):

    ifnotisinstance(floatstring,str):ifnotisinstance(floatstring,str):

& nbsp; & nbsp;raise ValueError("Expects a string input")

& nbsp; & nbsp; & nbsp; & nbsp; m = re.match (r "\ d+(\. \ d+)? $", floatString)m=re.match(r"\d+(\.\d+)?$",floatstring)

    returnmisnotNonereturnmis notNone

in (isFloat ("foo")) & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; # Sai(isfloat("foo"))       # False

in (isFloat (". 3456")) & nbsp; & nbsp; & nbsp; & nbsp; # Sai(isfloat(".3456"))     # False

in (isFloat ("1.23")) & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp;# true(isfloat("1.23"))      # True

in (isFloat ("1..23")) & nbsp; & nbsp; & nbsp; & nbsp; # Sai(isfloat("1..23"))     # False

in (isFloat ("2")) # ĐÚNG VẬY(isfloat("2"))         # True

in (isFloat ("2."))(isfloat("2."))        # False

in (isFloat ("2.345,67")) & nbsp; & nbsp;# false(isfloat("2,345.67"))  # False

Trực quan, chúng tôi thực hiện sơ đồ dưới đây thành mã. Chúng tôi duy trì một biến trạng thái cho đến khi chúng tôi hoàn thành quét chuỗi đầu vào. Trạng thái sẽ quyết định chấp nhận một ký tự trong đầu vào và chuyển sang trạng thái khác hoặc từ chối nhân vật và chấm dứt. Chức năng này chỉ trả về đúng nếu nó dừng ở các trạng thái chấp nhận được, cụ thể là, số nguyên của Hồi giáo hoặc thập phân. Mã này dễ hiểu hơn và có cấu trúc hơn.

Trên thực tế, cách tiếp cận tốt hơn là sử dụng một biểu thức chính quy để khớp với chuỗi đầu vào, cụ thể là

Nhập REcoding style for your project. Having a consistent way to write code is the first step in offloading some of your mental burdens later when you read what you have written. This also makes you spot mistakes easier.

def isFloat (floatString):

& nbsp; & nbsp;

& nbsp; & nbsp; & nbsp; & nbsp; m = re.match (r "\ d+(\. \ d+)? $", floatString)

  • Tuy nhiên, một trình kết hợp biểu thức thông thường cũng đang chạy một máy trạng thái dưới mui xe.

Có nhiều cách để khám phá về chủ đề này. Ví dụ, làm thế nào chúng ta có thể tách biệt trách nhiệm của các chức năng và đối tượng tốt hơn để làm cho mã của chúng ta có thể duy trì hơn và dễ hiểu hơn. Đôi khi, sử dụng cấu trúc dữ liệu khác nhau có thể cho phép chúng tôi viết mã đơn giản hơn, giúp làm cho mã của chúng tôi mạnh mẽ hơn. Nó không phải là một khoa học, nhưng hầu như luôn luôn, các lỗi có thể tránh được nếu mã đơn giản hơn.

  • Cuối cùng, xem xét áp dụng một phong cách mã hóa cho dự án của bạn. Có một cách nhất quán để viết mã là bước đầu tiên trong việc giảm tải một số gánh nặng tinh thần của bạn sau này khi bạn đọc những gì bạn đã viết. Điều này cũng làm cho bạn phát hiện sai lầm dễ dàng hơn.
  • đọc thêm
  • Phần này cung cấp nhiều tài nguyên hơn về chủ đề nếu bạn đang muốn đi sâu hơn.
  • Bài viết
  • Hướng dẫn theo phong cách Google Python

Sách

Xây dựng phần mềm an toàn của John Viega và Gary R. McGraw

Xây dựng các hệ thống an toàn và đáng tin cậy của Heather Adkins et al

  • Hướng dẫn về Hitchhiker về Python của Kenneth Reitz và Tanya Schlusser
  • Thực hành lập trình của Brian Kernighan và Rob Pike
  • Tái cấu trúc, Phiên bản thứ 2, bởi Martin Fowler
  • Bản tóm tắt

Trong hướng dẫn này, bạn đã thấy các kỹ thuật cấp cao để làm cho mã của bạn tốt hơn. Nó có thể được chuẩn bị tốt hơn cho một tình huống khác, vì vậy nó hoạt động cứng nhắc hơn. Nó cũng có thể dễ dàng đọc, duy trì và mở rộng, vì vậy nó phù hợp để tái sử dụng trong tương lai. Một số kỹ thuật được đề cập ở đây là chung cho các ngôn ngữ lập trình khác.

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

Cụ thể, bạn đã học được:

Tại sao chúng tôi muốn vệ sinh đầu vào của mình và làm thế nào nó có thể giúp chương trình của chúng tôi đơn giản hơn

Cách sử dụng chính xác range()7 làm công cụ để giúp phát triển
Python for Machine Learning

Cách sử dụng các ngoại lệ Python một cách thích hợp để đưa ra tín hiệu trong các tình huống bất ngờself-study tutorials with hundreds of working code to equip you with skills including:
debugging, profiling, duck typing, decorators, deployment, and much more...

Cho bạn xem hộp công cụ Python ở mức cao cho các dự án của bạn
Your Projects

Xem những gì bên trong