Làm thế nào để “và” và “hoặc” hoạt động với các giá trị không phải boolean?


99

Tôi đang cố gắng học python và bắt gặp một số mã hay và ngắn gọn nhưng không hoàn toàn hợp lý

bối cảnh là:

def fn(*args):
    return len(args) and max(args)-min(args)

Tôi hiểu những gì nó đang làm, nhưng tại sao python lại làm điều này - tức là trả về giá trị chứ không phải True / False?

10 and 7-2

trả về 5. Tương tự, việc thay đổi và đến hoặc sẽ dẫn đến thay đổi chức năng. Vì thế

10 or 7 - 2

Sẽ trả lại 10.

Đây có phải là phong cách hợp pháp / đáng tin cậy, hay có bất kỳ lỗi nào về điều này?


1
and(cũng như or) không bị hạn chế làm việc với hoặc trả về các giá trị boolean.
cs95

1
IMNSHO: đó là một cách viết hơi khó hiểu; Tôi không thể nói rõ nếu nó phải trả về một boolean (có giá trị min và max khác biệt) hay một số (sự khác biệt của min và max là gì). Nếu là trường hợp thứ hai, thì cũng có câu hỏi nếu đưa ra sự khác biệt đó của danh sách có độ dài bằng 0 dưới dạng một số. (Thay vì Nonehoặc một ngoại lệ)
ilkkachu

7
Nó hoạt động, như những người khác đã giải thích, tuy nhiên một vấn đề có thể xảy ra là nếu nó trả về, 0bạn không thể biết argsđược là trống hay không nhưng có tất cả các phần tử bằng nhau.
Đặc biệt là Lime

@E SpecialLime: chính xác. Tôi đã đề cập đến nó trong câu trả lời của mình .
Eric Duminil

Câu trả lời:


135

TL; DR

Chúng ta bắt đầu bằng cách tóm tắt hai hành vi của hai toán tử logic andor. Những thành ngữ này sẽ tạo cơ sở cho cuộc thảo luận của chúng tôi dưới đây.

and

Trả về giá trị Falsy đầu tiên nếu có, nếu không, trả về giá trị cuối cùng trong biểu thức.

or

Trả về giá trị Chân lý đầu tiên nếu có, nếu có, trả về giá trị cuối cùng trong biểu thức.

Hành vi cũng được tóm tắt trong tài liệu , đặc biệt là trong bảng này:

nhập mô tả hình ảnh ở đây

Toán tử duy nhất trả về giá trị boolean bất kể toán hạng của nó là nottoán tử.


Đánh giá "độ tin cậy" và "trung thực"

Tuyên bố

len(args) and max(args) - min(args)

Là một rất pythonic ngắn gọn (và có lẽ ít có thể đọc được) cách khác để nói "nếu argskhông được làm rỗng, trả lại kết quả của việc max(args) - min(args)", nếu không quay trở lại 0. Nói chung, nó là một biểu diễn ngắn gọn hơn của một if-elsebiểu thức. Ví dụ,

exp1 and exp2

Nên (đại khái) dịch thành:

r1 = exp1
if r1:
    r1 = exp2

Hoặc, tương đương,

r1 = exp1 if exp1 else exp2

Tương tự,

exp1 or exp2

Tương đương với,

r1 = exp1
if not r1:
    r1 = exp2

Ở đâu exp1exp2là các đối tượng python tùy ý hoặc các biểu thức trả về một số đối tượng. Chìa khóa để hiểu cách sử dụng của toán tử andvà lôgic orở đây là hiểu rằng chúng không bị giới hạn hoạt động trên hoặc trả về giá trị boolean. Bất kỳ đối tượng nào có giá trị truthiness đều có thể được kiểm tra tại đây. Điều này bao gồm int, str, list, dict, tuple, set, NoneType, và người sử dụng định nghĩa các đối tượng. Các quy tắc đoản mạch vẫn được áp dụng.

