Tam giác vuông ngược trong python

Đây là một đề xuất để tạo một cách gán cho các biến trong một biểu thức bằng cách sử dụng ký hiệu

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
0

Là một phần của thay đổi này, cũng có một bản cập nhật cho thứ tự đánh giá mức độ hiểu từ điển để đảm bảo các biểu thức khóa được thực thi trước các biểu thức giá trị (cho phép khóa được liên kết với một tên và sau đó được sử dụng lại như một phần của việc tính toán giá trị tương ứng)

Trong quá trình thảo luận về PEP này, người điều hành được gọi một cách không chính thức là “người điều hành hải mã”. Tên chính thức của cấu trúc là “Biểu thức gán” (theo tiêu đề PEP), nhưng chúng cũng có thể được gọi là “Biểu thức được đặt tên” (e. g. việc triển khai tham chiếu CPython sử dụng tên đó trong nội bộ)

cơ sở lý luận

Đặt tên cho kết quả của biểu thức là một phần quan trọng của lập trình, cho phép sử dụng tên mô tả thay cho biểu thức dài hơn và cho phép sử dụng lại. Hiện tại, tính năng này chỉ khả dụng ở dạng câu lệnh, khiến tính năng này không khả dụng ở dạng hiểu danh sách và các ngữ cảnh biểu thức khác

Ngoài ra, việc đặt tên cho các phần phụ của một biểu thức lớn có thể hỗ trợ trình gỡ lỗi tương tác, cung cấp các móc hiển thị hữu ích và các kết quả từng phần. Không có cách nào để nắm bắt các biểu thức con nội tuyến, điều này sẽ yêu cầu tái cấu trúc mã gốc; . Việc loại bỏ nhu cầu tái cấu trúc giúp giảm khả năng mã vô tình bị thay đổi như một phần của quá trình gỡ lỗi (nguyên nhân phổ biến của Heisenbugs) và dễ dàng hơn để ra lệnh cho lập trình viên khác

Tầm quan trọng của mã thực

Trong quá trình phát triển PEP này, nhiều người (cả người ủng hộ và người chỉ trích) có xu hướng một mặt tập trung vào các ví dụ về đồ chơi, mặt khác lại tập trung vào các ví dụ quá phức tạp.

Sự nguy hiểm của ví dụ đồ chơi là gấp đôi. chúng thường quá trừu tượng để khiến bất kỳ ai cũng phải thốt lên “ồ, thật hấp dẫn”, và chúng dễ dàng bị bác bỏ với câu “Dù sao thì tôi cũng sẽ không bao giờ viết nó theo cách đó đâu”

Sự nguy hiểm của các ví dụ quá phức tạp là chúng cung cấp một ống hút tiện lợi cho những người chỉ trích đề xuất để bác bỏ (“điều đó thật khó hiểu”)

Tuy nhiên, có một số sử dụng cho cả ví dụ cực kỳ đơn giản và cực kỳ phức tạp. chúng rất hữu ích để làm rõ ngữ nghĩa dự định. Do đó, sẽ có một số dưới đây

Tuy nhiên, để hấp dẫn, các ví dụ nên bắt nguồn từ mã thực, tôi. e. mã được viết mà không hề nghĩ đến PEP này, như một phần của ứng dụng hữu ích, dù lớn hay nhỏ. Tim Peters đã cực kỳ hữu ích bằng cách xem qua kho lưu trữ mã cá nhân của anh ấy và chọn các ví dụ về mã mà anh ấy đã viết (theo quan điểm của anh ấy) sẽ rõ ràng hơn nếu được viết lại bằng cách sử dụng (ít) các biểu thức gán. kết luận của mình. đề xuất hiện tại sẽ cho phép một sự cải thiện khiêm tốn nhưng rõ ràng trong một vài đoạn mã

Một cách sử dụng khác của mã thực là để quan sát gián tiếp giá trị mà các lập trình viên đặt vào tính gọn nhẹ. Guido van Rossum đã tìm kiếm thông qua cơ sở mã Dropbox và phát hiện ra một số bằng chứng cho thấy các lập trình viên đánh giá cao việc viết ít dòng hơn các dòng ngắn hơn

Trường hợp tại điểm. Guido đã tìm thấy một số ví dụ trong đó một lập trình viên lặp lại một biểu thức con, làm chậm chương trình, để tiết kiệm một dòng mã, e. g. thay vì viết

match = re.match(data)
group = match.group(1) if match else None

họ sẽ viết

group = re.match(data).group(1) if re.match(data) else None

Một ví dụ khác minh họa rằng các lập trình viên đôi khi phải làm nhiều việc hơn để tiết kiệm thêm một mức thụt đầu dòng

match1 = pattern1.match(data)
match2 = pattern2.match(data)
if match1:
    result = match1.group(1)
elif match2:
    result = match2.group(2)
else:
    result = None

Mã này cố khớp với

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
2 ngay cả khi
def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
3 khớp (trong trường hợp đó, khớp trên
def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
2 không bao giờ được sử dụng). Việc viết lại hiệu quả hơn sẽ là

________số 8

Cú pháp và ngữ nghĩa

Trong hầu hết các ngữ cảnh có thể sử dụng các biểu thức Python tùy ý, một biểu thức được đặt tên có thể xuất hiện. Đây là dạng

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
0 trong đó
def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
6 là bất kỳ biểu thức Python hợp lệ nào không phải là một bộ dữ liệu không được mở ngoặc đơn và
def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
7 là một mã định danh

Giá trị của một biểu thức được đặt tên như vậy giống như biểu thức được kết hợp, với tác dụng phụ bổ sung mà mục tiêu được gán giá trị đó

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
2

Trường hợp đặc biệt

