I have a python script that can receive either zero or three command line arguments. (Either it runs on default behavior or needs all three values specified.)
What’s the ideal syntax for something like:
if a and (not b or not c) or b and (not a or not c) or c and (not b or not a):
UPDATE: as correctly said by Volatility and Supr, you can apply De Morgan’s law and obtain equivalent:
if (a or b or c) and not (a and b and c):
My advice is to use whichever form is more significant to you and to other programmers. The first means “there is something false, but also something true”, the second “There is something true, but not everything”. If I were to optimize or do this in hardware, I would choose the second, here just choose the most readable (also taking in consideration the conditions you will be testing and their names). I picked the first.
This question already had many highly upvoted answers and an accepted answer, but all of them so far were distracted by various ways to express the boolean problem and missed a crucial point:
I have a python script that can receive either zero or three command
line arguments. (Either it runs on default behavior or needs all three
values specified)
This logic should not be the responsibility of your code in the first place, rather it should be handled by argparse module. Don’t bother writing a complex if statement, instead prefer to setup your argument parser something like this:
#!/usr/bin/env python
import argparse as ap
parser = ap.ArgumentParser()
parser.add_argument('--foo', nargs=3, default=['x', 'y', 'z'])
args = parser.parse_args()
print(args.foo)
And yes, it should be an option not a positional argument, because it is after all optional.
edited: To address the concern of LarsH in the comments, below is an example of how you could write it if you were certain you wanted the interface with either 3 or 0 positional args. I am of the opinion that the previous interface is better style, because optional arguments should be options, but here’s an alternative approach for the sake of completeness. Note the overriding kwarg usage when creating your parser, because argparse will auto-generate a misleading usage message otherwise!
#!/usr/bin/env python
import argparse as ap
parser = ap.ArgumentParser(usage='%(prog)s [-h] [a b c]\n')
parser.add_argument('abc', nargs='*', help='specify 3 or 0 items', default=['x', 'y', 'z'])
args = parser.parse_args()
if len(args.abc) != 3:
parser.error('expected 3 arguments')
print(args.abc)
Here are some usage examples:
# default case
wim@wim-zenbook:/tmp$ ./three_or_none.py
['x', 'y', 'z']
# explicit case
wim@wim-zenbook:/tmp$ ./three_or_none.py 1 2 3
['1', '2', '3']
# example failure mode
wim@wim-zenbook:/tmp$ ./three_or_none.py 1 2
usage: three_or_none.py [-h] [a b c]
three_or_none.py: error: expected 3 arguments
conds = iter([a, b, c])
if any(conds) and not any(conds):
# okay...
I think this should short-circuit fairly efficiently
Explanation
By making conds an iterator, the first use of any will short circuit and leave the iterator pointing to the next element if any item is true; otherwise, it will consume the entire list and be False. The next any takes the remaining items in the iterable, and makes sure than there aren’t any other true values… If there are, the whole statement can’t be true, thus there isn’t one unique element (so short circuits again). The last any will either return False or will exhaust the iterable and be True.
note: the above checks if only a single condition is set
If you want to check if one or more items, but not every item is set, then you can use:
The word “but” usually implies a conjunction, in other words “and”. Furthermore, “all of them” translates to a conjunction of conditions: this condition, and that condition, and other condition. The “not” inverts that entire conjunction.
I do not agree that the accepted answer. The author neglected to apply the most straightforward interpretation to the specification, and neglected to apply De Morgan’s Law to simplify the expression to fewer operators:
not a or not b or not c -> not (a and b and c)
while claiming that the answer is a “minimal form”.
To be clear, you want to made your decision based on how much of the parameters are logical TRUE (in case of string arguments – not empty)?
argsne = (1 if a else 0) + (1 if b else 0) + (1 if c else 0)
Then you made a decision:
if ( 0 < argsne < 3 ):
doSth()
Now the logic is more clear.
回答 8
为何不仅仅计算它们呢?
import sys
a = sys.argv
if len(a)=1:# No arguments were given, the program name count as oneelif len(a)=4:# Three arguments were givenelse:# another amount of arguments was given
import sys
a = sys.argv
if len(a) = 1 :
# No arguments were given, the program name count as one
elif len(a) = 4 :
# Three arguments were given
else :
# another amount of arguments was given
回答 9
如果您不介意有点晦涩难懂,则可以简单地滚动一下0 < (a + b + c) < 3,true如果您有一个和两个真实的陈述,则返回,如果全部为假或没有为假,则返回false。
If you don’t mind being a bit cryptic you can simly roll with 0 < (a + b + c) < 3 which will return true if you have between one and two true statements and false if all are false or none is false.
This also simplifies if you use functions to evaluate the bools as you only evaluate the variables once and which means you can write the functions inline and do not need to temporarily store the variables. (Example: 0 < ( a(x) + b(x) + c(x) ) < 3.)
As I understand it, you have a function that receives 3 arguments, but if it does not it will run on default behavior. Since you have not explained what should happen when 1 or 2 arguments are supplied I will assume it should simply do the default behavior. In which case, I think you will find the following answer very advantageous:
def method(a=None, b=None, c=None):
if all([a, b, c]):
# received 3 arguments
else:
# default behavior
However, if you want 1 or 2 arguments to be handled differently:
def method(a=None, b=None, c=None):
args = [a, b, c]
if all(args):
# received 3 arguments
elif not any(args):
# default behavior
else:
# some args (raise exception?)
note: This assumes that “False” values will not be passed into this method.
If you work with an iterator of conditions, it could be slow to access. But you don’t need to access each element more than once, and you don’t always need to read all of it. Here’s a solution that will work with infinite generators:
#!/usr/bin/env python3
from random import randint
from itertools import tee
def generate_random():
while True:
yield bool(randint(0,1))
def any_but_not_all2(s): # elegant
t1, t2 = tee(s)
return False in t1 and True in t2 # could also use "not all(...) and any(...)"
def any_but_not_all(s): # simple
hadFalse = False
hadTrue = False
for i in s:
if i:
hadTrue = True
else:
hadFalse = True
if hadTrue and hadFalse:
return True
return False
r1, r2 = tee(generate_random())
assert any_but_not_all(r1)
assert any_but_not_all2(r2)
assert not any_but_not_all([True, True])
assert not any_but_not_all2([True, True])
assert not any_but_not_all([])
assert not any_but_not_all2([])
assert any_but_not_all([True, False])
assert any_but_not_all2([True, False])
def _any_but_not_all(first, iterable):#doing dirty work
bool_first=bool(first)for x in iterable:if bool(x)isnot bool_first:returnTruereturnFalsedef any_but_not_all(arg,*args):#takes any amount of args convertable to boolreturn _any_but_not_all(arg, args)def v_any_but_not_all(iterable):#takes iterable or iterator
iterator=iter(iterable)return _any_but_not_all(next(iterator), iterator)
When every given bool is True, or when every given bool is False…
they all are equal to each other!
So, we just need to find two elements which evaluates to different bools
to know that there is at least one True and at least one False.
My short solution:
not bool(a)==bool(b)==bool(c)
I belive it short-circuits, cause AFAIK a==b==c equals a==b and b==c.
My generalized solution:
def _any_but_not_all(first, iterable): #doing dirty work
bool_first=bool(first)
for x in iterable:
if bool(x) is not bool_first:
return True
return False
def any_but_not_all(arg, *args): #takes any amount of args convertable to bool
return _any_but_not_all(arg, args)
def v_any_but_not_all(iterable): #takes iterable or iterator
iterator=iter(iterable)
return _any_but_not_all(next(iterator), iterator)
I wrote also some code dealing with multiple iterables, but I deleted it from here because I think it’s pointless. It’s however still available here.
回答 14
基本上,这是“一些(但不是全部)”功能(与any()和all()内置功能相比)。
这意味着结果之间应该有Falses 和True s。因此,您可以执行以下操作:
some =lambda ii: frozenset(bool(i)for i in ii).issuperset((True,False))# one way to test this is...
test =lambda iterable:(any(iterable)and(not all(iterable)))# see also http://stackoverflow.com/a/16522290/541412# Some test cases...assert(some(())==False)# all() is true, and any() is falseassert(some((False,))==False)# any() is falseassert(some((True,))==False)# any() and all() are trueassert(some((False,False))==False)assert(some((True,True))==False)assert(some((True,False))==True)assert(some((False,True))==True)
This is basically a “some (but not all)” functionality (when contrasted with the any() and all() builtin functions).
This implies that there should be Falses andTrues among the results. Therefore, you can do the following:
some = lambda ii: frozenset(bool(i) for i in ii).issuperset((True, False))
# one way to test this is...
test = lambda iterable: (any(iterable) and (not all(iterable))) # see also http://stackoverflow.com/a/16522290/541412
# Some test cases...
assert(some(()) == False) # all() is true, and any() is false
assert(some((False,)) == False) # any() is false
assert(some((True,)) == False) # any() and all() are true
assert(some((False,False)) == False)
assert(some((True,True)) == False)
assert(some((True,False)) == True)
assert(some((False,True)) == True)
One advantage of this code is that you only need to iterate once through the resulting (booleans) items.
One disadvantage is that all these truth-expressions are always evaluated, and do not do short-circuiting like the or/and operators.
I have been studying Python, and I read a chapter which describes the None value, but unfortunately this book isn’t very clear at some points. I thought that I would find the answer to my question, if I share it there.
I want to know what the None value is and what do you use it for?
And also, I don’t get this part of the book:
Assigning a value of None to a variable is one way to reset it to
its original, empty state.
What does that mean?
The answers were great, although I didn’t understand most of answers due to my low knowledge of the computer world (I haven’t learned about classes, objects, etc.). What does this sentence mean?
Assigning a value of None to a variable is one way to reset it
to its original, empty state.
Final:
Finally I’ve got my answer from looking to different answers. I must appreciate all the people who put their times to help me (especially Martijn Pieters and DSM), and I wish that I could choose all answers as the best, but the selection is limited to one. All of the answers were great.
Martijn’s answer explains what None is in Python, and correctly states that the book is misleading. Since Python programmers as a rule would never say
Assigning a value of None to a variable is one way to reset it to
its original, empty state.
it’s hard to explain what Briggs means in a way which makes sense and explains why no one here seems happy with it. One analogy which may help:
In Python, variable names are like stickers put on objects. Every sticker has a unique name written on it, and it can only be on one object at a time, but you could put more than one sticker on the same object, if you wanted to. When you write
F = "fork"
you put the sticker “F” on a string object "fork". If you then write
F = None
you move the sticker to the None object.
What Briggs is asking you to imagine is that you didn’t write the sticker "F", there was already an F sticker on the None, and all you did was move it, from None to "fork". So when you type F = None, you’re “reset[ting] it to its original, empty state”, if we decided to treat None as meaning empty state.
I can see what he’s getting at, but that’s a bad way to look at it. If you start Python and type print(F), you see
>>> print(F)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'F' is not defined
and that NameError means Python doesn’t recognize the name F, because there is no such sticker. If Briggs were right and F = None resets F to its original state, then it should be there now, and we should see
>>> print(F)
None
like we do after we type F = None and put the sticker on None.
So that’s all that’s going on. In reality, Python comes with some stickers already attached to objects (built-in names), but others you have to write yourself with lines like F = "fork" and A = 2 and c17 = 3.14, and then you can stick them on other objects later (like F = 10 or F = None; it’s all the same.)
Briggs is pretending that all possible stickers you might want to write were already stuck to the None object.
None is just a value that commonly is used to signify ’empty’, or ‘no value here’. It is a signal object; it only has meaning because the Python documentation says it has that meaning.
There is only one copy of that object in a given Python interpreter session.
If you write a function, and that function doesn’t use an explicit return statement, None is returned instead, for example. That way, programming with functions is much simplified; a function always returns something, even if it is only that one None object.
You can test for it explicitly:
if foo is None:
# foo is set to None
if bar is not None:
# bar is set to something *other* than None
Another use is to give optional parameters to functions an ’empty’ default:
def spam(foo=None):
if foo is not None:
# foo was specified, do something clever!
The function spam() has a optional argument; if you call spam() without specifying it, the default value None is given to it, making it easy to detect if the function was called with an argument or not.
Other languages have similar concepts. SQL has NULL; JavaScript has undefinedandnull, etc.
Note that in Python, variables exist by virtue of being used. You don’t need to declare a variable first, so there are no really empty variables in Python. Setting a variable to None is then not the same thing as setting it to a default empty value; None is a value too, albeit one that is often used to signal emptyness. The book you are reading is misleading on that point.
TypeError: can't set attributes of built-in/extension type 'NoneType'
AttributeError: 'NoneType' object has no attribute 'somefield'
AttributeError: 'NoneType' object has no attribute 'somefield'
The sole value of types.NoneType. None is frequently used to represent
the absence of a value, as when default arguments are not passed to a
function.
Changed in version 2.4: Assignments to None are illegal and raise a
SyntaxError.
Note The names None and debug cannot be reassigned (assignments to
them, even as an attribute name, raise SyntaxError), so they can be
considered “true” constants.
Let’s confirm the type of None first
print type(None)
print None.__class__
Output
<type 'NoneType'>
<type 'NoneType'>
Basically, NoneType is a data type just like int, float, etc. You can check out the list of default types available in Python in 8.15. types — Names for built-in types.
And, None is an instance of NoneType class. So we might want to create instances of None ourselves. Let’s try that
print types.IntType()
print types.NoneType()
Output
0
TypeError: cannot create 'NoneType' instances
So clearly, cannot create NoneType instances. We don’t have to worry about the uniqueness of the value None.
Let’s check how we have implemented None internally.
TypeError: can't set attributes of built-in/extension type 'NoneType'
AttributeError: 'NoneType' object has no attribute 'somefield'
AttributeError: 'NoneType' object has no attribute 'somefield'
The above seen statements produce these error messages, respectively. It means that, we cannot create attributes dynamically on a None instance.
Let us check what happens when we assign something None. As per the documentation, it should throw a SyntaxError. It means, if we assign something to None, the program will not be executed at all.
None = 1
Output
SyntaxError: cannot assign to None
We have established that
None is an instance of NoneType
None cannot have new attributes
Existing attributes of None cannot be changed.
We cannot create other instances of NoneType
We cannot even change the reference to None by assigning values to it.
So, as mentioned in the documentation, None can really be considered as a true constant.
The book you refer to is clearly trying to greatly simplify the meaning of None. Python variables don’t have an initial, empty state – Python variables are bound (only) when they’re defined. You can’t create a Python variable without giving it a value.
>>> print(x)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined
>>> def test(x):
... print(x)
...
>>> test()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: test() takes exactly 1 argument (0 given)
>>> def test():
... print(x)
...
>>> test()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in test
NameError: global name 'x' is not defined
but sometimes you want to make a function mean different things depending on whether a variable is defined or not. You can create an argument with a default value of None:
>>> def test(x=None):
... if x is None:
... print('no x here')
... else:
... print(x)
...
>>> test()
no x here
>>> test('x!')
x!
The fact that this value is the special None value is not terribly important in this case. I could’ve used any default value:
>>> def test(x=-1):
... if x == -1:
... print('no x here')
... else:
... print(x)
...
>>> test()
no x here
>>> test('x!')
x!
…but having None around gives us two benefits:
We don’t have to pick a special value like -1 whose meaning is unclear, and
Our function may actually need to handle -1 as a normal input.
>>> test(-1)
no x here
oops!
So the book is a little misleading mostly in its use of the word reset – assigning None to a name is a signal to a programmer that that value isn’t being used or that the function should behave in some default way, but to reset a value to its original, undefined state you must use the del keyword:
>>> x = 3
>>> x
3
>>> del x
>>> x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined
Now try to guess output of above list. Well, the answer is surprisingly as below:
list1 = [10, 'a']
list2 = [123]
list3 = [10, 'a']
But Why?
Many will mistakenly expect list1 to be equal to [10] and list3 to be equal to [‘a’], thinking that the list argument will be set to its default value of [] each time extendList is called.
However, what actually happens is that the new default list is created only once when the function is defined, and that same list is then used subsequently whenever extendList is invoked without a list argument being specified. This is because expressions in default arguments are calculated when the function is defined, not when it’s called.
list1 and list3 are therefore operating on the same default list, whereas list2 is operating on a separate list that it created (by passing its own empty list as the value for the list parameter).
‘None’ the savior: (Modify example above to produce desired behavior)
def extendList(val, list=None):
if list is None:
list = []
list.append(val)
return list
list1 = extendList(10)
list2 = extendList(123,[])
list3 = extendList('a')
print "list1 = %s" % list1
print "list2 = %s" % list2
print "list3 = %s" % list3
With this revised implementation, the output would be:
list1 = [10]
list2 = [123]
list3 = ['a']
Note – Example credit to toptal.com
回答 5
None是一个单例对象(意味着只有一个None),在语言和库中的许多地方都用于表示缺少其他值。
例如: if d是一个字典,如果存在d.get(k)则返回d[k],但None如果d没有key 则返回k。
None is a singleton object (meaning there is only one None), used in many places in the language and library to represent the absence of some other value.
For example: if d is a dictionary, d.get(k) will return d[k] if it exists, but None if d has no key k.
All of these are good answers but I think there’s more to explain why None is useful.
Imagine you collecting RSVPs to a wedding. You want to record whether each person will attend. If they are attending, you set person.attending = True. If they are not attending you set person.attending = False. If you have not received any RSVP, then person.attending = None. That way you can distinguish between no information – None – and a negative answer.
回答 7
我喜欢代码示例(以及水果),所以让我告诉你
apple ="apple"print(apple)>>> apple
apple =Noneprint(apple)>>>None
I love code examples (as well as fruit), so let me show you
apple = "apple"
print(apple)
>>> apple
apple = None
print(apple)
>>> None
None means nothing, it has no value.
None evaluates to False.
回答 8
largest=none
smallest =none
WhileTrue:
num =raw_input ('enter a number ')if num =="done ":breaktry:
inp =int (inp)except:Print'Invalid input'if largest is none :
largest=inp
elif inp>largest:
largest =none
print'maximum', largest
if smallest is none:
smallest =none
elif inp<smallest :
smallest =inp
print'minimum', smallest
print'maximum, minimum, largest, smallest
largest=none
smallest =none
While True :
num =raw_input ('enter a number ')
if num =="done ": break
try :
inp =int (inp)
except:
Print'Invalid input'
if largest is none :
largest=inp
elif inp>largest:
largest =none
print 'maximum', largest
if smallest is none:
smallest =none
elif inp<smallest :
smallest =inp
print 'minimum', smallest
print 'maximum, minimum, largest, smallest
I want to check my environment for the existence of a variable, say "FOO", in Python. For this purpose, I am using the os standard library. After reading the library’s documentation, I have figured out 2 ways to achieve my goal:
Method 1:
if "FOO" in os.environ:
pass
Method 2:
if os.getenv("FOO") is not None:
pass
I would like to know which method, if either, is a good/preferred conditional and why.
Use the first; it directly tries to check if something is defined in environ. Though the second form works equally well, it’s lacking semantically since you get a value back if it exists and only use it for a comparison.
You’re trying to see if something is present inenviron, why would you get just to compare it and then toss it away?
That’s exactly what getenv does:
Get an environment variable, return None if it doesn’t exist. The
optional second argument can specify an alternate default.
(this also means your check could just be if getenv("FOO"))
you don’t want to get it, you want to check for it’s existence.
Either way, getenv is just a wrapper around environ.get but you don’t see people checking for membership in mappings with:
from os import environ
if environ.get('Foo') is not None:
To summarize, use:
if "FOO" in os.environ:
pass
if you just want to check for existence, while, use getenv("FOO") if you actually want to do something with the value you might get.
There is a case for either solution, depending on what you want to do conditional on the existence of the environment variable.
Case 1
When you want to take different actions purely based on the existence of the environment variable, without caring for its value, the first solution is the best practice. It succinctly describes what you test for: is ‘FOO’ in the list of environment variables.
if 'KITTEN_ALLERGY' in os.environ:
buy_puppy()
else:
buy_kitten()
Case 2
When you want to set a default value if the value is not defined in the environment variables the second solution is actually useful, though not in the form you wrote it:
server = os.getenv('MY_CAT_STREAMS', 'youtube.com')
or perhaps
server = os.environ.get('MY_CAT_STREAMS', 'youtube.com')
Note that if you have several options for your application you might want to look into ChainMap, which allows to merge multiple dicts based on keys. There is an example of this in the ChainMap documentation:
import os
MANDATORY_ENV_VARS =["FOO","BAR"]for var in MANDATORY_ENV_VARS:if var notin os.environ:raiseEnvironmentError("Failed because {} is not set.".format(var))
In case you want to check if multiple env variables are not set, you can do the following:
import os
MANDATORY_ENV_VARS = ["FOO", "BAR"]
for var in MANDATORY_ENV_VARS:
if var not in os.environ:
raise EnvironmentError("Failed because {} is not set.".format(var))
My comment might not be relevant to the tags given. However, I was lead to this page from my search.
I was looking for similar check in R and I came up the following with the help of @hugovdbeg post. I hope it would be helpful for someone who is looking for similar solution in R
No, it’s not possible (at least not with arbitrary statements), nor is it desirable. Fitting everything on one line would most likely violate PEP-8 where it is mandated that lines should not exceed 80 characters in length.
It’s also against the Zen of Python: “Readability counts”. (Type import this at the Python prompt to read the whole thing).
You can use a ternary expression in Python, but only for expressions, not for statements:
>>> a = "Hello" if foo() else "Goodbye"
Edit:
Your revised question now shows that the three statements are identical except for the value being assigned. In that case, a chained ternary operator does work, but I still think that it’s less readable:
>>> i=100
>>> a = 1 if i<100 else 2 if i>100 else 0
>>> a
0
>>> i=101
>>> a = 1 if i<100 else 2 if i>100 else 0
>>> a
2
>>> i=99
>>> a = 1 if i<100 else 2 if i>100 else 0
>>> a
1
回答 1
如果您仅在不同情况下需要不同的表达式,那么这可能对您有用:
expr1 if condition1 else expr2 if condition2 else expr
If you only need different expressions for different cases then this may work for you:
expr1 if condition1 else expr2 if condition2 else expr
For example:
a = "neg" if b<0 else "pos" if b>0 else "zero"
回答 2
只需在else语句中嵌套另一个if子句。但这并没有使它看起来更漂亮。
>>> x=5>>> x if x>0else("zero"if x==0else"invalid value")5>>> x =0>>> x if x>0else("zero"if x==0else"invalid value")'zero'>>> x =-1>>> x if x>0else("zero"if x==0else"invalid value")'invalid value'
Just nest another if clause in the else statement. But that doesn’t make it look any prettier.
>>> x=5
>>> x if x>0 else ("zero" if x==0 else "invalid value")
5
>>> x = 0
>>> x if x>0 else ("zero" if x==0 else "invalid value")
'zero'
>>> x = -1
>>> x if x>0 else ("zero" if x==0 else "invalid value")
'invalid value'
回答 3
尽管有其他一些答案:是的,这是可能的:
if expression1:
statement1
elif expression2:
statement2
else:
statement3
转换为以下一种衬纸:
statement1 if expression1 else(statement2 if expression2 else statement3)
It also depends on the nature of your expressions. The general advice on the other answers of “not doing it” is quite valid for generic statements and generic expressions.
But if all you need is a “dispatch” table, like, calling a different function depending on the value of a given option, you can put the functions to call inside a dictionary.
People have already mentioned ternary expressions. Sometimes with a simple conditional assignment as your example, it is possible to use a mathematical expression to perform the conditional assignment. This may not make your code very readable, but it does get it on one fairly short line. Your example could be written like this:
x = 2*(i>100) | 1*(i<100)
The comparisons would be True or False, and when multiplying with numbers would then be either 1 or 0. One could use a + instead of an | in the middle.
回答 9
在三元运算符是一个简洁的表达的最好方式。语法为variable = value_1 if condition else value_2。因此,对于您的示例,您必须两次应用三元运算符:
i =23# set any value for i
x =2if i >100else1if i <100else0
The ternary operator is the best way to a concise expression. The syntax is variable = value_1 if condition else value_2. So, for your example, you must apply the ternary operator twice:
i = 23 # set any value for i
x = 2 if i > 100 else 1 if i < 100 else 0
回答 10
您可以使用嵌套三元if语句。
# if-else ternary construct
country_code ='USA'
is_USA =Trueif country_code =='USA'elseFalseprint('is_USA:', is_USA)# if-elif-else ternary construct# Create function to avoid repeating code.def get_age_category_name(age):
age_category_name ='Young'if age <=40else('Middle Aged'if age >40and age <=65else'Senior')return age_category_name
print(get_age_category_name(25))print(get_age_category_name(50))print(get_age_category_name(75))
one two three que
0 10 1.2 4.2 10
1 15 70 0.03 NaN
2 8 5 0 NaN
If you have more than one condition, then you could use np.select instead.
For example, if you wish df['que'] to equal df['two'] when df['one'] < df['two'], then
One way is to use a Boolean series to index the column df['one']. This gives you a new column where the True entries have the same value as the same row as df['one'] and the False values are NaN.
The Boolean series is just given by your if statement (although it is necessary to use & instead of and):
>>> df['que'] = df['one'][(df['one'] >= df['two']) & (df['one'] <= df['three'])]
>>> df
one two three que
0 10 1.2 4.2 10
1 15 70 0.03 NaN
2 8 5 0 NaN
If you want the NaN values to be replaced by other values, you can use the fillna method on the new column que. I’ve used 0 instead of the empty string here:
>>> df['que'] = df['que'].fillna(0)
>>> df
one two three que
0 10 1.2 4.2 10
1 15 70 0.03 0
2 8 5 0 0
This construct is done a lot, but since it goes over every condition before it hits the else I have the feeling this is not very efficient, let alone Pythonic. On the other hand, it does need to know if any of those conditions are met, so it should test it anyway.
Does anybody know if and how this could be done more efficiently or is this simply the best possible way to do it?
something ='something'for i in xrange(1000000):if something =='this':
the_thing =1elif something =='that':
the_thing =2elif something =='there':
the_thing =3else:
the_thing =4
2.py
something ='something'
options ={'this':1,'that':2,'there':3}for i in xrange(1000000):
the_thing = options.get(something,4)
3.py
something ='something'
options ={'this':1,'that':2,'there':3}for i in xrange(1000000):if something in options:
the_thing = options[something]else:
the_thing =4
4.py
from collections import defaultdict
something ='something'
options = defaultdict(lambda:4,{'this':1,'that':2,'there':3})for i in xrange(1000000):
the_thing = options[something]
…looks like it ought to be faster, but it’s actually slower than the if … elif … else construct, because it has to call a function, which can be a significant performance overhead in a tight loop.
Consider these examples…
1.py
something = 'something'
for i in xrange(1000000):
if something == 'this':
the_thing = 1
elif something == 'that':
the_thing = 2
elif something == 'there':
the_thing = 3
else:
the_thing = 4
2.py
something = 'something'
options = {'this': 1, 'that': 2, 'there': 3}
for i in xrange(1000000):
the_thing = options.get(something, 4)
3.py
something = 'something'
options = {'this': 1, 'that': 2, 'there': 3}
for i in xrange(1000000):
if something in options:
the_thing = options[something]
else:
the_thing = 4
4.py
from collections import defaultdict
something = 'something'
options = defaultdict(lambda: 4, {'this': 1, 'that': 2, 'there': 3})
for i in xrange(1000000):
the_thing = options[something]
Option #4 does have the additional memory overhead of adding a new item for every distinct key miss, so if you’re expecting an unbounded number of distinct key misses, I’d go with option #3, which is still a significant improvement on the original construct.
from random import shuffle
def doThis():passdef doThat():passdef doThere():passdef doSomethingElse():pass
options ={'this':doThis,'that':doThat,'there':doThere}
lis = range(10**4)+ options.keys()*100
shuffle(lis)def get():for x in lis:
options.get(x, doSomethingElse)()def key_in_dic():for x in lis:if x in options:
options[x]()else:
doSomethingElse()def if_else():for x in lis:if x =='this':
doThis()elif x =='that':
doThat()elif x =='there':
doThere()else:
doSomethingElse()
结果:
>>>from so import*>>>%timeit get()100 loops, best of 3:5.06 ms per loop
>>>%timeit key_in_dic()100 loops, best of 3:3.55 ms per loop
>>>%timeit if_else()100 loops, best of 3:6.42 ms per loop
对于10**5不存在的密钥和100个有效密钥:
>>>%timeit get()10 loops, best of 3:84.4 ms per loop
>>>%timeit key_in_dic()10 loops, best of 3:50.4 ms per loop
>>>%timeit if_else()10 loops, best of 3:104 ms per loop
因此,对于普通字典而言,在key in options这里使用键是最有效的方法:
if key in options:
options[key]()else:
doSomethingElse()
If something is not found in the options dict then dict.get will return the default value doThisMostOfTheTime
Some timing comparisons:
Script:
from random import shuffle
def doThis():pass
def doThat():pass
def doThere():pass
def doSomethingElse():pass
options = {'this':doThis, 'that':doThat, 'there':doThere}
lis = range(10**4) + options.keys()*100
shuffle(lis)
def get():
for x in lis:
options.get(x, doSomethingElse)()
def key_in_dic():
for x in lis:
if x in options:
options[x]()
else:
doSomethingElse()
def if_else():
for x in lis:
if x == 'this':
doThis()
elif x == 'that':
doThat()
elif x == 'there':
doThere()
else:
doSomethingElse()
Results:
>>> from so import *
>>> %timeit get()
100 loops, best of 3: 5.06 ms per loop
>>> %timeit key_in_dic()
100 loops, best of 3: 3.55 ms per loop
>>> %timeit if_else()
100 loops, best of 3: 6.42 ms per loop
For 10**5 non-existent keys and 100 valid keys::
>>> %timeit get()
10 loops, best of 3: 84.4 ms per loop
>>> %timeit key_in_dic()
10 loops, best of 3: 50.4 ms per loop
>>> %timeit if_else()
10 loops, best of 3: 104 ms per loop
So, for a normal dictionary checking for the key using key in options is the most efficient way here:
if key in options:
options[key]()
else:
doSomethingElse()
回答 2
可以使用pypy吗?
保留原始代码,但在pypy上运行可使我的速度提高50倍。
CPython:
matt$ python
Python2.6.8(unknown,Nov262012,10:25:03)[GCC 4.2.1CompatibleAppleClang3.0(tags/Apple/clang-211.12)] on darwin
Type"help","copyright","credits"or"license"for more information.>>>>>>from timeit import timeit
>>> timeit("""
... if something == 'this': pass
... elif something == 'that': pass
... elif something == 'there': pass
... else: pass
... ""","something='foo'", number=10000000)1.728302001953125
pypy:
matt$ pypy
Python2.7.3(daf4a1b651e0,Dec072012,23:00:16)[PyPy2.0.0-beta1 with GCC 4.2.1] on darwin
Type"help","copyright","credits"or"license"for more information.And now for something completely different:``a 10th of forever is1h45''>>>>>>>>from timeit import timeit
>>>> timeit("""
.... if something == 'this': pass
.... elif something == 'that': pass
.... elif something == 'there': pass
.... else: pass
.... ""","something='foo'", number=10000000)0.03306388854980469
Keeping your original code but running it on pypy gives a 50x speed-up for me.
CPython:
matt$ python
Python 2.6.8 (unknown, Nov 26 2012, 10:25:03)
[GCC 4.2.1 Compatible Apple Clang 3.0 (tags/Apple/clang-211.12)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> from timeit import timeit
>>> timeit("""
... if something == 'this': pass
... elif something == 'that': pass
... elif something == 'there': pass
... else: pass
... """, "something='foo'", number=10000000)
1.728302001953125
Pypy:
matt$ pypy
Python 2.7.3 (daf4a1b651e0, Dec 07 2012, 23:00:16)
[PyPy 2.0.0-beta1 with GCC 4.2.1] on darwin
Type "help", "copyright", "credits" or "license" for more information.
And now for something completely different: ``a 10th of forever is 1h45''
>>>>
>>>> from timeit import timeit
>>>> timeit("""
.... if something == 'this': pass
.... elif something == 'that': pass
.... elif something == 'there': pass
.... else: pass
.... """, "something='foo'", number=10000000)
0.03306388854980469
回答 3
这里是将动态条件转换为字典的if的示例。
selector ={lambda d: datetime(2014,12,31)>= d :'before2015',lambda d: datetime(2015,1,1)<= d < datetime(2016,1,1):'year2015',lambda d: datetime(2016,1,1)<= d < datetime(2016,12,31):'year2016'}def select_by_date(date, selector=selector):
selected =[selector[x]for x in selector if x(date)]or['after2016']return selected[0]
I need to set the value of num1 to 20 if someBoolValue is True; and do nothing otherwise. So, here is my code for that
num1 = 20 if someBoolValue else num1
Is there someway I could avoid the ...else num1 part to make it look cleaner? An equivalent to
if someBoolValue:
num1 = 20
I tried replacing it with ...else pass like this: num1=20 if someBoolValue else pass. All I got was syntax error. Nor I could just omit the ...else num1 part.
I don’t think this is possible in Python, since what you’re actually trying to do probably gets expanded to something like this:
num1 = 20 if someBoolValue else num1
If you exclude else num1, you’ll receive a syntax error since I’m quite sure that the assignment must actually return something.
As others have already mentioned, you could do this, but it’s bad because you’ll probably just end up confusing yourself when reading that piece of code the next time:
if someBoolValue: num1=20
I’m not a big fan of the num1 = someBoolValue and 20 or num1 for the exact same reason. I have to actually think twice on what that line is doing.
The best way to actually achieve what you want to do is the original version:
if someBoolValue:
num1 = 20
The reason that’s the best verison is because it’s very obvious what you want to do, and you won’t confuse yourself, or whoever else is going to come in contact with that code later.
Also, as a side note, num1 = 20 if someBoolValue is valid Ruby code, because Ruby works a bit differently.
But don’t do that. This style is normally not expected. People prefer the longer form for clarity and consistency.
if someBoolValue:
num1 = 20
(Equally, camel caps should be avoided. So rather use some_bool_value.)
Note that an in-line expressionsome_value if predicate without an else part does not exist because there would not be a return value if the predicate were false. However, expressions must have a clearly defined return value in all cases. This is different from usage as in, say, Ruby or Perl.
回答 3
您可以使用以下之一:
(falseVal, trueVal)[TEST]
TEST and trueVal or falseVal
No. I guess you were hoping that something like num1 = 20 if someBoolValue would work, but it doesn’t. I think the best way is with the if statement as you have written it:
if someBoolValue:
num1 = 20
回答 5
num1 =10+10*(someBoolValue isTrue)
那是我新的最终答案。先前的答复如下,并且对于所述问题过大。Getting_too_clever == not Good。这是先前的答案…如果您想为Truecond 添加一件事,为False:添加另一件事,还是不错的:
That’s my new final answer.
Prior answer was as follows and was overkill for the stated problem. Getting_too_clever == not Good. Here’s the prior answer… still good if you want add one thing for True cond and another for False:
num1 = 10 + (0,10)[someBoolValue is True]
You mentioned num1 would already have a value that should be left alone. I assumed num1 = 10 since that’s the first statement of the post, so the operation to get to 20 is to add 10.
If one line code is definitely going to happen for you, Python 3.8 introduces assignment expressions affectionately known as “the walrus operator”.
:=
someBoolValue and (num := 20)
The 20 will be assigned to num if the first boolean expression is True. The assignment must be inside parentheses here otherwise you will get a syntax error.
num = 10
someBoolValue = True
someBoolValue and (num := 20)
print(num) # 20
num = 10
someBoolValue = False
someBoolValue and (num := 20)
print(num) # 10
回答 9
对于来自Google的未来旅行者来说,这是一种新方法(可从python 3.8开始使用):
b =1if a := b:# this section is only reached if b is not 0 or false.# Also, a is set to bprint(a, b)
Errors should never pass silently.
Unless explicitly silenced.
Should using a try instead of an if be interpreted as an error passing silently? And if so, are you explicitly silencing it by using it in this way, therefore making it OK?
I’m not referring to situations where you can only do things 1 way; for example:
try:
import foo
except ImportError:
import baz
回答 0
你应该更喜欢try/except过if/else如果结果
加快速度(例如,通过防止额外的查询)
更清晰的代码(行数更少/更易于阅读)
通常,它们并存。
加速
如果尝试通过以下方式在长列表中查找元素:
try:
x = my_list[index]exceptIndexError:
x ='NO_ABC'
当index可能在列表中并且通常不引发IndexError 时,尝试除外是最好的选择。这样,您就可以避免进行额外的查询if index < len(my_list)。
Python鼓励使用异常,您可以使用Dive Into Python中的短语来处理异常。您的示例不仅(优美地)处理异常,而不是让其静默通过,而且仅在未找到索引的特殊情况下才发生异常(因此,单词异常!)。
#check whether int conversion will raise an errorifnot isinstance(s, str)ornot s.isdigit():returnNoneelif len(s)>10:#too many digits for int conversionreturnNoneelse:return int(s)
You should prefer try/except over if/else if that results in
speed-ups (for example by preventing extra lookups)
cleaner code (fewer lines/easier to read)
Often, these go hand-in-hand.
speed-ups
In the case of trying to find an element in a long list by:
try:
x = my_list[index]
except IndexError:
x = 'NO_ABC'
the try, except is the best option when the index is probably in the list and the IndexError is usually not raised. This way you avoid the need for an extra lookup by if index < len(my_list).
Python encourages the use of exceptions, which you handle is a phrase from Dive Into Python. Your example not only handles the exception (gracefully), rather than letting it silently pass, also the exception occurs only in the exceptional case of index not being found (hence the word exception!).
cleaner code
The official Python Documentation mentions EAFP: Easier to ask for forgiveness than permission and Rob Knight notes that catching errors rather than avoiding them, can result in cleaner, easier to read code. His example says it like this:
Worse (LBYL ‘look before you leap’):
#check whether int conversion will raise an error
if not isinstance(s, str) or not s.isdigit():
return None
elif len(s) > 10: #too many digits for int conversion
return None
else:
return int(s)
Better (EAFP: Easier to ask for forgiveness than permission):
In this particular case, you should use something else entirely:
x = myDict.get("ABC", "NO_ABC")
In general, though: If you expect the test to fail frequently, use if. If the test is expensive relative to just trying the operation and catching the exception if it fails, use try. If neither one of these conditions applies, go with whatever reads easier.
Using try and except directly rather than inside an if guard should always be done if there is any possibility of a race condition. For example, if you want to ensure that a directory exists, do not do this:
import os, sys
if not os.path.isdir('foo'):
try:
os.mkdir('foo')
except OSError, e
print e
sys.exit(1)
If another thread or process creates the directory between isdir and mkdir, you’ll exit. Instead, do this:
import os, sys, errno
try:
os.mkdir('foo')
except OSError, e
if e.errno != errno.EEXIST:
print e
sys.exit(1)
That will only exit if the ‘foo’ directory can’t be created.
If it’s trivial to check whether something will fail before you do it, you should probably favor that. After all, constructing exceptions (including their associated tracebacks) takes time.
Exceptions should be used for:
things that are unexpected, or…
things where you need to jump more than one level of logic (e.g. where a break doesn’t get you far enough), or…
things where you don’t know exactly what is going to be handling the exception ahead of time, or…
things where checking ahead of time for failure is expensive (relative to just attempting the operation)
Note that oftentimes, the real answer is “neither” – for instance, in your first example, what you really should do is just use .get() to provide a default:
As the other posts mention, it depends on the situation. There are a few dangers with using try/except in place of checking the validity of your data in advance, especially when using it on bigger projects.
The code in the try block may have a chance to wreak all sorts of havoc before the exception is caught – if you proactively check beforehand with an if statement you can avoid this.
If the code called in your try block raises a common exception type, like TypeError or ValueError, you may not actually catch the same exception you were expecting to catch – it may be something else that raise the same exception class before or after even getting to the line where your exception may be raised.
e.g., suppose you had:
try:
x = my_list[index_list[3]]
except IndexError:
x = 'NO_ABC'
The IndexError says nothing about whether it occurred when trying to get an element of index_list or my_list.
Should using a try instead of an if be interpreted as an error passing silently? And if so, are you explicitly silencing it by using it in this way, therefore making it OK?
Using try is acknowledging that an error may pass, which is the opposite of having it pass silently. Using except is causing it not to pass at all.
Using try: except: is preferred in cases where if: else: logic is more complicated. Simple is better than complex; complex is better than complicated; and it’s easier to ask for forgiveness than permission.
What “errors should never pass silently” is warning about, is the case where code could raise an exception that you know about, and where your design admits the possibility, but you haven’t designed in a way to deal with the exception. Explicitly silencing an error, in my view, would be doing something like pass in an except block, which should only be done with an understanding that “doing nothing” really is the correct error handling in the particular situation. (This is one of the few times where I feel like a comment in well-written code is probably really needed.)
However, in your particular example, neither is appropriate:
x = myDict.get('ABC', 'NO_ABC')
The reason everyone is pointing this out – even though you acknowledge your desire to understand in general, and inability to come up with a better example – is that equivalent side-steps actually exist in quite a lot of cases, and looking for them is the first step in solving the problem.
Whenever you use try/except for control flow, ask yourself:
Is it easy to see when the try block succeeds and when it fails?
Are you aware of all side effects inside the try block?
Are you aware of all cases in which the try block throws the exception?
If the implementation of the try block changes, will your control flow still behave as expected?
If the answer to one or more of these questions is ‘no’, there might be a lot of forgiveness to ask for; most likely from your future self.
An example.
I recently saw code in a larger project that looked like this:
try:
y = foo(x)
except ProgrammingError:
y = bar(x)
Talking to the programmer it turned that the intended control flow was:
If x is an integer, do y = foo(x).
If x is a list of integers, do y = bar(x).
This worked because foo made a database query and the query would be successful if x was an integer and throw a ProgrammingError if x was a list.
Using try/except is a bad choice here:
The name of the exception, ProgrammingError, does not give away the actual problem (that x is not an integer), which makes it difficult to see what is going on.
The ProgrammingError is raised during a database call, which wastes time. Things would get truly horrible if it turned out that foo writes something to the database before it throws an exception, or alters the state of some other system.
It is unclear if ProgrammingError is only raised when x is a list of integers. Suppose for instance that there is a typo in foo‘s database query. This might also raise a ProgrammingError. The consequence is that bar(x) is now also called when x is an integer. This might raise cryptic exceptions or produce unforeseeable results.
The try/except block adds a requirement to all future implementations of foo. Whenever we change foo, we must now think about how it handles lists and make sure that it throws a ProgrammingError and not, say, an AttributeError or no error at all.
In your particular case, as others stated, you should use dict.get():
get(key[, default])
Return the value for key if key is in the
dictionary, else default. If default is not given, it defaults to
None, so that this method never raises a KeyError.
def check_all_conditions():
x = check_size()if x:return x
x = check_color()if x:return x
x = check_tone()if x:return x
x = check_flavor()if x:return xreturnNone
I have a method that calls 4 other methods in sequence to check for specific conditions, and returns immediately (not checking the following ones) whenever one returns something Truthy.
def check_all_conditions():
x = check_size()
if x:
return x
x = check_color()
if x:
return x
x = check_tone()
if x:
return x
x = check_flavor()
if x:
return x
return None
This seems like a lot of baggage code. Instead of each 2-line if statement, I’d rather do something like:
x and return x
But that is invalid Python. Am I missing a simple, elegant solution here? Incidentally, in this situation, those four check methods may be expensive, so I do not want to call them multiple times.
回答 0
您可以使用循环:
conditions =(check_size, check_color, check_tone, check_flavor)for condition in conditions:
result = condition()if result:return result
conditions =(check_size, check_color, check_tone, check_flavor)
checks =(condition()for condition in conditions)return next((check for check in checks if check),None)
conditions = (check_size, check_color, check_tone, check_flavor)
checks = (condition() for condition in conditions)
return next((check for check in checks if check), None)
This is a variant of Martijns first example. It also uses the “collection of callables”-style in order to allow short-circuiting.
Instead of a loop you can use the builtin any.
conditions = (check_size, check_color, check_tone, check_flavor)
return any(condition() for condition in conditions)
Note that any returns a boolean, so if you need the exact return value of the check, this solution will not work. any will not distinguish between 14, 'red', 'sharp', 'spicy' as return values, they will all be returned as True.
回答 6
您是否考虑过只写if x: return x一行?
def check_all_conditions():
x = check_size()if x:return x
x = check_color()if x:return x
x = check_tone()if x:return x
x = check_flavor()if x:return x
returnNone
Have you considered just writing if x: return x all on one line?
def check_all_conditions():
x = check_size()
if x: return x
x = check_color()
if x: return x
x = check_tone()
if x: return x
x = check_flavor()
if x: return x
return None
This isn’t any less repetitive than what you had, but IMNSHO it reads quite a bit smoother.
Note that although this implementation is probably the clearest, it evaluates all the checks even if the first one is True.
If you really need to stop at the first failed check, consider using reduce which is made to convert a list to a simple value:
def check_all_conditions():
checks = [check_size, check_color, check_tone, check_flavor]
return reduce(lambda a, f: a or f(), checks, False)
reduce(function, iterable[, initializer]) : Apply function of two
arguments cumulatively to the items of iterable, from left to right,
so as to reduce the iterable to a single value. The left argument, x,
is the accumulated value and the right argument, y, is the update
value from the iterable. If the optional initializer is present, it is
placed before the items of the iterable in the calculation
In your case:
lambda a, f: a or f() is the function that checks that either the accumulator a or the current check f() is True. Note that if a is True, f() won’t be evaluated.
checks contains check functions (the f item from the lambda)
False is the initial value, otherwise no check would happen and the result would always be True
any and reduce are basic tools for functional programming. I strongly encourage you to train these out as well as map which is awesome too!
回答 8
如果您想要相同的代码结构,则可以使用三元语句!
def check_all_conditions():
x = check_size()
x = x if x else check_color()
x = x if x else check_tone()
x = x if x else check_flavor()return x if x elseNone
If you want the same code structure, you could use ternary statements!
def check_all_conditions():
x = check_size()
x = x if x else check_color()
x = x if x else check_tone()
x = x if x else check_flavor()
return x if x else None
I think this looks nice and clear if you look at it.
For me, the best answer is that from @phil-frost, followed by @wayne-werner’s.
What I find interesting is that no one has said anything about the fact that a function will be returning many different data types, which will make then mandatory to do checks on the type of x itself to do any further work.
So I would mix @PhilFrost’s response with the idea of keeping a single type:
def all_conditions(x):
yield check_size(x)
yield check_color(x)
yield check_tone(x)
yield check_flavor(x)
def assessed_x(x,func=all_conditions):
for condition in func(x):
if condition:
return x
return None
Notice that x is passed as an argument, but also all_conditions is used as a passed generator of checking functions where all of them get an x to be checked, and return True or False. By using func with all_conditions as default value, you can use assessed_x(x), or you can pass a further personalised generator via func.
That way, you get x as soon as one check passes, but it will always be the same type.
Ideally, I would re-write the check_ functions to return True or False rather than a value. Your checks then become
if check_size(x):
return x
#etc
Assuming your x is not immutable, your function can still modify it (although they can’t reassign it) – but a function called check shouldn’t really be modifying it anyway.
回答 11
上面的Martijns第一个示例略有变化,避免了if循环内:
Status=Nonefor c in[check_size, check_color, check_tone, check_flavor]:Status=Statusor c();returnStatus
import random
import timeit
def check_size():if random.random()<0.25:return"BIG"def check_color():if random.random()<0.25:return"RED"def check_tone():if random.random()<0.25:return"SOFT"def check_flavor():if random.random()<0.25:return"SWEET"def check_all_conditions_Bernard():
x = check_size()if x:return x
x = check_color()if x:return x
x = check_tone()if x:return x
x = check_flavor()if x:return x
returnNonedef check_all_Martijn_Pieters():
conditions =(check_size, check_color, check_tone, check_flavor)for condition in conditions:
result = condition()if result:return result
def check_all_conditions_timgeb():return check_size()or check_color()or check_tone()or check_flavor()orNonedef check_all_conditions_Reza():return check_size()or check_color()or check_tone()or check_flavor()def check_all_conditions_Phinet():
x = check_size()
x = x if x else check_color()
x = x if x else check_tone()
x = x if x else check_flavor()return x if x elseNonedef all_conditions():yield check_size()yield check_color()yield check_tone()yield check_flavor()def check_all_conditions_Phil_Frost():for condition in all_conditions():if condition:return condition
def main():
num =10000000
random.seed(20)print("Bernard:", timeit.timeit('check_all_conditions_Bernard()','from __main__ import check_all_conditions_Bernard', number=num))
random.seed(20)print("Martijn Pieters:", timeit.timeit('check_all_Martijn_Pieters()','from __main__ import check_all_Martijn_Pieters', number=num))
random.seed(20)print("timgeb:", timeit.timeit('check_all_conditions_timgeb()','from __main__ import check_all_conditions_timgeb', number=num))
random.seed(20)print("Reza:", timeit.timeit('check_all_conditions_Reza()','from __main__ import check_all_conditions_Reza', number=num))
random.seed(20)print("Phinet:", timeit.timeit('check_all_conditions_Phinet()','from __main__ import check_all_conditions_Phinet', number=num))
random.seed(20)print("Phil Frost:", timeit.timeit('check_all_conditions_Phil_Frost()','from __main__ import check_all_conditions_Phil_Frost', number=num))if __name__ =='__main__':
main()
I like @timgeb’s. In the meantime I would like to add that expressing None in the return statement is not needed as the collection of or separated statements are evaluated and the first none-zero, none-empty, none-None is returned and if there isn’t any then None is returned whether there is a None or not!
So my check_all_conditions() function looks like this:
def check_all_conditions():
return check_size() or check_color() or check_tone() or check_flavor()
Using timeit with number=10**7 I looked at the running time of a number of the suggestions. For the sake of comparison I just used the random.random() function to return a string or None based on random numbers. Here is the whole code:
import random
import timeit
def check_size():
if random.random() < 0.25: return "BIG"
def check_color():
if random.random() < 0.25: return "RED"
def check_tone():
if random.random() < 0.25: return "SOFT"
def check_flavor():
if random.random() < 0.25: return "SWEET"
def check_all_conditions_Bernard():
x = check_size()
if x:
return x
x = check_color()
if x:
return x
x = check_tone()
if x:
return x
x = check_flavor()
if x:
return x
return None
def check_all_Martijn_Pieters():
conditions = (check_size, check_color, check_tone, check_flavor)
for condition in conditions:
result = condition()
if result:
return result
def check_all_conditions_timgeb():
return check_size() or check_color() or check_tone() or check_flavor() or None
def check_all_conditions_Reza():
return check_size() or check_color() or check_tone() or check_flavor()
def check_all_conditions_Phinet():
x = check_size()
x = x if x else check_color()
x = x if x else check_tone()
x = x if x else check_flavor()
return x if x else None
def all_conditions():
yield check_size()
yield check_color()
yield check_tone()
yield check_flavor()
def check_all_conditions_Phil_Frost():
for condition in all_conditions():
if condition:
return condition
def main():
num = 10000000
random.seed(20)
print("Bernard:", timeit.timeit('check_all_conditions_Bernard()', 'from __main__ import check_all_conditions_Bernard', number=num))
random.seed(20)
print("Martijn Pieters:", timeit.timeit('check_all_Martijn_Pieters()', 'from __main__ import check_all_Martijn_Pieters', number=num))
random.seed(20)
print("timgeb:", timeit.timeit('check_all_conditions_timgeb()', 'from __main__ import check_all_conditions_timgeb', number=num))
random.seed(20)
print("Reza:", timeit.timeit('check_all_conditions_Reza()', 'from __main__ import check_all_conditions_Reza', number=num))
random.seed(20)
print("Phinet:", timeit.timeit('check_all_conditions_Phinet()', 'from __main__ import check_all_conditions_Phinet', number=num))
random.seed(20)
print("Phil Frost:", timeit.timeit('check_all_conditions_Phil_Frost()', 'from __main__ import check_all_conditions_Phil_Frost', number=num))
if __name__ == '__main__':
main()
You’ll need a assertFalsey function that raises an exception when one of the called function arguments evaluates as truthy:
def assertFalsey(*funcs):
for f in funcs:
o = f()
if o:
raise TruthyException(o)
The above could be modified so as to also provide arguments for the functions to be evaluated.
And of course you’ll need the TruthyException itself. This exception provides the object that triggered the exception:
class TruthyException(Exception):
def __init__(self, obj, *args):
super().__init__(*args)
self.trigger = obj
You can turn the original function into something more general, of course:
def get_truthy_condition(*conditions):
try:
assertFalsey(*conditions)
except TruthyException as e:
return e.trigger
else:
return None
result = get_truthy_condition(check_size, check_color, check_tone, check_flavor)
This might be a bit slower because you are using both an if statement and handling an exception. However, the exception is only handled a maximum of one time, so the hit to performance should be minor unless you expect to run the check and get a True value many many thousands of times.
The pythonic way is either using reduce (as someone already mentioned) or itertools (as shown below), but it seems to me that simply using short circuiting of the or operator produces clearer code
我要跳进这里,从来没有写过Python的任何一行,但是我认为这if x = check_something(): return x是有效的吗?
如果是这样的话:
def check_all_conditions():if(x := check_size()):return x
if(x := check_color()):return x
if(x := check_tone()):return x
if(x := check_flavor()):return x
returnNone
I’m going to jump in here and have never written a single line of Python, but I assume if x = check_something(): return x is valid?
if so:
def check_all_conditions():
if (x := check_size()): return x
if (x := check_color()): return x
if (x := check_tone()): return x
if (x := check_flavor()): return x
return None
def status(k ='a', s ={'a':'b','b':'c','c':'d','d':None}):
select =lambda next, test : test if test else next
d ={'a':lambda: select(s['a'], check_size()),'b':lambda: select(s['b'], check_color()),'c':lambda: select(s['c'], check_tone()),'d':lambda: select(s['d'], check_flavor())}while k in d : k = d[k]()return k
select函数消除了每次调用check_FUNCTION两次的需要,即避免check_FUNCTION() if check_FUNCTION() else next添加另一个函数层。这对于长时间运行的功能很有用。字典中的lambda会延迟其值的执行,直到while循环为止。
def status(k=check_size):
select =lambda next, test : test if test else next
d ={check_size :lambda: select(check_color, check_size()),
check_color :lambda: select(check_tone, check_color()),
check_tone :lambda: select(check_flavor, check_tone()),
check_flavor:lambda: select(None, check_flavor())}while k in d : k = d[k]()return k
I have seen some interesting implementations of switch/case statements with dicts in the past that led me to this answer. Using the example you’ve provided you would get the following. (It’s madness using_complete_sentences_for_function_names, so check_all_conditions is renamed to status. See (1))
def status(k = 'a', s = {'a':'b','b':'c','c':'d','d':None}) :
select = lambda next, test : test if test else next
d = {'a': lambda : select(s['a'], check_size() ),
'b': lambda : select(s['b'], check_color() ),
'c': lambda : select(s['c'], check_tone() ),
'd': lambda : select(s['d'], check_flavor())}
while k in d : k = d[k]()
return k
The select function eliminates the need to call each check_FUNCTION twice i.e. you avoid check_FUNCTION() if check_FUNCTION() else next by adding another function layer. This is useful for long running functions. The lambdas in the dict delay execution of it’s values until the while loop.
As a bonus you may modify the execution order and even skip some of the tests by altering k and s e.g. k='c',s={'c':'b','b':None} reduces the number of tests and reverses the original processing order.
The timeit fellows might haggle over the cost of adding an extra layer or two to the stack and the cost for the dict look up but you seem more concerned with the prettiness of the code.
Alternatively a simpler implementation might be the following :
def status(k=check_size) :
select = lambda next, test : test if test else next
d = {check_size : lambda : select(check_color, check_size() ),
check_color : lambda : select(check_tone, check_color() ),
check_tone : lambda : select(check_flavor, check_tone() ),
check_flavor: lambda : select(None, check_flavor())}
while k in d : k = d[k]()
return k
I mean this not in terms of pep8 but in terms of using one concise descriptive word in place of a sentence. Granted the OP may be following some coding convention, working one some existing code base or not care for terse terms in their codebase.