Nhưng tin cậy là gì?
Nó đề cập đến cách các đối tượng được đánh giá khi được sử dụng trong biểu thức điều kiện. @Patrick Haugh tóm tắt về độ tin cậy trong bài đăng này .

Tất cả các giá trị được coi là "đúng" ngoại trừ các giá trị sau, là "sai":

  • None
  • False
  • 0
  • 0.0
  • 0j
  • Decimal(0)
  • Fraction(0, 1)
  • [] - trống rỗng list
  • {} - trống rỗng dict
  • () - trống rỗng tuple
  • '' - trống rỗng str
  • b'' - trống rỗng bytes
  • set() - trống rỗng set
  • trống rỗng range, giống nhưrange(0)
  • đối tượng mà
    • obj.__bool__() trả lại False
    • obj.__len__() trả lại 0

Giá trị "đúng" sẽ thỏa mãn việc kiểm tra được thực hiện bởi ifhoặc các while câu lệnh. Chúng tôi sử dụng "true" và "falsy" để phân biệt với các boolgiá trị TrueFalse.


Cách andhoạt động

Chúng tôi xây dựng câu hỏi của OP như một cuộc thảo luận về cách các toán tử này trong những trường hợp này.

Cho một hàm với định nghĩa

def foo(*args):
    ...

Làm cách nào để trả lại sự khác biệt giữa giá trị tối thiểu và giá trị lớn nhất trong danh sách không hoặc nhiều đối số?

Tìm giá trị tối thiểu và tối đa rất dễ dàng (sử dụng các hàm có sẵn!). Vấn đề duy nhất ở đây là xử lý thích hợp trường hợp góc mà danh sách đối số có thể trống (ví dụ: gọi foo()). Chúng ta có thể thực hiện cả hai trong một dòng nhờ andtoán tử:

def foo(*args):
     return len(args) and max(args) - min(args)

foo(1, 2, 3, 4, 5)
# 4

foo()
# 0

Kể từ khi andđược sử dụng, biểu thức thứ hai cũng phải được đánh giá nếu biểu thức đầu tiên là True. Lưu ý rằng, nếu biểu thức đầu tiên được đánh giá là đúng, giá trị trả về luôn là kết quả của biểu thức thứ hai . Nếu biểu thức đầu tiên được đánh giá là Falsy, thì kết quả trả về là kết quả của biểu thức đầu tiên.

Trong hàm trên, If foonhận một hoặc nhiều đối số len(args)lớn hơn 0(một số dương), do đó, kết quả trả về là max(args) - min(args). OTOH, nếu không có đối số được truyền, len(args)0đó là Falsy, và 0được trả về.

Lưu ý rằng một cách thay thế để viết hàm này sẽ là:

def foo(*args):
    if not len(args):
        return 0

    return max(args) - min(args)

Hay, ngắn gọn hơn,

def foo(*args):
    return 0 if not args else max(args) - min(args)

Nếu tất nhiên, không có hàm nào trong số này thực hiện bất kỳ kiểm tra kiểu nào, vì vậy trừ khi bạn hoàn toàn tin tưởng đầu vào được cung cấp, đừng dựa vào tính đơn giản của các cấu trúc này.


Cách orhoạt động

Tôi giải thích hoạt động của ormột cách tương tự với một ví dụ có sẵn.

Cho một hàm với định nghĩa

def foo(*args):
    ...

Bạn sẽ hoàn thành như thế nào foođể trả lại tất cả các số 9000?

Chúng tôi sử dụng orđể xử lý trường hợp góc ở đây. Chúng tôi định nghĩa foolà:

def foo(*args):
     return [x for x in args if x > 9000] or 'No number over 9000!'

foo(9004, 1, 2, 500)
# [9004]

foo(1, 2, 3, 4)
# 'No number over 9000!'

foothực hiện lọc trên danh sách để giữ lại tất cả các số 9000. Nếu tồn tại bất kỳ số nào như vậy, kết quả của việc hiểu danh sách là một danh sách không trống, là Truthy, vì vậy nó được trả về (viết tắt ở đây). Nếu không tồn tại những con số như vậy, thì kết quả của danh sách comp []là Falsy. Vì vậy, biểu thức thứ hai bây giờ được đánh giá (một chuỗi không rỗng) và được trả về.