Có một số chỗ không cho phép biểu thức gán để tránh sự mơ hồ hoặc nhầm lẫn của người dùng

  • Biểu thức gán không được đặt trong dấu ngoặc đơn bị cấm ở cấp cao nhất của câu lệnh biểu thức. Thí dụ

    def foo(answer = p := 42):  # INVALID
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...
    
    3

    Quy tắc này được đưa vào để đơn giản hóa sự lựa chọn cho người dùng giữa câu lệnh gán và biểu thức gán – không có vị trí cú pháp nào mà cả hai đều hợp lệ

  • Các biểu thức gán không được đặt trong dấu ngoặc đơn bị cấm ở cấp cao nhất của phía bên tay phải của câu lệnh gán. Thí dụ

    def foo(answer = p := 42):  # INVALID
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...
    
    4

    Một lần nữa, quy tắc này được đưa vào để tránh hai cách nói giống nhau về mặt hình ảnh.

  • Các biểu thức gán không được đặt trong dấu ngoặc đơn bị cấm đối với giá trị của đối số từ khóa trong cuộc gọi. Thí dụ

    def foo(answer = p := 42):  # INVALID
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...
    
    5

    Quy tắc này được đưa vào để không cho phép mã quá khó hiểu và bởi vì việc phân tích đối số từ khóa đã đủ phức tạp rồi

  • Các biểu thức gán không được đặt trong ngoặc đơn bị cấm ở cấp cao nhất của giá trị mặc định của hàm. Thí dụ

    def foo(answer = p := 42):  # INVALID
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...
    

    Quy tắc này được đưa vào để ngăn chặn các tác dụng phụ ở vị trí mà ngữ nghĩa chính xác của nó đã gây nhầm lẫn cho nhiều người dùng (cf. đề xuất kiểu phổ biến đối với các giá trị mặc định có thể thay đổi) và cũng để lặp lại lệnh cấm tương tự trong các cuộc gọi (dấu đầu dòng trước đó)

  • Các biểu thức gán không được mở ngoặc đơn bị cấm làm chú thích cho các đối số, giá trị trả về và phép gán. Thí dụ

    def foo(answer = p := 42):  # INVALID
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...
    
    7

    Lý do ở đây tương tự như hai trường hợp trước;

  • Các biểu thức gán không được đặt trong dấu ngoặc đơn bị cấm trong các hàm lambda. Thí dụ

    group = re.match(data).group(1) if re.match(data) else None
    
    0

    Điều này cho phép

    def foo(answer = p := 42):  # INVALID
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...
    
    70 luôn liên kết ít chặt chẽ hơn
    def foo(answer = p := 42):  # INVALID
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...
    
    59; . Trong trường hợp tên sẽ được sử dụng nhiều lần, biểu thức có thể cần được đặt trong ngoặc đơn, vì vậy lệnh cấm này sẽ hiếm khi ảnh hưởng đến mã

  • Biểu thức gán bên trong chuỗi f yêu cầu dấu ngoặc đơn. Thí dụ

    group = re.match(data).group(1) if re.match(data) else None
    
    1

    Điều này cho thấy rằng cái giống như toán tử gán trong chuỗi f không phải lúc nào cũng là toán tử gán. Trình phân tích chuỗi f sử dụng

    def foo(answer = p := 42):  # INVALID
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...
    
    8 để biểu thị các tùy chọn định dạng. Để duy trì khả năng tương thích ngược, việc sử dụng toán tử gán bên trong chuỗi f phải được đặt trong ngoặc đơn. Như đã lưu ý ở trên, việc sử dụng toán tử gán này không được khuyến nghị

Phạm vi mục tiêu

Một biểu thức gán không giới thiệu một phạm vi mới. Trong hầu hết các trường hợp, phạm vi mà mục tiêu sẽ bị ràng buộc là tự giải thích. nó là phạm vi hiện tại. Nếu phạm vi này chứa khai báo

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
73 hoặc
def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
74 cho mục tiêu, thì biểu thức gán sẽ tôn trọng điều đó. Một lambda (là một định nghĩa hàm rõ ràng, nếu ẩn danh) được tính là một phạm vi cho mục đích này

Có một trường hợp đặc biệt. một biểu thức gán xảy ra trong một danh sách, tập hợp hoặc đọc chính tả hoặc trong một biểu thức trình tạo (dưới đây được gọi chung là "sự hiểu") liên kết mục tiêu trong phạm vi chứa, tôn trọng khai báo

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
73 hoặc
def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
74 cho mục tiêu trong phạm vi đó, nếu một . Với mục đích của quy tắc này, phạm vi chứa của một cách hiểu lồng nhau là phạm vi chứa cách hiểu ngoài cùng. Lambda được tính là phạm vi chứa

Động lực cho trường hợp đặc biệt này là gấp đôi. Đầu tiên, nó cho phép chúng ta nắm bắt một cách thuận tiện một “nhân chứng” cho một biểu thức

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
77, hoặc một ví dụ ngược lại cho
def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
78, chẳng hạn

group = re.match(data).group(1) if re.match(data) else None
2

Thứ hai, nó cho phép một cách nhỏ gọn để cập nhật trạng thái có thể thay đổi từ một cách hiểu, chẳng hạn

group = re.match(data).group(1) if re.match(data) else None
3

Tuy nhiên, tên đích của biểu thức gán không được giống với tên đích của ____ xuất hiện trong bất kỳ cách hiểu nào có chứa biểu thức gán. Các tên sau là cục bộ đối với cách hiểu mà chúng xuất hiện, do đó, sẽ mâu thuẫn nếu việc sử dụng cùng tên để chỉ phạm vi chứa cách hiểu ngoài cùng thay thế

Ví dụ:

group = re.match(data).group(1) if re.match(data) else None
00 không hợp lệ. phần
group = re.match(data).group(1) if re.match(data) else None
01 xác định rằng
group = re.match(data).group(1) if re.match(data) else None
02 là cục bộ để hiểu, nhưng phần
group = re.match(data).group(1) if re.match(data) else None
03 khẳng định rằng
group = re.match(data).group(1) if re.match(data) else None
02 không phải là cục bộ để hiểu. Lý do tương tự cũng làm cho các ví dụ này không hợp lệ

group = re.match(data).group(1) if re.match(data) else None
4

Mặc dù về mặt kỹ thuật có thể gán ngữ nghĩa nhất quán cho những trường hợp này, nhưng rất khó để xác định liệu những ngữ nghĩa đó có thực sự có ý nghĩa hay không khi không có trường hợp sử dụng thực tế. Theo đó, việc triển khai tham chiếu sẽ đảm bảo rằng các trường hợp như vậy tăng

group = re.match(data).group(1) if re.match(data) else None
05, thay vì thực thi với hành vi được xác định bởi việc triển khai

Hạn chế này áp dụng ngay cả khi biểu thức gán không bao giờ được thực hiện

group = re.match(data).group(1) if re.match(data) else None
5

Đối với nội dung hiểu (phần trước từ khóa “for” đầu tiên) và biểu thức bộ lọc (phần sau “if” và trước bất kỳ “for” lồng nhau nào), hạn chế này chỉ áp dụng cho các tên đích cũng được sử dụng làm biến lặp trong . Các biểu thức lambda xuất hiện ở những vị trí này giới thiệu một phạm vi chức năng rõ ràng mới và do đó có thể sử dụng các biểu thức gán mà không có hạn chế bổ sung nào

