Python's equivalent of Ruby's ||=
I think there is some confusion from the people who aren't really sure what the conditional assignment operator (||=
) does, and also some misunderstanding about how variables are spawned in Ruby.
Everyone should read this article on the subject. A TLDR quote:
A common misconception is that a ||= b is equivalent to a = a || b, but it behaves like a || a = b
In a = a || b, a is set to something by the statement on every run, whereas with a || a = b, a is only set if a is logically false (i.e. if it's nil or false) because || is 'short circuiting'. That is, if the left hand side of the || comparison is true, there's no need to check the right hand side.
And another very important note:
...a variable assignment, even if not run, immediately summons that variable into being.
# Ruby
x = 10 if 2 == 5
puts x
Even though the first line won't be run, x will exist on the second line and no exception will be raised.
This means that Ruby will absolutely ensure that there is a variable container for a value to be placed into before any righthand conditionals take place. ||=
doesn't assign if a
is not defined, it assigns if a
is falsy (again, false
or nil
- nil
being the default nothingness value in Ruby), whilst guaranteeing a
is defined.
What does this mean for Python?
Well, if a
is defined, the following:
# Ruby
a ||= 10
is actually equivalent to:
# Python
if not a:
a = 10
while the following:
# Either language
a = a or 10
is close, but it always assigns a value, whereas the previous examples do not.
And if a
is not defined the whole operation is closer to:
# Python
a = None
if not a:
a = 10
Because a very explicit example of what a ||= 10
does when a
is not defined would be:
# Ruby
if not defined? a
a = nil
end
if not a
a = 10
end
At the end of the day, the ||=
operator is not completely translatable to Python in any kind of 'Pythonic' way, because of how it relies on the underlying variable spawning in Ruby.
There is no particularly elegant way in python, because it's not particularly elegant to get yourself into a situation where you don't know whether a variable is existing or not. However, this seems closest:
try:
var
except NameError:
var = var_new
I'm not familiar with the ||=
operator in ruby, but from what you have described this block should have the correct behaviour. That is, we leave var
bound as is if it was an already existing variable, and we set it to var_new
otherwise.
This is approximately, and idiomatically, what you want:
var = var or var_new
Python's rules for variables that "don't exist" are quite strict; this will throw an exception if var
has not been previously assigned. However, if var
evaluates as falsey, it will receive the value of var_new
.
I say this is "idiomatically what you want" because the idiomatic larger structure for this kind of thing, in Python, goes like so:
var1 = None
var2 = None
var3 = None
# ... code here that may or may not set var1 through var3 ...
var1 = var1 or "default1"
var2 = var2 or "default2"
var3 = var3 or "default3"
Note also that Python has a fairly broad notion of "falsey". This construct is only valid if var
cannot have been assigned zero, False, or any object considered to be "empty" (e.g. ""
, []
, {}
...). If you really want it to trigger only on None you have to write the more verbose
var = var if var is not None else var_new
and, rather than do that, I would generally look for another way to solve the larger problem.
Finally, if you can structure your code like this instead...
var1 = "default1"
var2 = "default2"
var3 = "default3"
# ... code here that may or may not set var1 through var3 ...
... then you should, because it's shorter and less complicated that way, and the None vs falsey issue is completely avoided.