Sử dụng các điều kiện, chúng ta có thể viết lại hàm này thành,

def foo(*args):
    r = [x for x in args if x > 9000]
    if not r:
        return 'No number over 9000!' 

    return r

Như trước đây, cấu trúc này linh hoạt hơn trong việc xử lý lỗi.


33
Nó không phải là "con trăn" để hy sinh tất cả sự rõ ràng cho ngắn gọn, mà tôi nghĩ là trường hợp ở đây. Nó không phải là một cấu trúc đơn giản.
DBedrenko

11
Tôi nghĩ rằng một người nên lưu ý rằng các biểu thức điều kiện Python đã làm cho cú pháp này ít phổ biến hơn. Tôi chắc chắn thích max (args) - min (args) nếu len (args) khác 0 so với bản gốc.
richardb

3
Một số khác phổ biến mà là khó hiểu lúc đầu, được gán một giá trị nếu không tồn tại: "some_var = arg hoặc 3"
Erik

12
@Baldrickk trước khi mọi người bắt đầu sử dụng cú pháp này để ủng hộ các toán tử bậc ba, hãy nhớ rằng khi nói đến biểu thức điều kiện n-ary, các toán tử bậc ba có thể nhanh chóng vượt ra khỏi tầm tay. Ví dụ, if ... else (if ... else (if ... else (if ... else ...)))cũng có thể được viết lại ... and ... and ... and ... and ...và tại thời điểm đó, nó thực sự trở nên khó tranh luận về khả năng đọc cho cả hai trường hợp.
cs95

4
Việc hy sinh sự rõ ràng cho ngắn gọn không phải là điều khó hiểu, nhưng điều này không làm như vậy. Đó là một thành ngữ nổi tiếng. Đó là một thành ngữ bạn phải học, giống như bất kỳ thành ngữ nào khác, nhưng nó hầu như không 'hy sinh sự rõ ràng'.
Miles Rout vào

18

Trích dẫn từ Tài liệu Python

Lưu ý rằng không phải andvà cũng không or hạn chế các giá trị họ trở về FalseTrue, nhưng thay vì trả lại lập luận đánh giá cuối cùng . Điều này đôi khi hữu ích, ví dụ: nếu slà một chuỗi cần được thay thế bằng giá trị mặc định nếu nó trống, biểu thức s or 'foo'mang lại giá trị mong muốn.

Vì vậy, đây là cách Python được thiết kế để đánh giá các biểu thức boolean và tài liệu trên cung cấp cho chúng ta cái nhìn sâu sắc về lý do tại sao họ làm như vậy.

Để nhận một giá trị boolean, chỉ cần gõ nó.

return bool(len(args) and max(args)-min(args))

Tại sao?

Chập mạch.

Ví dụ:

2 and 3 # Returns 3 because 2 is Truthy so it has to check 3 too
0 and 3 # Returns 0 because 0 is Falsey and there's no need to check 3 at all

Điều tương tự cũng xảy orra, nghĩa là, nó sẽ trả về biểu thức là Truthy ngay khi nó tìm thấy nó, vì việc đánh giá phần còn lại của biểu thức là dư thừa.

Thay vì trả về Hardcore Truehoặc False, Python trả về Truthy hoặc Falsey , chúng sẽ đánh giá thành Truehoặc False. Bạn có thể sử dụng biểu thức như hiện tại và nó sẽ vẫn hoạt động.


Để biết TruthyFalsey là gì , hãy xem câu trả lời của Patrick Haugh


7

hoặc thực hiện logic boolean, nhưng chúng trả về một trong các giá trị thực khi chúng đang so sánh. Khi sử dụng , các giá trị được đánh giá trong ngữ cảnh boolean từ trái sang phải. 0, '', [], (), {}None là false trong ngữ cảnh boolean; mọi thứ khác đều đúng.