Do các ràng buộc thiết kế trong quá trình triển khai tham chiếu (bộ phân tích bảng ký hiệu không thể dễ dàng phát hiện khi các tên được sử dụng lại giữa biểu thức có thể hiểu lặp lại ở ngoài cùng bên trái và phần còn lại của khả năng hiểu), các biểu thức đã đặt tên hoàn toàn không được phép như một phần của biểu thức có thể hiểu lặp lại (phần

group = re.match(data).group(1) if re.match(data) else None
6

Một ngoại lệ khác được áp dụng khi một biểu thức gán xuất hiện trong một cách hiểu có phạm vi chứa là phạm vi lớp. Nếu các quy tắc trên dẫn đến mục tiêu được gán trong phạm vi của lớp đó, thì biểu thức gán rõ ràng là không hợp lệ. Trường hợp này cũng tăng

group = re.match(data).group(1) if re.match(data) else None
05

group = re.match(data).group(1) if re.match(data) else None
7

(Lý do cho ngoại lệ sau là phạm vi hàm ẩn được tạo để hiểu – hiện tại không có cơ chế thời gian chạy nào để hàm tham chiếu đến một biến trong phạm vi lớp chứa và chúng tôi không muốn thêm cơ chế như vậy. Nếu vấn đề này đã được giải quyết, trường hợp đặc biệt này có thể bị xóa khỏi đặc điểm kỹ thuật của biểu thức gán. Lưu ý rằng sự cố đã tồn tại đối với việc sử dụng một biến được xác định trong phạm vi lớp từ khả năng hiểu. )

Xem Phụ lục B để biết một số ví dụ về cách các quy tắc cho mục tiêu hiểu chuyển thành mã tương đương

Ưu tiên tương đối của def foo(answer = p := 42): # INVALID ... def foo(answer=(p := 42)): # Valid, though not great style ... 59

Nhóm toán tử

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
59 chặt chẽ hơn dấu phẩy ở tất cả các vị trí cú pháp hợp lệ, nhưng kém chặt chẽ hơn tất cả các toán tử khác, bao gồm
group = re.match(data).group(1) if re.match(data) else None
09,
group = re.match(data).group(1) if re.match(data) else None
10,
group = re.match(data).group(1) if re.match(data) else None
11 và biểu thức điều kiện (
group = re.match(data).group(1) if re.match(data) else None
12). Như sau từ phần “Các trường hợp ngoại lệ” ở trên, nó không bao giờ được phép ở cùng cấp độ với
def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
9. Trong trường hợp muốn phân nhóm khác, nên sử dụng dấu ngoặc đơn

Toán tử

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
59 có thể được sử dụng trực tiếp trong đối số lệnh gọi hàm vị trí;

Một số ví dụ để làm rõ những gì hợp lệ hoặc không hợp lệ về mặt kỹ thuật

group = re.match(data).group(1) if re.match(data) else None
8

Hầu hết các ví dụ “hợp lệ” ở trên đều không được khuyến nghị, vì những người đọc mã nguồn Python là con người, những người chỉ lướt qua một số mã có thể bỏ sót sự khác biệt. Nhưng những trường hợp đơn giản không phản cảm

group = re.match(data).group(1) if re.match(data) else None
9

PEP này khuyến nghị luôn đặt khoảng trắng xung quanh

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
59, tương tự như khuyến nghị của PEP 8 cho
def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
9 khi được sử dụng để gán, trong khi phần sau không cho phép khoảng trắng xung quanh
def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
9 được sử dụng cho đối số từ khóa. )

Thay đổi thứ tự đánh giá

Để có ngữ nghĩa được xác định chính xác, đề xuất yêu cầu thứ tự đánh giá phải được xác định rõ. Về mặt kỹ thuật, đây không phải là một yêu cầu mới, vì các lệnh gọi hàm có thể đã có tác dụng phụ. Python đã có một quy tắc là các biểu thức con thường được đánh giá từ trái sang phải. Tuy nhiên, các biểu thức gán làm cho các tác dụng phụ này rõ ràng hơn và chúng tôi đề xuất một thay đổi duy nhất đối với thứ tự đánh giá hiện tại

  • Trong cách hiểu chính tả
    group = re.match(data).group(1) if re.match(data) else None
    
    18,
    group = re.match(data).group(1) if re.match(data) else None
    
    19 hiện được đánh giá trước
    group = re.match(data).group(1) if re.match(data) else None
    
    20. Chúng tôi đề xuất thay đổi điều này để
    group = re.match(data).group(1) if re.match(data) else None
    
    20 được đánh giá trước
    group = re.match(data).group(1) if re.match(data) else None
    
    19. (Trong một màn hình chính tả như
    group = re.match(data).group(1) if re.match(data) else None
    
    23, đây đã là trường hợp và cả trong
    group = re.match(data).group(1) if re.match(data) else None
    
    24, điều này rõ ràng phải tương đương với khả năng hiểu chính tả. )

Sự khác nhau giữa biểu thức gán và câu lệnh gán

Quan trọng nhất, vì

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
59 là một biểu thức, nó có thể được sử dụng trong các ngữ cảnh mà các câu lệnh là bất hợp pháp, bao gồm các hàm lambda và khả năng hiểu

Ngược lại, biểu thức gán không hỗ trợ các tính năng nâng cao có trong câu lệnh gán

  • Nhiều mục tiêu không được hỗ trợ trực tiếp

    match1 = pattern1.match(data)
    match2 = pattern2.match(data)
    if match1:
        result = match1.group(1)
    elif match2:
        result = match2.group(2)
    else:
        result = None
    
    0

  • Các mục tiêu chuyển nhượng đơn lẻ không phải là một
    def foo(answer = p := 42):  # INVALID
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...
    
    7 không được hỗ trợ

    match1 = pattern1.match(data)
    match2 = pattern2.match(data)
    if match1:
        result = match1.group(1)
    elif match2:
        result = match2.group(2)
    else:
        result = None
    
    1

  • Ưu tiên xung quanh dấu phẩy là khác nhau

    match1 = pattern1.match(data)
    match2 = pattern2.match(data)
    if match1:
        result = match1.group(1)
    elif match2:
        result = match2.group(2)
    else:
        result = None
    
    2

  • Không hỗ trợ đóng gói và giải nén lặp lại (cả dạng thông thường và dạng mở rộng)

    match1 = pattern1.match(data)
    match2 = pattern2.match(data)
    if match1:
        result = match1.group(1)
    elif match2:
        result = match2.group(2)
    else:
        result = None
    
    3

  • Chú thích loại nội tuyến không được hỗ trợ

    match1 = pattern1.match(data)
    match2 = pattern2.match(data)
    if match1:
        result = match1.group(1)
    elif match2:
        result = match2.group(2)
    else:
        result = None
    
    4

  • Bài tập tăng cường không được hỗ trợ

    match1 = pattern1.match(data)
    match2 = pattern2.match(data)
    if match1:
        result = match1.group(1)
    elif match2:
        result = match2.group(2)
    else:
        result = None
    
    5

Thông số kỹ thuật thay đổi trong quá trình thực hiện

Những thay đổi sau đây đã được thực hiện dựa trên kinh nghiệm triển khai và đánh giá bổ sung sau khi PEP được chấp nhận lần đầu tiên và trước Python 3. 8 đã được phát hành

  • để thống nhất với các ngoại lệ tương tự khác và để tránh khóa tên ngoại lệ không nhất thiết sẽ cải thiện sự rõ ràng cho người dùng cuối, lớp con
    group = re.match(data).group(1) if re.match(data) else None
    
    27 được đề xuất ban đầu của
    group = re.match(data).group(1) if re.match(data) else None
    
    05 đã bị loại bỏ để chỉ tăng trực tiếp
    group = re.match(data).group(1) if re.match(data) else None
    
    05. [3]
  • do hạn chế trong quy trình phân tích bảng ký hiệu của CPython, việc triển khai tham chiếu tăng
    group = re.match(data).group(1) if re.match(data) else None
    
    05 cho tất cả các cách sử dụng biểu thức được đặt tên bên trong các biểu thức có thể hiểu, thay vì chỉ tăng chúng khi mục tiêu biểu thức được đặt tên xung đột với một trong các biến lặp trong phần hiểu. Điều này có thể được xem xét lại với các ví dụ đủ thuyết phục, nhưng độ phức tạp bổ sung cần thiết để thực hiện hạn chế chọn lọc hơn dường như không đáng giá đối với các trường hợp sử dụng thuần túy giả thuyết

