tl;dr
When doing comparisons to singletons like None, you should always use is or is not, not the simple if or if not. Your code doesn't work as expected because if layout does not tell whether layout is None or not.
Those lines should be changed so that the layout reference is set to self.main_layout only if layout is None:
def someFunction(self, ..., layout=None):
if layout is None:
layout = self.main_layout
...
Explanation
In PyQt, the if <PyQt-object>: syntax does not always result in checking whether <PyQt-object> is None or not. If, for instance, <PyQt-object> is a Qt layout, it checks if the layout contains at least one QLayoutItem, which is an abstract object used to manage the geometry of widgets, spacers or nested layouts within that layout.
Truthfulness of an object or expression
Remember that if is a conditional statement that evaluates the "truthfulness" of an expression: if <something>: evaluates if <something> is true or false. If the <something> is an object, then the expression is evaluated through Truth Value Testing:
By default, an object is considered true unless its class defines either a __bool__() method that returns False or a __len__() method that returns zero, when called with the object.
If an object (typically, a class) does not implement those methods, if <object>: will enter the block of that statement because the object "exists" (it is "something" that is not "none").
If it does implement one of those functions, it will enter that block only if one those functions return True or not zero, respectively.
Those are convenience implementations (commonly called "syntactic sugar") allowing things such as if <someObject>:
if someList:
... # the list has at least one item
if someString:
... # the string is not empty
# as opposed to
if len(someList):
...
if someString != '':
...
PyQt implementation
Historically, PyQt implements some built-in dunder methods (instance methods that start and end with a double underscore, such as __init__) used in the data model when certain conditions are met for the class.
Typically, if a Qt class has a count() function, it normally means that it can contain some items in a mono-dimensional data model (a "list"). This is the case of classes such as, for instance: QListWidget, QComboBox, QPolygon[F], and, interestingly enough, layout managers (including layouts that do not "show" items in a mono-dimensional concept, which is the case of QGridLayout).
In most cases, when a class implements count(), PyQt also implements the __len__() method, internally returning the result of count().
Remember the "Truth Value Testing" above: if <object>: means that an object is considered true, unless the class defines either __bool__() that returns False, or a __len__() returning zero.
If a class does contain a count() function, PyQt implements __len__() and returns the result of count(). Consider the following:
combo = None
if not combo:
# this will be printed
print('no combo')
combo = QComboBox()
if not combo:
# this will be printed as well!
print('the combo exists, but it is empty!')
Another example of these convenience implementations is for classes that have a contains() function, such as QRect, which implements __contains__() in PyQt; in that case, using the in operator returns the result of the various QRect::contains() function overrides:
point = QPoint(1, 2)
rect = QRect(0, 0, 10, 10)
if point in rect:
... # the rectangle contains the point
# as opposed to
if rect.contains(point):
...
Note that these convenience implementations are only available in PyQt, not PySide.
If you plan to release your program allowing the possibility of using either of the bindings (such as using shims like QtPy or Qt.py) you cannot use that syntax, and you must follow the official Qt API.
Truthfulness of a Qt layout
Qt layout managers (subclasses of QLayout) always have a list of their items, it doesn't matter how they're eventually placed or shown in the UI.
Even though QGridLayout shows its items in rows and columns, it still has an internal list of items, and its count() function returns the length of that list.
This means that, in PyQt, if layout may evaluate as False if the layout is empty (therefore the block within if layout: won't be executed), even though a QLayout object does exist.
Proper comparison against None and better syntax
As written at the beginning, a reference that may be None should never be tested with a simple if <object>: unless you really know what you're doing.
Consider the post if A vs if A is not None:, and the following important note in the Programming Recommendations in the Style Guide for Python Code:
Comparisons to singletons like None should always be done with is or is not, never the equality operators.
Also, beware of writing if x when you really mean if x is not None – e.g. when testing whether a variable or argument that defaults to None was set to some other value. The other value might have a type (such as a container) that could be false in a boolean context!
In a case like yours, not only it would be mandatory to use the is None (or is not None) testing due to the PyQt implementation explained above, but it would be more appropriate in principle: the usage of None for keyworded (default) arguments means that the argument can be explicitly used with None (to follow the default behavior) and with anything that may not compare to None, as the "Truth Value Testing" explains: if <object>: may give the same result even if that object is 0, an empty string, False, an empty list and any other object that may implement __bool__ or __len__ even though the object is not None.
Your code could be changed in the following way:
layout = layout if layout is not None else self.main_layout
Yet, while sometimes necessary, there is usually no point in overwriting the reference with itself if the object is considered valid. The above would be fundamentally identical to:
if layout is not None:
layout = layout
else:
layout = self.main_layout
You can probably spot the redundancy in the second line: it's completely pointless.
You probably choose the x if x else y "one-liner" approach for code simplicity, but in reality it's usually inefficient and irrelevant.
Now, consider this approach, using a slightly different syntax while giving an absolutely identical result:
layout = self.main_layout if layout is None else layout
It's slightly simpler, and sometimes preferable. Yet, let's see the more verbose version without the one-liner:
if layout is None:
layout = self.main_layout
else:
layout = layout
It's clear that the whole else: block is completely pointless.
This means that you can (and should!) change those lines with the following:
def someFunction(self, ..., layout=None):
if layout is None:
layout = self.main_layout
...
Yes, those are two lines, not one. But they are more appropriate.
Note that Python does allow one-liners. The following would be completely legal:
def someFunction(self, ..., layout=None):
if layout is None: layout = self.main_layout
...
Yet, such a syntax is normally discouraged: the block indentation requirement of Python is not just about code fanciness, but readability, which is an extremely important aspect of programming.
The last snippet above will only give extremely small benefits when the code is being parsed from the Python interpreter, but you'd need to have thousands of similar one-liners to see any relevant difference, while still having thousands of less readable lines that will make your code more difficult to read and understand, both while debugging and reviewing (especially if returning to code written a lot of time before).
Remember: don't code "short", code smart.
if layout is not Noneinstead ofif layout, or, better:layout = self.main_layout if layout is None else layout.bool(object)orif objectreturns True. Layouts have some overridden methods. An empty layout evaluates to False.