Nếu tất cả các giá trị đều đúng trong ngữ cảnh boolean trả về giá trị cuối cùng.

>>> 2 and 5
5
>>> 2 and 5 and 10
10

Nếu bất kỳ giá trị nào là false trong ngữ cảnh boolean trả về giá trị false đầu tiên.

>>> '' and 5
''
>>> 2 and 0 and 5
0

Vì vậy, mã

return len(args) and max(args)-min(args)

trả về giá trị max(args)-min(args)khi có args khác thì nó trả về giá trị len(args)là 0.


5

Đây có phải là phong cách hợp pháp / đáng tin cậy, hay có bất kỳ lỗi nào về điều này?

Đây là hợp pháp, nó là một đánh giá ngắn mạch nơi giá trị cuối cùng được trả về.

Bạn cung cấp một ví dụ tốt. Hàm sẽ trả về 0nếu không có đối số nào được truyền và mã không phải kiểm tra trường hợp đặc biệt không có đối số nào được truyền.

Một cách khác để sử dụng điều này là đặt mặc định Không có đối số cho một nguyên thủy có thể thay đổi, như một danh sách trống:

def fn(alist=None):
    alist = alist or []
    ....

Nếu một số giá trị không đúng sự thật được chuyển đến alistnó thì mặc định là một danh sách trống, một cách tiện dụng để tránh một ifcâu lệnh và lỗi đối số mặc định có thể thay đổi


3

Gotchas

Vâng, có một vài vấn đề.

fn() == fn(3) == fn(4, 4)

Đầu tiên, nếu fntrả về 0, bạn không thể biết nó được gọi mà không có bất kỳ tham số nào, với một tham số hay với nhiều tham số bằng nhau:

>>> fn()
0
>>> fn(3)
0
>>> fn(3, 3, 3)
0

Nghĩa fnlà gì?

Sau đó, Python là một ngôn ngữ động. Nó không được chỉ định ở bất kỳ nơi nào làm gì fn, đầu vào của nó phải là gì và đầu ra của nó sẽ như thế nào. Do đó, việc đặt tên chính xác cho hàm thực sự quan trọng. Tương tự, các đối số không cần phải được gọi args. delta(*numbers)hoặc calculate_range(*numbers)có thể mô tả tốt hơn những gì chức năng phải làm.

Lỗi đối số

Cuối cùng, andtoán tử logic được cho là ngăn hàm không thành công nếu được gọi mà không có bất kỳ đối số nào. Tuy nhiên, nó vẫn không thành công nếu một số đối số không phải là số:

>>> fn('1')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in fn
TypeError: unsupported operand type(s) for -: 'str' and 'str'
>>> fn(1, '2')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in fn
TypeError: '>' not supported between instances of 'str' and 'int'
>>> fn('a', 'b')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in fn
TypeError: unsupported operand type(s) for -: 'str' and 'str'

Có thể thay thế

Đây là cách viết hàm theo kiểu "Dễ xin tha hơn xin phép". nguyên tắc :

def delta(*numbers):
    try:
        return max(numbers) - min(numbers)
    except TypeError:
        raise ValueError("delta should only be called with numerical arguments") from None
    except ValueError:
        raise ValueError("delta should be called with at least one numerical argument") from None

Ví dụ:

>>> delta()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in delta
ValueError: delta should be called with at least one numerical argument
>>> delta(3)
0
>>> delta('a')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in delta
ValueError: delta should only be called with numerical arguments
>>> delta('a', 'b')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in delta
ValueError: delta should only be called with numerical arguments
>>> delta('a', 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in delta
ValueError: delta should only be called with numerical arguments
>>> delta(3, 4.5)
1.5
>>> delta(3, 5, 7, 2)
5

Nếu bạn thực sự không muốn tăng một ngoại lệ khi deltađược gọi mà không có bất kỳ đối số nào, bạn có thể trả về một số giá trị không thể thực hiện được (ví dụ: -1hoặc None):

>>> def delta(*numbers):
...     try:
...         return max(numbers) - min(numbers)
...     except TypeError:
...         raise ValueError("delta should only be called with numerical arguments") from None
...     except ValueError:
...         return -1 # or None
... 
>>> 
>>> delta()
-1

0

Đây có phải là phong cách hợp pháp / đáng tin cậy, hay có bất kỳ lỗi nào về điều này?

Tôi muốn thêm vào câu hỏi này rằng nó không chỉ hợp pháp và đáng tin cậy mà còn rất thực tế. Đây là một ví dụ đơn giản:

>>>example_list = []
>>>print example_list or 'empty list'
empty list

Do đó bạn thực sự có thể sử dụng nó theo cách có lợi cho mình. Để được rõ ràng đây là cách tôi nhìn thấy nó:

Or nhà điều hành

orToán tử của Python trả về giá trị Truth-y đầu tiên hoặc giá trị cuối cùng và dừng lại

And nhà điều hành

andToán tử của Python trả về giá trị False-y đầu tiên hoặc giá trị cuối cùng và dừng

Đằng sau hậu trường

Trong python, tất cả các số được hiểu là Truengoại trừ 0. Do đó, nói:

0 and 10 

giống như:

False and True

Đó là rõ ràng False. Do đó, hợp lý là nó trả về 0


0

Đúng. Đây là hành vi chính xác của và so sánh.

Ít nhất bằng Python, A and Blợi nhuận Bnếu Ađược về cơ bản Truebao gồm nếu Alà không Null, KHÔNG NoneKHÔNG một container rỗng (ví dụ như một sản phẩm nào list, dict, vv). AIFF được trả về về Acơ bản là FalsehoặcNone hoặc Rỗng hoặc Null.

Mặt khác, A or Blợi nhuận Anếu Ađược về cơ bản Truebao gồm nếu Alà không Null, KHÔNG NoneKHÔNG một container rỗng (ví dụ như một sản phẩm nào list, dict, vv), nếu không nó sẽ trả về B.

Thật dễ dàng để không nhận thấy (hoặc bỏ qua) hành vi này bởi vì, trong Python, bất kỳ non-null đối tượng không trống nào được đánh giá là True đều được coi như một boolean.

Ví dụ: tất cả những điều sau đây sẽ in "True"

if [102]: 
    print "True"
else: 
    print "False"

if "anything that is not empty or None": 
    print "True"
else: 
    print "False"

if {1, 2, 3}: 
    print "True"
else: 
    print "False"

Mặt khác, tất cả những điều sau sẽ in "Sai"

if []: 
    print "True"
else: 
    print "False"

if "": 
    print "True"
else: 
    print "False"

if set ([]): 
    print "True"
else: 
    print "False"

Cảm ơn bạn. Tôi muốn viết Avề cơ bản True. Đã sửa.
emmanuelsa,

0

hiểu một cách đơn giản,

VÀ: if first_val is False return first_val else second_value

ví dụ:

1 and 2 # here it will return 2 because 1 is not False

nhưng,

0 and 2 # will return 0 because first value is 0 i.e False

và => nếu ai sai, nó sẽ là sai. nếu cả hai đều đúng thì chỉ nó mới trở thành sự thật

HOẶC LÀ : if first_val is False return second_val else first_value

lý do là, nếu đầu tiên là sai, nó kiểm tra xem 2 là đúng hay không.

ví dụ:

1 or 2 # here it will return 1 because 1 is not False

nhưng,

0 or 2 # will return 2 because first value is 0 i.e False

hoặc => nếu ai sai thì sẽ đúng. vì vậy nếu giá trị đầu tiên là sai thì không có vấn đề gì 2 giá trị giả sử là. vì vậy nó trả về giá trị thứ hai mà nó có thể là.

nếu ai là sự thật thì nó sẽ trở thành sự thật. nếu cả hai đều sai thì nó sẽ trở thành sai.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.