ví dụ

Ví dụ từ thư viện chuẩn Python

Địa điểm. py

env_base chỉ được sử dụng trên những dòng này, việc gán nó vào if sẽ di chuyển nó làm “tiêu đề” của khối

  • Hiện hành

    match1 = pattern1.match(data)
    match2 = pattern2.match(data)
    if match1:
        result = match1.group(1)
    elif match2:
        result = match2.group(2)
    else:
        result = None
    
    6

  • cải thiện

    match1 = pattern1.match(data)
    match2 = pattern2.match(data)
    if match1:
        result = match1.group(1)
    elif match2:
        result = match2.group(2)
    else:
        result = None
    
    7

_pydecimal. py

Tránh lồng nhau

group = re.match(data).group(1) if re.match(data) else None
31 và loại bỏ một mức thụt đầu dòng

  • Hiện hành

    match1 = pattern1.match(data)
    match2 = pattern2.match(data)
    if match1:
        result = match1.group(1)
    elif match2:
        result = match2.group(2)
    else:
        result = None
    
    8

  • cải thiện

    match1 = pattern1.match(data)
    match2 = pattern2.match(data)
    if match1:
        result = match1.group(1)
    elif match2:
        result = match2.group(2)
    else:
        result = None
    
    9

sao chép. py

Mã trông đều đặn hơn và tránh nhiều if lồng nhau. (Xem Phụ lục A để biết nguồn gốc của ví dụ này. )

  • Hiện hành

    match1 = pattern1.match(data)
    if match1:
        result = match1.group(1)
    else:
        match2 = pattern2.match(data)
        if match2:
            result = match2.group(2)
        else:
            result = None
    
    0

  • cải thiện

    match1 = pattern1.match(data)
    if match1:
        result = match1.group(1)
    else:
        match2 = pattern2.match(data)
        if match2:
            result = match2.group(2)
        else:
            result = None
    
    1

ngày giờ. py

tz chỉ được sử dụng cho

group = re.match(data).group(1) if re.match(data) else None
32, di chuyển nhiệm vụ của nó bên trong if sẽ giúp hiển thị phạm vi của nó

  • Hiện hành

    match1 = pattern1.match(data)
    if match1:
        result = match1.group(1)
    else:
        match2 = pattern2.match(data)
        if match2:
            result = match2.group(2)
        else:
            result = None
    
    2

  • cải thiện

    match1 = pattern1.match(data)
    if match1:
        result = match1.group(1)
    else:
        match2 = pattern2.match(data)
        if match2:
            result = match2.group(2)
        else:
            result = None
    
    3

sysconfig. py

Gọi

group = re.match(data).group(1) if re.match(data) else None
33 trong điều kiện
group = re.match(data).group(1) if re.match(data) else None
34 và gọi
group = re.match(data).group(1) if re.match(data) else None
35 trên các dòng if làm cho mã gọn hơn mà không khó hiểu hơn

  • Hiện hành

    match1 = pattern1.match(data)
    if match1:
        result = match1.group(1)
    else:
        match2 = pattern2.match(data)
        if match2:
            result = match2.group(2)
        else:
            result = None
    
    4

  • cải thiện

    match1 = pattern1.match(data)
    if match1:
        result = match1.group(1)
    else:
        match2 = pattern2.match(data)
        if match2:
            result = match2.group(2)
        else:
            result = None
    
    5

Đơn giản hóa việc hiểu danh sách

Khả năng hiểu danh sách có thể ánh xạ và lọc hiệu quả bằng cách nắm bắt điều kiện

match1 = pattern1.match(data)
if match1:
    result = match1.group(1)
else:
    match2 = pattern2.match(data)
    if match2:
        result = match2.group(2)
    else:
        result = None
6

Tương tự như vậy, một biểu thức con có thể được sử dụng lại trong biểu thức chính, bằng cách đặt tên cho nó khi sử dụng lần đầu

match1 = pattern1.match(data)
if match1:
    result = match1.group(1)
else:
    match2 = pattern2.match(data)
    if match2:
        result = match2.group(2)
    else:
        result = None
7

Lưu ý rằng trong cả hai trường hợp, biến

group = re.match(data).group(1) if re.match(data) else None
36 bị ràng buộc trong phạm vi chứa (i. e. cùng mức với
group = re.match(data).group(1) if re.match(data) else None
37 hoặc
group = re.match(data).group(1) if re.match(data) else None
38)

Chụp các giá trị điều kiện

Các biểu thức gán có thể được sử dụng để tạo hiệu quả tốt trong tiêu đề của câu lệnh

group = re.match(data).group(1) if re.match(data) else None
31 hoặc
group = re.match(data).group(1) if re.match(data) else None
34

match1 = pattern1.match(data)
if match1:
    result = match1.group(1)
else:
    match2 = pattern2.match(data)
    if match2:
        result = match2.group(2)
    else:
        result = None
8

Riêng với vòng lặp

group = re.match(data).group(1) if re.match(data) else None
34, điều này có thể loại bỏ nhu cầu phải có vòng lặp vô hạn, phép gán và điều kiện. Nó cũng tạo ra sự song song mượt mà giữa một vòng lặp chỉ sử dụng một lệnh gọi hàm làm điều kiện của nó và một vòng lặp sử dụng lệnh đó làm điều kiện của nó nhưng cũng sử dụng giá trị thực

Cái nĩa

Một ví dụ từ thế giới UNIX cấp thấp

match1 = pattern1.match(data)
if match1:
    result = match1.group(1)
else:
    match2 = pattern2.match(data)
    if match2:
        result = match2.group(2)
    else:
        result = None
9

Các đề xuất thay thế bị từ chối

Các đề xuất tương tự như đề xuất này thường xuyên xuất hiện trên ý tưởng python. Dưới đây là một số cú pháp thay thế, một số cú pháp cụ thể để hiểu, đã bị từ chối để ủng hộ cú pháp nêu trên

Thay đổi quy tắc phạm vi để hiểu

Phiên bản trước của PEP này đã đề xuất những thay đổi tinh tế đối với các quy tắc phạm vi để hiểu, để làm cho chúng dễ sử dụng hơn trong phạm vi lớp và để thống nhất phạm vi của “phần có thể lặp lại ngoài cùng” và phần còn lại của phần hiểu. Tuy nhiên, phần này của đề xuất sẽ gây ra sự không tương thích ngược và đã bị rút lại để PEP có thể tập trung vào các biểu thức chuyển nhượng

cách viết thay thế

