Sometimes I break long conditions in ifs onto several lines. The most obvious way to do this is:
if (cond1 == 'val1' and cond2 == 'val2' and
cond3 == 'val3' and cond4 == 'val4'):
do_something
Isn’t very very appealing visually, because the action blends with the conditions. However, it is the natural way using correct Python indentation of 4 spaces.
For the moment I’m using:
if ( cond1 == 'val1' and cond2 == 'val2' and
cond3 == 'val3' and cond4 == 'val4'):
do_something
You don’t need to use 4 spaces on your second conditional line. Maybe use:
if (cond1 == 'val1' and cond2 == 'val2' and
cond3 == 'val3' and cond4 == 'val4'):
do_something
Also, don’t forget the whitespace is more flexible than you might think:
if (
cond1 == 'val1' and cond2 == 'val2' and
cond3 == 'val3' and cond4 == 'val4'
):
do_something
if (cond1 == 'val1' and cond2 == 'val2' and
cond3 == 'val3' and cond4 == 'val4'):
do_something
Both of those are fairly ugly though.
Maybe lose the brackets (the Style Guide discourages this though)?
if cond1 == 'val1' and cond2 == 'val2' and \
cond3 == 'val3' and cond4 == 'val4':
do_something
This at least gives you some differentiation.
Or even:
if cond1 == 'val1' and cond2 == 'val2' and \
cond3 == 'val3' and \
cond4 == 'val4':
do_something
I think I prefer:
if cond1 == 'val1' and \
cond2 == 'val2' and \
cond3 == 'val3' and \
cond4 == 'val4':
do_something
Here’s the Style Guide, which (since 2010) recommends using brackets.
Here’s my very personal take: long conditions are (in my view) a code smell that suggests refactoring into a boolean-returning function/method. For example:
def is_action__required(...):
return (cond1 == 'val1' and cond2 == 'val2'
and cond3 == 'val3' and cond4 == 'val4')
Now, if I found a way to make multi-line conditions look good, I would probably find myself content with having them and skip the refactoring.
On the other hand, having them perturb my aesthetic sense acts as an incentive for refactoring.
My conclusion, therefore, is that multiple line conditions should look ugly and this is an incentive to avoid them.
I suggest moving the and keyword to the second line and indenting all lines containing conditions with two spaces instead of four:
if (cond1 == 'val1' and cond2 == 'val2'
and cond3 == 'val3' and cond4 == 'val4'):
do_something
This is exactly how I solve this problem in my code. Having a keyword as the first word in the line makes the condition a lot more readable, and reducing the number of spaces further distinguishes condition from action.
# No extra indentation.if(this_is_one_thing and
that_is_another_thing):
do_something()# Add a comment, which will provide some distinction in editors# supporting syntax highlighting.if(this_is_one_thing and
that_is_another_thing):# Since both conditions are true, we can frobnicate.
do_something()# Add some extra indentation on the conditional continuation line.if(this_is_one_thing
and that_is_another_thing):
do_something()
It seems worth quoting PEP 0008 (Python’s official style guide), since it comments upon this issue at modest length:
When the conditional part of an if -statement is long enough to require that it be written across multiple lines, it’s worth noting that the combination of a two character keyword (i.e. if ), plus a single space, plus an opening parenthesis creates a natural 4-space indent for the subsequent lines of the multiline conditional. This can produce a visual conflict with the indented suite of code nested inside the if -statement, which would also naturally be indented to 4 spaces. This PEP takes no explicit position on how (or whether) to further visually distinguish such conditional lines from the nested suite inside the if -statement. Acceptable options in this situation include, but are not limited to:
# No extra indentation.
if (this_is_one_thing and
that_is_another_thing):
do_something()
# Add a comment, which will provide some distinction in editors
# supporting syntax highlighting.
if (this_is_one_thing and
that_is_another_thing):
# Since both conditions are true, we can frobnicate.
do_something()
# Add some extra indentation on the conditional continuation line.
if (this_is_one_thing
and that_is_another_thing):
do_something()
Note the “not limited to” in the quote above; besides the approaches suggested in the style guide, some of the ones suggested in other answers to this question are acceptable too.
Adding to what @krawyoti said… Long conditions smell because they are difficult to read and difficult to understand. Using a function or a variable makes the code clearer. In Python, I prefer to use vertical space, enclose parenthesis, and place the logical operators at the beginning of each line so the expressions don’t look like “floating”.
conditions_met = (
cond1 == 'val1'
and cond2 == 'val2'
and cond3 == 'val3'
and cond4 == 'val4'
)
if conditions_met:
do_something
If the conditions need to be evaluated more than once, as in a while loop, then using a local function is best.
Personally, I like to add meaning to long if-statements. I would have to search through code to find an appropriate example, but here’s the first example that comes to mind: let’s say I happen to run into some quirky logic where I want to display a certain page depending on many variables.
English: “If the logged-in user is NOT an administrator teacher, but is just a regular teacher, and is not a student themselves…”
if not user.isAdmin() and user.isTeacher() and not user.isStudent():
doSomething()
Sure this might look fine, but reading those if statements is a lot of work. How about we assign the logic to label that makes sense. The “label” is actually the variable name:
displayTeacherPanel = not user.isAdmin() and user.isTeacher() and not user.isStudent()
if displayTeacherPanel:
showTeacherPanel()
This may seem silly, but you might have yet another condition where you ONLY want to display another item if, and only if, you’re displaying the teacher panel OR if the user has access to that other specific panel by default:
if displayTeacherPanel or user.canSeeSpecialPanel():
showSpecialPanel()
Try writing the above condition without using variables to store and label your logic, and not only do you end up with a very messy, hard-to-read logical statement, but you also just repeated yourself. While there are reasonable exceptions, remember: Don’t Repeat Yourself (DRY).
回答 12
“ all”和“ any”对于相同类型案例的许多条件都很好。但是他们总是评估所有条件。如本例所示:
def c1():print" Executed c1"returnFalsedef c2():print" Executed c2"returnFalseprint"simple and (aborts early!)"if c1()and c2():passprintprint"all (executes all :( )"if all((c1(),c2())):passprint
(I’ve lightly modified the identifiers as fixed-width names aren’t representative of real code – at least not real code that I encounter – and will belie an example’s readability.)
if (cond1 == "val1" and cond22 == "val2"
and cond333 == "val3" and cond4444 == "val4"):
do_something
This works well for “and” and “or” (it’s important that they’re first on the second line), but much less so for other long conditions. Fortunately, the former seem to be the more common case while the latter are often easily rewritten with a temporary variable. (It’s usually not hard, but it can be difficult or much less obvious/readable to preserve the short-circuiting of “and”/”or” when rewriting.)
Since I found this question from your blog post about C++, I’ll include that my C++ style is identical:
if (cond1 == "val1" and cond22 == "val2"
and cond333 == "val3" and cond4444 == "val4") {
do_something
}
# Check if every string in a list contains a substring:
my_list =['a substring is like a string','another substring']if all('substring'in item for item in my_list):print("Hello World!")# orif all('substring'in item
for item in my_list
):print("Hello World!")
In recent times I have been preferring the all and any functions, since I rarely mix And and Or comparisons this works well, and has the additional advantage of Failing Early with generators comprehension:
if all([
cond1,
cond2,
]):
print("Hello World!")
Just remember to pass in a single iterable! Passing in N-arguments is not correct.
Note: any is like many or comparisons, all is like many and comparisons.
This combines nicely with generator comprehensions, for example:
# Check if every string in a list contains a substring:
my_list = [
'a substring is like a string',
'another substring'
]
if all('substring' in item for item in my_list):
print("Hello World!")
# or
if all(
'substring' in item
for item in my_list
):
print("Hello World!")
All respondents that also provide multi-conditionals for the if statement is just as ugly as the problem presented. You don’t solve this problem by doing the same thing..
Even the PEP 0008 answer is repulsive.
Here is a far more readable approach
condition = random.randint(0, 100) # to demonstrate
anti_conditions = [42, 67, 12]
if condition not in anti_conditions:
pass
Want me to eat my words? Convince me you need multi-conditionals and I’ll literally print this and eat it for your amusement.
I think @zkanda’s solution would be good with a minor twist. If you had your conditions and values in their own respective lists, you could use a list comprehension to do the comparison, which would make things a bit more general for adding condition/value pairs.
conditions = [1, 2, 3, 4]
values = [1, 2, 3, 4]
if all([c==v for c, v in zip(conditions, values)]):
# do something
If I did want to hard-code a statement like this, I would write it like this for legibility:
if (condition1==value1) and (condition2==value2) and \
(condition3==value3) and (condition4==value4):
And just to throw another solution out there with an iand operator:
proceed = True
for c, v in zip(conditions, values):
proceed &= c==v
if proceed:
# do something
回答 19
出于完整性考虑,仅提供了一些其他随机想法。如果它们对您有用,请使用它们。否则,您最好尝试其他方法。
您也可以使用字典来做到这一点:
>>> x ={'cond1':'val1','cond2':'val2'}>>> y ={'cond1':'val1','cond2':'val2'}>>> x == y
True
这个选项比较复杂,但是您可能还会发现它有用:
classKlass(object):def __init__(self, some_vars):#initialize conditions heredef __nonzero__(self):return(self.cond1 =='val1'and self.cond2 =='val2'and
self.cond3 =='val3'and self.cond4 =='val4')
foo =Klass()if foo:print"foo is true!"else:print"foo is false!"
邓诺(Dunno)是否适合您,但这是您可以考虑的另一种选择。这是另一种方式:
classKlass(object):def __init__(self):#initialize conditions heredef __eq__(self):return(self.cond1 =='val1'and self.cond2 =='val2'and
self.cond3 =='val3'and self.cond4 =='val4')
x =Klass(some_values)
y =Klass(some_other_values)if x == y:print'x == y'else:print'x!=y'
Just a few other random ideas for completeness’s sake. If they work for you, use them. Otherwise, you’re probably better off trying something else.
You could also do this with a dictionary:
>>> x = {'cond1' : 'val1', 'cond2' : 'val2'}
>>> y = {'cond1' : 'val1', 'cond2' : 'val2'}
>>> x == y
True
This option is more complicated, but you may also find it useful:
class Klass(object):
def __init__(self, some_vars):
#initialize conditions here
def __nonzero__(self):
return (self.cond1 == 'val1' and self.cond2 == 'val2' and
self.cond3 == 'val3' and self.cond4 == 'val4')
foo = Klass()
if foo:
print "foo is true!"
else:
print "foo is false!"
Dunno if that works for you, but it’s another option to consider. Here’s one more way:
class Klass(object):
def __init__(self):
#initialize conditions here
def __eq__(self):
return (self.cond1 == 'val1' and self.cond2 == 'val2' and
self.cond3 == 'val3' and self.cond4 == 'val4')
x = Klass(some_values)
y = Klass(some_other_values)
if x == y:
print 'x == y'
else:
print 'x!=y'
The last two I haven’t tested, but the concepts should be enough to get you going if that’s what you want to go with.
(And for the record, if this is just a one time thing, you’re probably just better off using the method you presented at first. If you’re doing the comparison in lots of places, these methods may enhance readability enough to make you not feel so bad about the fact that they are kind of hacky.)
I’ve been struggling to find a decent way to do this as well, so I just came up with an idea (not a silver bullet, since this is mainly a matter of taste).
if bool(condition1 and
condition2 and
...
conditionN):
foo()
bar()
I find a few merits in this solution compared to others I’ve seen, namely, you get exactly an extra 4 spaces of indentation (bool), allowing all conditions to line up vertically, and the body of the if statement can be indented in a clear(ish) way. This also keeps the benefits of short-circuit evaluation of boolean operators, but of course adds the overhead of a function call that basically does nothing. You could argue (validly) that any function returning its argument could be used here instead of bool, but like I said, it’s just an idea and it’s ultimately a matter of taste.
Funny enough, as I was writing this and thinking about the “problem”, I came up with yet another idea, which removes the overhead of a function call. Why not indicate that we’re about to enter a complex condition by using extra pairs of parentheses? Say, 2 more, to give a nice 2 space indent of the sub-conditions relative to the body of the if statement. Example:
if (((foo and
bar and
frob and
ninja_bear))):
do_stuff()
I kind of like this because when you look at it, a bell immediatelly rings in your head saying “hey, there’s a complex thing going on here!”. Yes, I know that parentheses don’t help readability, but these conditions should appear rarely enough, and when they do show up, you are going to have to stop and read them carefuly anyway (because they’re complex).
Anyway, just two more proposals that I haven’t seen here. Hope this helps someone :)
回答 21
您可以将其分为两行
total = cond1 =='val'and cond2 =='val2'and cond3 =='val3'and cond4 == val4
if total:
do_something()
I know this thread is old, but I have some Python 2.7 code and PyCharm (4.5) still complains about this case:
if foo is not None:
if (cond1 == 'val1' and cond2 == 'val2' and
cond3 == 'val3' and cond4 == 'val4'):
# some comment about do_something
do_something
Even with the PEP8 warning “visually indented line with same indent as next logical line”, the actual code is completely OK? It’s not “over-indenting?”
…there are times I wish Python would’ve bit the bullet and just gone with curly braces. I wonder how many bugs have been accidentally introduced over the years due to accidental mis-indentation…
if our if & an else condition has to execute multiple statement inside of it than we can write like below.
Every when we have if else example with one statement inside of it .
param_Val01 =Value01#give a meaningful name for param_Val(i) preferable an integer
param_Val02 =Value02
param_Val03 =Value03
param_Val04 =Value04# and ... etc
conditions =0# this is a value placeholder########Add script that if true will make:
conditions = conditions + param_Val01 #value of placeholder is updated########### repeat as neededif conditions = param_Val01 + param_Val02 + param_Val03 + param_Val04:do something
Pardon my noobness, but it happens that I’m not as knowledgeable of #Python as anyone of you here, but it happens that I have found something similar when scripting my own objects in a 3D BIM modeling, so I will adapt my algorithm to that of python.
The problem that I find here, is double sided:
Values my seem foreign for someone who may try to decipher the script.
Code maintenance will come at a high cost, if those values are changed (most probable), or if new conditions must be added (broken schema)
Do to bypass all these problems, your script must go like this
param_Val01 = Value 01 #give a meaningful name for param_Val(i) preferable an integer
param_Val02 = Value 02
param_Val03 = Value 03
param_Val04 = Value 04 # and ... etc
conditions = 0 # this is a value placeholder
########
Add script that if true will make:
conditions = conditions + param_Val01 #value of placeholder is updated
########
### repeat as needed
if conditions = param_Val01 + param_Val02 + param_Val03 + param_Val04:
do something
Pros of this method:
Script is readable.
Script can be easy maintained.
conditions is a 1 comparison operation to a sum of values that represents the desired conditions.
However, this quickly gets problematic as the number of values returned increases. What if you want to return four or five values? Sure, you could keep tupling them, but it gets easy to forget which value is where. It’s also rather ugly to unpack them wherever you want to receive them.
Option: Using a dictionary
The next logical step seems to be to introduce some sort of ‘record notation’. In Python, the obvious way to do this is by means of a dict.
(Just to be clear, y0, y1, and y2 are just meant as abstract identifiers. As pointed out, in practice you’d use meaningful identifiers.)
Now, we have a mechanism whereby we can project out a particular member of the returned object. For example,
result['y0']
Option: Using a class
However, there is another option. We could instead return a specialized structure. I’ve framed this in the context of Python, but I’m sure it applies to other languages as well. Indeed, if you were working in C this might very well be your only option. Here goes:
In Python the previous two are perhaps very similar in terms of plumbing – after all { y0, y1, y2 } just end up being entries in the internal __dict__ of the ReturnValue.
There is one additional feature provided by Python though for tiny objects, the __slots__ attribute. The class could be expressed as:
The __slots__ declaration takes a sequence of instance variables and reserves just enough space in each instance to hold a value for each variable. Space is saved because __dict__ is not created for each instance.
Using Python 3.7’s new dataclasses, return a class with automatically added special methods, typing and other useful tools:
@dataclass
class Returnvalue:
y0: int
y1: float
y3: int
def total_cost(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return ReturnValue(y0, y1, y2)
Option: Using a list
Another suggestion which I’d overlooked comes from Bill the Lizard:
def h(x):
result = [x + 1]
result.append(x * 3)
result.append(y0 ** y3)
return result
This is my least favorite method though. I suppose I’m tainted by exposure to Haskell, but the idea of mixed-type lists has always felt uncomfortable to me. In this particular example the list is -not- mixed type, but it conceivably could be.
A list used in this way really doesn’t gain anything with respect to the tuple as far as I can tell. The only real difference between lists and tuples in Python is that lists are mutable, whereas tuples are not.
I personally tend to carry over the conventions from functional programming: use lists for any number of elements of the same type, and tuples for a fixed number of elements of predetermined types.
Question
After the lengthy preamble, comes the inevitable question. Which method (do you think) is best?
In recent versions of Python 3 (3.6+, I think), the new typing library got the NamedTuple class to make named tuples easier to create and more powerful. Inheriting from typing.NamedTuple lets you use docstrings, default values, and type annotations.
Example (From the docs):
class Employee(NamedTuple): # inherit from typing.NamedTuple
name: str
id: int = 3 # default value
employee = Employee('Guido')
assert employee.id == 3
For small projects I find it easiest to work with tuples. When that gets too hard to manage (and not before) I start grouping things into logical structures, however I think your suggested use of dictionaries and ReturnValue objects is wrong (or too simplistic).
Returning a dictionary with keys "y0", "y1", "y2", etc. doesn’t offer any advantage over tuples. Returning a ReturnValue instance with properties .y0, .y1, .y2, etc. doesn’t offer any advantage over tuples either. You need to start naming things if you want to get anywhere, and you can do that using tuples anyway:
A lot of the answers suggest you need to return a collection of some sort, like a dictionary or a list. You could leave off the extra syntax and just write out the return values, comma-separated. Note: this technically returns a tuple.
I find that if I make a function that returns anything more than 2-3 variables I’ll fold them up in a dictionary. Otherwise I tend to forget the order and content of what I’m returning.
Also, introducing a ‘special’ structure makes your code more difficult to follow. (Someone else will have to search through the code to find out what it is)
If your concerned about type look up, use descriptive dictionary keys, for example, ‘x-values list’.
I prefer to use tuples whenever a tuple feels “natural”; coordinates are a typical example, where the separate objects can stand on their own, e.g. in one-axis only scaling calculations, and the order is important. Note: if I can sort or shuffle the items without an adverse effect to the meaning of the group, then I probably shouldn’t use a tuple.
I use dictionaries as a return value only when the grouped objects aren’t always the same. Think optional email headers.
For the rest of the cases, where the grouped objects have inherent meaning inside the group or a fully-fledged object with its own methods is needed, I use a class.
回答 6
我更喜欢:
def g(x):
y0 = x +1
y1 = x *3
y2 = y0 ** y3
return{'y0':y0,'y1':y1 ,'y2':y2 }
Python’s tuples, dicts, and objects offer the programmer a smooth tradeoff between formality and convenience for small data structures (“things”). For me, the choice of how to represent a thing is dictated mainly by how I’m going to use the structure. In C++, it’s a common convention to use struct for data-only items and class for objects with methods, even though you can legally put methods on a struct; my habit is similar in Python, with dict and tuple in place of struct.
For coordinate sets, I’ll use a tuple rather than a point class or a dict (and note that you can use a tuple as a dictionary key, so dicts make great sparse multidimensional arrays).
If I’m going to be iterating over a list of things, I prefer unpacking tuples on the iteration:
for score,id,name in scoreAllTheThings():
if score > goodScoreThreshold:
print "%6.3f #%6d %s"%(score,id,name)
…as the object version is more cluttered to read:
for entry in scoreAllTheThings():
if entry.score > goodScoreThreshold:
print "%6.3f #%6d %s"%(entry.score,entry.id,entry.name)
…let alone the dict.
for entry in scoreAllTheThings():
if entry['score'] > goodScoreThreshold:
print "%6.3f #%6d %s"%(entry['score'],entry['id'],entry['name'])
If the thing is widely used, and you find yourself doing similar non-trivial operations on it in multiple places in the code, then it’s usually worthwhile to make it a class object with appropriate methods.
Finally, if I’m going to be exchanging data with non-Python system components, I’ll most often keep them in a dict because that’s best suited to JSON serialization.
+1 on S.Lott’s suggestion of a named container class.
For Python 2.6 and up, a named tuple provides a useful way of easily creating these container classes, and the results are “lightweight and require no more memory than regular tuples”.
“Best” is a partially subjective decision. Use tuples for small return sets in the general case where an immutable is acceptable. A tuple is always preferable to a list when mutability is not a requirement.
For more complex return values, or for the case where formality is valuable (i.e. high value code) a named tuple is better. For the most complex case an object is usually best. However, it’s really the situation that matters. If it makes sense to return an object because that is what you naturally have at the end of the function (e.g. Factory pattern) then return the object.
As the wise man said:
Premature optimization is the root of all evil (or at least most of
it) in programming.