Sự khác biệt giữa hai dòng mã này là gì:
if not x == 'val':
và
if x != 'val':
Là một trong những hiệu quả hơn so với khác?
Nó sẽ tốt hơn để sử dụng
if x == 'val':
pass
else:
Sự khác biệt giữa hai dòng mã này là gì:
if not x == 'val':
và
if x != 'val':
Là một trong những hiệu quả hơn so với khác?
Nó sẽ tốt hơn để sử dụng
if x == 'val':
pass
else:
Câu trả lời:
Sử dụng dis
để xem mã byte được tạo cho hai phiên bản:
not ==
4 0 LOAD_FAST 0 (foo)
3 LOAD_FAST 1 (bar)
6 COMPARE_OP 2 (==)
9 UNARY_NOT
10 RETURN_VALUE
!=
4 0 LOAD_FAST 0 (foo)
3 LOAD_FAST 1 (bar)
6 COMPARE_OP 3 (!=)
9 RETURN_VALUE
Cái sau có ít hoạt động hơn, và do đó có khả năng sẽ hiệu quả hơn một chút.
Điều này đã được chỉ ra trong các cam kết (cảm ơn, @Quincunx ) rằng nơi bạn có if foo != bar
so với if not foo == bar
số lượng hoạt động là hoàn toàn giống nhau, đó chỉ là những COMPARE_OP
thay đổi và POP_JUMP_IF_TRUE
chuyển sang POP_JUMP_IF_FALSE
:
not ==
:
2 0 LOAD_FAST 0 (foo)
3 LOAD_FAST 1 (bar)
6 COMPARE_OP 2 (==)
9 POP_JUMP_IF_TRUE 16
!=
2 0 LOAD_FAST 0 (foo)
3 LOAD_FAST 1 (bar)
6 COMPARE_OP 3 (!=)
9 POP_JUMP_IF_FALSE 16
Trong trường hợp này, trừ khi có sự khác biệt về số lượng công việc cần thiết cho mỗi so sánh, không chắc bạn sẽ thấy bất kỳ sự khác biệt nào về hiệu suất.
Tuy nhiên, lưu ý rằng hai phiên bản sẽ không luôn giống nhau về mặt logic , vì nó sẽ phụ thuộc vào việc triển khai __eq__
và __ne__
cho các đối tượng được đề cập. Theo tài liệu mô hình dữ liệu :
Không có mối quan hệ ngụ ý giữa các nhà khai thác so sánh. Sự thật
x==y
không ngụ ý đóx!=y
là sai.
Ví dụ:
>>> class Dummy(object):
def __eq__(self, other):
return True
def __ne__(self, other):
return True
>>> not Dummy() == Dummy()
False
>>> Dummy() != Dummy()
True
Cuối cùng, và có lẽ quan trọng nhất là: nói chung, nơi hai là logic giống nhau, x != y
là nhiều hơn nữa có thể đọc hơnnot x == y
.
__eq__
không phù hợp __ne__
đều bị phá vỡ.
not x == y
có thêm một hướng dẫn. Khi tôi đặt mã vào một if
, hóa ra cả hai đều có cùng một số hướng dẫn, chỉ một cái có POP_JUMP_IF_TRUE
và cái kia POP_JUMP_IF_FALSE
(đó là sự khác biệt duy nhất giữa chúng, ngoài việc sử dụng khác nhau COMPARE_OP
). Khi tôi biên dịch mã mà không có if
s, tôi đã nhận được những gì bạn có.
==
và !=
không loại trừ lẫn nhau là một triển khai giống như SQL liên quan đến null
các giá trị. Trong SQL null
không trả lại true
để !=
so sánh với bất kỳ giá trị khác, vì vậy việc triển khai python giao diện SQL cũng có thể có cùng một vấn đề.
not ==
và !=
, nó dường như là phần thú vị nhất trong câu trả lời của tôi! Tôi không nghĩ rằng đây là nơi để sống nếu, tại sao và khi điều đó có ý nghĩa - xem ví dụ: Tại sao Python có một __ne__
phương thức toán tử thay vì chỉ __eq__
?
@jonrsharpe có một lời giải thích tuyệt vời về những gì đang xảy ra. Tôi nghĩ rằng tôi chỉ thể hiện sự khác biệt về thời gian khi chạy mỗi trong số 3 tùy chọn 10.000.000 lần (đủ để hiển thị một chút khác biệt).
Mã đã được sử dụng:
def a(x):
if x != 'val':
pass
def b(x):
if not x == 'val':
pass
def c(x):
if x == 'val':
pass
else:
pass
x = 1
for i in range(10000000):
a(x)
b(x)
c(x)
Và kết quả hồ sơ cProfile:
Vì vậy, chúng ta có thể thấy rằng có một sự khác biệt rất nhỏ trong khoảng ~ 0,7% giữa if not x == 'val':
và if x != 'val':
. Trong số này, if x != 'val':
là nhanh nhất.
Tuy nhiên, đáng ngạc nhiên nhất, chúng ta có thể thấy rằng
if x == 'val':
pass
else:
trên thực tế là nhanh nhất và nhịp đập if x != 'val':
~ 0,3%. Điều này không dễ đọc lắm, nhưng tôi đoán nếu bạn muốn cải thiện hiệu suất không đáng kể, người ta có thể đi theo con đường này.
Trong lần đầu tiên, Python phải thực thi thêm một thao tác hơn mức cần thiết (thay vì chỉ kiểm tra không bằng nó phải kiểm tra xem có đúng không nếu nó bằng nhau, do đó thêm một thao tác nữa). Không thể nói sự khác biệt từ một lần thực hiện, nhưng nếu chạy nhiều lần, lần thứ hai sẽ hiệu quả hơn. Nhìn chung, tôi sẽ sử dụng cái thứ hai, nhưng về mặt toán học thì chúng giống nhau
>>> from dis import dis
>>> dis(compile('not 10 == 20', '', 'exec'))
1 0 LOAD_CONST 0 (10)
3 LOAD_CONST 1 (20)
6 COMPARE_OP 2 (==)
9 UNARY_NOT
10 POP_TOP
11 LOAD_CONST 2 (None)
14 RETURN_VALUE
>>> dis(compile('10 != 20', '', 'exec'))
1 0 LOAD_CONST 0 (10)
3 LOAD_CONST 1 (20)
6 COMPARE_OP 3 (!=)
9 POP_TOP
10 LOAD_CONST 2 (None)
13 RETURN_VALUE
Ở đây bạn có thể thấy rằng not x == y
có một hướng dẫn nhiều hơn x != y
. Vì vậy, sự khác biệt hiệu suất sẽ rất nhỏ trong hầu hết các trường hợp trừ khi bạn đang thực hiện hàng triệu so sánh và thậm chí sau đó điều này có thể sẽ không phải là nguyên nhân của nút cổ chai.
Một lưu ý bổ sung, vì các câu trả lời khác trả lời chính xác câu hỏi của bạn, là nếu một lớp chỉ xác định __eq__()
và không __ne__()
, thì bạn COMPARE_OP (!=)
sẽ chạy __eq__()
và phủ nhận nó. Vào thời điểm đó, tùy chọn thứ ba của bạn có thể sẽ hiệu quả hơn một chút, nhưng chỉ nên được xem xét nếu bạn CẦN tốc độ, vì nó khó hiểu một cách nhanh chóng.
Đó là về cách đọc của bạn. not
Toán tử là động, đó là lý do tại sao bạn có thể áp dụng nó trong
if not x == 'val':
Nhưng !=
có thể được đọc trong một bối cảnh tốt hơn như là một toán tử mà ngược lại với những gì ==
làm.
not
toán tử là năng động" ?
Tôi muốn mở rộng về nhận xét dễ đọc của tôi ở trên.
Một lần nữa, tôi hoàn toàn đồng ý với khả năng đọc ghi đè các mối quan tâm khác (không đáng kể về hiệu suất).
Điều tôi muốn chỉ ra là bộ não diễn giải "tích cực" nhanh hơn "tiêu cực". Ví dụ: "dừng" so với "không đi" (một ví dụ khá tệ hại do sự khác biệt về số lượng từ).
Vì vậy, đưa ra một sự lựa chọn:
if a == b
(do this)
else
(do that)
thích hợp hơn cho chức năng tương đương:
if a != b
(do that)
else
(do this)
Ít khả năng đọc / dễ hiểu dẫn đến nhiều lỗi hơn. Có lẽ không phải trong mã hóa ban đầu, nhưng việc bảo trì (không thông minh như bạn!) Thay đổi ...