Nhìn chung ngữ nghĩa giống như đề xuất hiện tại, nhưng được đánh vần khác

  1. group = re.match(data).group(1) if re.match(data) else None
    
    42

    def foo(answer = p := 42):  # INVALID
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...
    
    20

    group = re.match(data).group(1) if re.match(data) else None
    
    42 đã có nghĩa trong các câu lệnh
    group = re.match(data).group(1) if re.match(data) else None
    
    44,
    group = re.match(data).group(1) if re.match(data) else None
    
    45 và
    group = re.match(data).group(1) if re.match(data) else None
    
    46 (với các ngữ nghĩa khác nhau), điều này sẽ tạo ra sự nhầm lẫn không cần thiết hoặc yêu cầu cách viết hoa đặc biệt (e. g. cấm chỉ định trong tiêu đề của các câu lệnh này)

    (Lưu ý rằng

    group = re.match(data).group(1) if re.match(data) else None
    
    47 không chỉ gán giá trị của
    group = re.match(data).group(1) if re.match(data) else None
    
    48 cho
    group = re.match(data).group(1) if re.match(data) else None
    
    49 – nó gọi
    group = re.match(data).group(1) if re.match(data) else None
    
    50 và gán kết quả của việc đó cho
    group = re.match(data).group(1) if re.match(data) else None
    
    49. )

    Các lý do bổ sung để thích

    def foo(answer = p := 42):  # INVALID
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...
    
    59 hơn cách đánh vần này bao gồm

    • Trong
      group = re.match(data).group(1) if re.match(data) else None
      
      53, mục tiêu của bài tập không xuất hiện trước mặt bạn – nó chỉ đọc như
      group = re.match(data).group(1) if re.match(data) else None
      
      54 và nhìn bề ngoài thì quá giống với
      group = re.match(data).group(1) if re.match(data) else None
      
      55
    • Trong tất cả các tình huống khác khi mệnh đề
      group = re.match(data).group(1) if re.match(data) else None
      
      56 được cho phép, ngay cả những người đọc có kỹ năng trung gian cũng có thể dự đoán mệnh đề đó (tuy nhiên là tùy chọn) bởi từ khóa bắt đầu dòng và ngữ pháp liên kết chặt chẽ từ khóa đó với mệnh đề as
      • group = re.match(data).group(1) if re.match(data) else None
        
        57
      • group = re.match(data).group(1) if re.match(data) else None
        
        58
      • group = re.match(data).group(1) if re.match(data) else None
        
        59

      Ngược lại, biểu thức gán không thuộc về

      group = re.match(data).group(1) if re.match(data) else None
      
      31 hoặc
      group = re.match(data).group(1) if re.match(data) else None
      
      34 bắt đầu dòng và chúng tôi cũng cố ý cho phép biểu thức gán trong các ngữ cảnh khác

    • Nhịp điệu song song giữa
      • group = re.match(data).group(1) if re.match(data) else None
        
        62
      • group = re.match(data).group(1) if re.match(data) else None
        
        63

      củng cố nhận dạng trực quan các biểu thức chuyển nhượng

  2. group = re.match(data).group(1) if re.match(data) else None
    
    64

    def foo(answer = p := 42):  # INVALID
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...
    
    21

    Cú pháp này được lấy cảm hứng từ các ngôn ngữ như R và Haskell và một số máy tính có thể lập trình. (Lưu ý rằng không thể có mũi tên hướng sang trái

    group = re.match(data).group(1) if re.match(data) else None
    
    65 trong Python, vì nó sẽ được hiểu là dấu trừ đơn nguyên và nhỏ hơn. ) Cú pháp này có một lợi thế nhỏ so với 'as' ở chỗ nó không xung đột với
    group = re.match(data).group(1) if re.match(data) else None
    
    46,
    group = re.match(data).group(1) if re.match(data) else None
    
    45 và
    group = re.match(data).group(1) if re.match(data) else None
    
    44, nhưng nếu không thì tương đương. Nhưng nó hoàn toàn không liên quan đến cách sử dụng khác của Python đối với
    group = re.match(data).group(1) if re.match(data) else None
    
    69 (chú thích kiểu trả về hàm) và so với
    def foo(answer = p := 42):  # INVALID
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...
    
    59 (xuất hiện từ Algol-58), nó có truyền thống yếu hơn nhiều

  3. Tô điểm cho các tên địa phương có dấu chấm ở đầu

    def foo(answer = p := 42):  # INVALID
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...
    
    22

    Điều này có lợi thế là việc sử dụng bị rò rỉ có thể dễ dàng được phát hiện, loại bỏ một số dạng mơ hồ về cú pháp. Tuy nhiên, đây sẽ là nơi duy nhất trong Python mà phạm vi của một biến được mã hóa thành tên của nó, khiến cho việc tái cấu trúc trở nên khó khăn hơn

  4. Thêm một
    group = re.match(data).group(1) if re.match(data) else None
    
    71 vào bất kỳ câu lệnh nào để tạo các ràng buộc tên cục bộ

    def foo(answer = p := 42):  # INVALID
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...
    
    23

    Thứ tự thực thi bị đảo ngược (phần thân thụt lề được thực hiện trước, tiếp theo là “tiêu đề”). Điều này yêu cầu một từ khóa mới, trừ khi một từ khóa hiện có được sử dụng lại (rất có thể là

    group = re.match(data).group(1) if re.match(data) else None
    
    72). Xem PEP 3150 để thảo luận trước về chủ đề này (với từ khóa được đề xuất là
    group = re.match(data).group(1) if re.match(data) else None
    
    73)

  5. group = re.match(data).group(1) if re.match(data) else None
    
    74

    def foo(answer = p := 42):  # INVALID
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...
    
    24

    Cú pháp này có ít xung đột hơn so với

    group = re.match(data).group(1) if re.match(data) else None
    
    56 (chỉ xung đột với ký hiệu
    group = re.match(data).group(1) if re.match(data) else None
    
    76), nhưng nếu không thì có thể so sánh với nó. Thay vì song song với
    group = re.match(data).group(1) if re.match(data) else None
    
    77 (có thể hữu ích nhưng cũng có thể gây nhầm lẫn), điều này không có sự tương đồng, nhưng gợi nhiều liên tưởng

Câu lệnh điều kiện dạng đặc biệt

Một trong những trường hợp sử dụng phổ biến nhất là câu lệnh

group = re.match(data).group(1) if re.match(data) else None
31 và
group = re.match(data).group(1) if re.match(data) else None
34. Thay vì một giải pháp tổng quát hơn, đề xuất này tăng cường cú pháp của hai câu lệnh này để thêm một phương tiện nắm bắt giá trị được so sánh

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
25

Điều này hoạt động tốt nếu và CHỈ nếu điều kiện mong muốn dựa trên tính trung thực của giá trị được chụp. Do đó, nó hiệu quả đối với các trường hợp sử dụng cụ thể (khớp regex, đọc ổ cắm trả về

group = re.match(data).group(1) if re.match(data) else None
80 khi hoàn thành) và hoàn toàn vô dụng trong các trường hợp phức tạp hơn (e. g. trong đó điều kiện là ________ 381 và bạn muốn nắm bắt giá trị của ________ 382). Việc hiểu danh sách cũng không có ích lợi gì.

Thuận lợi. Không có sự mơ hồ về cú pháp. Nhược điểm. Chỉ trả lời một phần nhỏ các trường hợp sử dụng có thể, ngay cả trong câu lệnh

group = re.match(data).group(1) if re.match(data) else None
31/
group = re.match(data).group(1) if re.match(data) else None
34

Hiểu biết về vỏ bọc đặc biệt

Một trường hợp sử dụng phổ biến khác là hiểu (list/set/dict và genexps). Như trên, các đề xuất đã được thực hiện cho các giải pháp cụ thể để hiểu

  1. group = re.match(data).group(1) if re.match(data) else None
    
    85,
    group = re.match(data).group(1) if re.match(data) else None
    
    86 hoặc
    group = re.match(data).group(1) if re.match(data) else None
    
    87

    def foo(answer = p := 42):  # INVALID
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...
    
    26

    Điều này đưa biểu thức con đến một vị trí ở giữa vòng lặp 'for' và biểu thức. Nó giới thiệu một từ khóa ngôn ngữ bổ sung, tạo ra xung đột. Trong số ba,

    group = re.match(data).group(1) if re.match(data) else None
    
    85 đọc rõ ràng nhất, nhưng cũng có khả năng xung đột lớn nhất (e. g. SQLAlchemy và numpy có các phương thức
    group = re.match(data).group(1) if re.match(data) else None
    
    85, cũng như
    group = re.match(data).group(1) if re.match(data) else None
    
    90 trong thư viện chuẩn)

  2. group = re.match(data).group(1) if re.match(data) else None
    
    91

    def foo(answer = p := 42):  # INVALID
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...
    
    27

    Như trên, nhưng sử dụng lại từ khóa

    group = re.match(data).group(1) if re.match(data) else None
    
    46. Không đọc quá tệ và không cần từ khóa ngôn ngữ bổ sung. Tuy nhiên, bị giới hạn trong khả năng hiểu và không thể dễ dàng chuyển đổi thành cú pháp vòng lặp for "viết tay". Có vấn đề C mà dấu bằng trong một biểu thức hiện có thể tạo liên kết tên, thay vì thực hiện so sánh. Sẽ đặt ra câu hỏi tại sao “với TÊN = EXPR. ” không thể được sử dụng như một tuyên bố của riêng mình

  3. group = re.match(data).group(1) if re.match(data) else None
    
    93

    def foo(answer = p := 42):  # INVALID
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...
    
    28

    Theo tùy chọn 2, nhưng sử dụng

    group = re.match(data).group(1) if re.match(data) else None
    
    56 thay vì dấu bằng. Căn chỉnh về mặt cú pháp với các cách sử dụng khác của
    group = re.match(data).group(1) if re.match(data) else None
    
    56 để liên kết tên, nhưng một phép chuyển đổi đơn giản sang dạng viết tắt của vòng lặp for sẽ tạo ra các ngữ nghĩa khác biệt đáng kể;

Bất kể cách viết được chọn là gì, điều này tạo ra sự khác biệt rõ rệt giữa khả năng hiểu và dạng vòng lặp tay dài không được kiểm soát tương đương. Không còn có thể mở vòng lặp thành dạng câu lệnh mà không làm lại bất kỳ ràng buộc tên nào. Từ khóa duy nhất có thể được sử dụng lại cho nhiệm vụ này là

group = re.match(data).group(1) if re.match(data) else None
46, do đó mang lại cho nó những ngữ nghĩa khác nhau một cách lén lút trong cách hiểu so với trong một câu lệnh;

Giảm quyền ưu tiên của toán tử

Có hai ưu tiên hợp lý cho toán tử

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
59. Hoặc nó nên ràng buộc càng lỏng lẻo càng tốt, cũng như việc gán câu lệnh; . Đặt ưu tiên của nó giữa các toán tử so sánh và số học (chính xác là. chỉ thấp hơn bitwise OR) cho phép hầu hết các cách sử dụng bên trong điều kiện
group = re.match(data).group(1) if re.match(data) else None
34 và
group = re.match(data).group(1) if re.match(data) else None
31 được đánh vần mà không có dấu ngoặc đơn, vì rất có thể bạn muốn nắm bắt giá trị của một thứ gì đó, sau đó thực hiện so sánh trên đó

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
29

Khi find() trả về -1, vòng lặp kết thúc. Nếu

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
59 liên kết lỏng lẻo như
def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
9, thì điều này sẽ thu được kết quả so sánh (thường là
match1 = pattern1.match(data)
match2 = pattern2.match(data)
if match1:
    result = match1.group(1)
elif match2:
    result = match2.group(2)
else:
    result = None
03 hoặc
match1 = pattern1.match(data)
match2 = pattern2.match(data)
if match1:
    result = match1.group(1)
elif match2:
    result = match2.group(2)
else:
    result = None
04), điều này ít hữu ích hơn

Mặc dù hành vi này sẽ thuận tiện trong nhiều tình huống, nhưng nó cũng khó giải thích hơn là “sự. = toán tử hoạt động giống như câu lệnh gán”, và do đó, quyền ưu tiên cho

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
59 đã được đặt gần nhất có thể với quyền ưu tiên của
def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
9 (ngoại trừ việc nó liên kết chặt chẽ hơn dấu phẩy)

Cho phép dấu phẩy ở bên phải

Một số nhà phê bình đã tuyên bố rằng các biểu thức gán nên cho phép các bộ dữ liệu không được mở ngoặc ở bên phải, do đó hai bộ này sẽ tương đương nhau

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
30

(Với phiên bản hiện tại của đề xuất, cái sau sẽ tương đương với

match1 = pattern1.match(data)
match2 = pattern2.match(data)
if match1:
    result = match1.group(1)
elif match2:
    result = match2.group(2)
else:
    result = None
07. )

Tuy nhiên, việc áp dụng lập trường này một cách hợp lý sẽ dẫn đến kết luận rằng khi được sử dụng trong một lệnh gọi hàm, các biểu thức gán cũng liên kết ít chặt chẽ hơn dấu phẩy, vì vậy chúng ta sẽ có sự tương đương khó hiểu sau đây

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
31

Tùy chọn ít gây nhầm lẫn hơn là làm cho

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
59 liên kết chặt chẽ hơn dấu phẩy

Luôn yêu cầu dấu ngoặc đơn

Nó đã được đề xuất là luôn yêu cầu dấu ngoặc đơn xung quanh một biểu thức gán. Điều này sẽ giải quyết nhiều điểm mơ hồ và thực sự sẽ cần có dấu ngoặc đơn để trích xuất biểu thức con mong muốn. Nhưng trong các trường hợp sau, dấu ngoặc đơn thừa cảm thấy dư thừa

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
32

Phản đối thường xuyên

Tại sao không biến bài tập hiện tại thành một biểu thức?

C và các dẫn xuất của nó định nghĩa toán tử

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
9 là một biểu thức, chứ không phải là một câu lệnh như cách của Python. Điều này cho phép thực hiện các bài tập trong nhiều ngữ cảnh hơn, bao gồm cả những ngữ cảnh mà việc so sánh phổ biến hơn. Sự giống nhau về cú pháp giữa
match1 = pattern1.match(data)
match2 = pattern2.match(data)
if match1:
    result = match1.group(1)
elif match2:
    result = match2.group(2)
else:
    result = None
10 và
match1 = pattern1.match(data)
match2 = pattern2.match(data)
if match1:
    result = match1.group(1)
elif match2:
    result = match2.group(2)
else:
    result = None
11 cho thấy ngữ nghĩa khác biệt đáng kể của chúng. Do đó, đề xuất này sử dụng
def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
59 để làm rõ sự khác biệt

Với các biểu thức gán, tại sao phải bận tâm với các câu lệnh gán?

Hai hình thức có tính linh hoạt khác nhau. Toán tử

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
59 có thể được sử dụng bên trong một biểu thức lớn hơn;

Tại sao không sử dụng phạm vi cục bộ và ngăn ngừa ô nhiễm không gian tên?

Các bản sửa đổi trước đây của đề xuất này liên quan đến phạm vi cục bộ (giới hạn trong một câu lệnh), ngăn chặn rò rỉ tên và ô nhiễm không gian tên. Trong khi một lợi thế nhất định trong một số tình huống, điều này làm tăng sự phức tạp trong nhiều tình huống khác và chi phí không được chứng minh bằng lợi ích. Để đơn giản hóa ngôn ngữ, các ràng buộc tên được tạo ở đây hoàn toàn tương đương với bất kỳ ràng buộc tên nào khác, bao gồm cả việc sử dụng ở phạm vi lớp hoặc mô-đun sẽ tạo ra các tên hiển thị bên ngoài. Điều này không khác với các vòng lặp

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
79 hoặc các cấu trúc khác và có thể được giải theo cùng một cách.
match1 = pattern1.match(data)
match2 = pattern2.match(data)
if match1:
    result = match1.group(1)
elif match2:
    result = match2.group(2)
else:
    result = None
17 tên một khi nó không còn cần thiết, hoặc đặt trước nó bằng dấu gạch dưới

(Tác giả muốn cảm ơn Guido van Rossum và Christoph Groth vì những gợi ý của họ để chuyển đề xuất theo hướng này. [2])

Đề xuất hướng dẫn phong cách

Vì phép gán biểu thức đôi khi có thể được sử dụng tương đương với phép gán câu lệnh, câu hỏi nên ưu tiên cái nào sẽ được đặt ra. Vì lợi ích của các hướng dẫn kiểu như PEP 8, hai đề xuất được đề xuất

  1. Nếu có thể sử dụng các câu lệnh gán hoặc biểu thức gán, hãy ưu tiên các câu lệnh;
  2. Nếu việc sử dụng các biểu thức gán sẽ dẫn đến sự mơ hồ về thứ tự thực hiện, hãy cấu trúc lại nó để sử dụng các câu lệnh thay thế

Sự nhìn nhận

Các tác giả muốn cảm ơn Nick Coghlan và Steven D'Aprano vì những đóng góp đáng kể của họ cho đề xuất này và các thành viên của danh sách gửi thư cố vấn cốt lõi để được hỗ trợ thực hiện

Phụ lục A. Phát hiện của Tim Peters

Đây là một bài luận ngắn Tim Peters đã viết về chủ đề này

Tôi không thích các dòng mã “bận rộn” và cũng không thích đặt logic không liên quan đến khái niệm trên một dòng. Vì vậy, ví dụ, thay vì

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
33

tôi thích

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
34

thay thế. Vì vậy, tôi nghi ngờ rằng tôi sẽ tìm thấy một vài nơi mà tôi muốn sử dụng các biểu thức gán. Tôi thậm chí không xem xét chúng cho các dòng đã kéo dài nửa màn hình. Trong các trường hợp khác, phán quyết “không liên quan”

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
35

là một cải tiến lớn so với briefer

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
36

Hai câu lệnh ban đầu đang thực hiện những điều hoàn toàn khác nhau về mặt khái niệm và việc kết hợp chúng lại với nhau là điều điên rồ về mặt khái niệm

Trong các trường hợp khác, việc kết hợp logic liên quan khiến nó khó hiểu hơn, chẳng hạn như viết lại

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
37

như người tóm tắt

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
38

Bài kiểm tra

group = re.match(data).group(1) if re.match(data) else None
34 ở đó quá tinh vi, chủ yếu dựa vào đánh giá nghiêm ngặt từ trái sang phải trong bối cảnh không đoản mạch hoặc xâu chuỗi phương thức. Bộ não của tôi không được kết nối theo cách đó

Nhưng những trường hợp như thế rất hiếm. Liên kết tên rất thường xuyên và “thưa thớt tốt hơn dày đặc” không có nghĩa là “gần như trống rỗng tốt hơn thưa thớt”. Ví dụ: tôi có nhiều hàm trả về

match1 = pattern1.match(data)
match2 = pattern2.match(data)
if match1:
    result = match1.group(1)
elif match2:
    result = match2.group(2)
else:
    result = None
19 hoặc
match1 = pattern1.match(data)
match2 = pattern2.match(data)
if match1:
    result = match1.group(1)
elif match2:
    result = match2.group(2)
else:
    result = None
20 để thông báo "Tôi không có gì hữu ích để trả về trong trường hợp này, nhưng vì điều đó thường được mong đợi nên tôi sẽ không làm phiền bạn bằng một ngoại lệ". Điều này về cơ bản giống như các hàm tìm kiếm biểu thức chính quy trả về
match1 = pattern1.match(data)
match2 = pattern2.match(data)
if match1:
    result = match1.group(1)
elif match2:
    result = match2.group(2)
else:
    result = None
19 khi không khớp. Vì vậy, có rất nhiều mã của mẫu

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
39

Tôi thấy điều đó rõ ràng hơn, và chắc chắn là ít đánh máy và đọc mẫu hơn một chút, vì

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
40

Thật tuyệt khi đánh đổi một lượng nhỏ khoảng trắng ngang để lấy một _dòng_ mã xung quanh khác trên màn hình. Lúc đầu, tôi không quan tâm lắm đến điều này, nhưng nó tăng lên rất thường xuyên và tôi nhanh chóng cảm thấy khó chịu vì thực sự không thể chạy mã briefer. Điều đó làm tôi ngạc nhiên

Có những trường hợp khác mà biểu thức gán thực sự tỏa sáng. Thay vì chọn một mã khác từ mã của tôi, Kirill Balunov đã đưa ra một ví dụ đáng yêu từ hàm

match1 = pattern1.match(data)
match2 = pattern2.match(data)
if match1:
    result = match1.group(1)
elif match2:
    result = match2.group(2)
else:
    result = None
22 của thư viện tiêu chuẩn trong
match1 = pattern1.match(data)
match2 = pattern2.match(data)
if match1:
    result = match1.group(1)
elif match2:
    result = match2.group(2)
else:
    result = None
23

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
41

Sự thụt lề ngày càng tăng gây hiểu lầm về mặt ngữ nghĩa. logic về mặt khái niệm là phẳng, "thử nghiệm đầu tiên thành công sẽ thắng"

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
42

Việc sử dụng các biểu thức gán dễ dàng cho phép cấu trúc trực quan của mã nhấn mạnh tính phẳng về mặt khái niệm của logic;

Một ví dụ nhỏ hơn từ mã của tôi làm tôi thích thú, cả hai đều cho phép đặt logic liên quan vốn có trong một dòng và cho phép loại bỏ mức thụt đầu dòng “nhân tạo” gây phiền nhiễu

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
43

đã trở thành

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
44

group = re.match(data).group(1) if re.match(data) else None
31 đó dài bao nhiêu tùy ý tôi, nhưng vẫn dễ theo dõi

Vì vậy, nói chung, trong hầu hết các dòng ràng buộc một tên, tôi sẽ không sử dụng các biểu thức gán, nhưng vì cấu trúc đó quá thường xuyên, nên tôi sẽ để lại nhiều chỗ. Trong hầu hết các trường hợp sau, tôi đã tìm thấy một chiến thắng nhỏ cộng lại do tần suất xảy ra và trong phần còn lại, tôi tìm thấy một chiến thắng từ trung bình đến lớn. Tôi chắc chắn sẽ sử dụng nó thường xuyên hơn so với

group = re.match(data).group(1) if re.match(data) else None
31 bậc ba, nhưng ít thường xuyên hơn đáng kể so với bài tập tăng cường

Một ví dụ số

Tôi có một ví dụ khác mà tôi khá ấn tượng vào thời điểm đó

Trong đó tất cả các biến là số nguyên dương và a ít nhất lớn bằng căn bậc n của x, thuật toán này trả về giá trị sàn của căn bậc n của x (và gần gấp đôi số bit chính xác trên mỗi lần lặp)

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
45

Không rõ tại sao nó hoạt động, nhưng không rõ ràng hơn ở dạng "vòng lặp rưỡi". Thật khó để chứng minh tính đúng đắn nếu không dựa trên cái nhìn sâu sắc đúng đắn (“trung bình số học - bất đẳng thức trung bình hình học”) và biết một số điều không tầm thường về cách hoạt động của các hàm tầng lồng nhau. Đó là, những thách thức nằm ở toán học, không thực sự ở mã hóa

Nếu bạn biết tất cả những điều đó, thì dạng biểu thức gán dễ dàng được đọc là "trong khi dự đoán hiện tại quá lớn, hãy đoán nhỏ hơn", trong đó "quá lớn?"

Trước mắt tôi, hình thức ban đầu khó hiểu hơn

def foo(answer = p := 42):  # INVALID
    ...
def foo(answer=(p := 42)):  # Valid, though not great style
    ...
46

Phụ lục B. Bản dịch mã thô để hiểu

Phụ lục này cố gắng làm rõ (mặc dù không chỉ định) các quy tắc khi mục tiêu xuất hiện trong một cách hiểu hoặc trong một biểu thức trình tạo. Đối với một số ví dụ minh họa, chúng tôi hiển thị mã gốc, chứa phần hiểu và bản dịch, trong đó phần hiểu đã được thay thế bằng một hàm tạo tương đương cộng với một số giàn giáo

match1 = pattern1.match(data)
match2 = pattern2.match(data)
if match1:
    result = match1.group(1)
elif match2:
    result = match2.group(2)
else:
    result = None
26 tương đương với
match1 = pattern1.match(data)
match2 = pattern2.match(data)
if match1:
    result = match1.group(1)
elif match2:
    result = match2.group(2)
else:
    result = None
27, tất cả các ví dụ này đều sử dụng cách hiểu danh sách mà không làm mất tính tổng quát. Và vì những ví dụ này nhằm làm rõ các trường hợp cạnh của quy tắc, nên chúng không cố trông giống như mã thực

Ghi chú. khả năng hiểu đã được thực hiện thông qua tổng hợp các hàm tạo lồng nhau như trong phụ lục này. Phần mới đang thêm các khai báo thích hợp để thiết lập phạm vi dự định của các mục tiêu biểu thức gán (cùng phạm vi mà chúng giải quyết như thể phép gán được thực hiện trong khối chứa khả năng hiểu ngoài cùng). Đối với các mục đích suy luận kiểu, các phần mở rộng minh họa này không có nghĩa là các mục tiêu của biểu thức gán luôn là Tùy chọn (nhưng chúng chỉ ra phạm vi ràng buộc của mục tiêu)

Hãy bắt đầu với lời nhắc về mã nào được tạo cho biểu thức trình tạo mà không có biểu thức gán

  • Mã gốc (EXPR thường tham khảo VAR)

    def foo(answer = p := 42):  # INVALID
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...
    
    47

  • Bản dịch (đừng lo xung đột tên)

    def foo(answer = p := 42):  # INVALID
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...
    
    48

Hãy thêm một biểu thức gán đơn giản

  • Mã gốc

    def foo(answer = p := 42):  # INVALID
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...
    
    49

  • Dịch

    def foo(answer = p := 42):  # INVALID
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...
    
    50

Hãy thêm một khai báo

match1 = pattern1.match(data)
match2 = pattern2.match(data)
if match1:
    result = match1.group(1)
elif match2:
    result = match2.group(2)
else:
    result = None
28 trong
match1 = pattern1.match(data)
match2 = pattern2.match(data)
if match1:
    result = match1.group(1)
elif match2:
    result = match2.group(2)
else:
    result = None
29

  • Mã gốc

    def foo(answer = p := 42):  # INVALID
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...
    
    51

  • Dịch

    def foo(answer = p := 42):  # INVALID
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...
    
    52

Hoặc thay vào đó, hãy thêm một khai báo

match1 = pattern1.match(data)
match2 = pattern2.match(data)
if match1:
    result = match1.group(1)
elif match2:
    result = match2.group(2)
else:
    result = None
30 vào
match1 = pattern1.match(data)
match2 = pattern2.match(data)
if match1:
    result = match1.group(1)
elif match2:
    result = match2.group(2)
else:
    result = None
29

  • Mã gốc

    def foo(answer = p := 42):  # INVALID
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...
    
    53

  • Dịch

    def foo(answer = p := 42):  # INVALID
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...
    
    54

Cuối cùng, hãy lồng hai cách hiểu

  • Mã gốc

    def foo(answer = p := 42):  # INVALID
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...
    
    55

  • Dịch

    def foo(answer = p := 42):  # INVALID
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...
    
    56

Phụ lục C. Không có thay đổi đối với ngữ nghĩa phạm vi

Bởi vì đó là một điểm gây nhầm lẫn, hãy lưu ý rằng không có gì thay đổi về ngữ nghĩa phạm vi của Python. Các phạm vi chức năng-cục bộ tiếp tục được giải quyết tại thời điểm biên dịch và có phạm vi tạm thời không xác định tại thời điểm chạy (“đóng hoàn toàn”). Thí dụ