Is there any expression in python that similar to ruby's ||=
Asked Answered
T

6

5

I came across an interesting expression in Ruby:

a ||= "new"

It means that if a is not defined, the "new" value will be assigned to a; otherwise, a will be the same as it is. It is useful when doing some DB query. If the value is set, I don't want to fire another DB query.

So I tried the similar mindset in Python:

a = a if a is not None else "new"

It failed. I think that it because you cannot do "a = a" in Python, if a is not defined.

So the solutions that I can come out are checking locals() and globals(), or using try...except expression:

myVar = myVar if 'myVar' in locals() and 'myVar' in globals() else "new"

or

try:
    myVar
except NameError:
    myVar = None

myVar = myVar if myVar else "new"

As we can see, the solutions are not that elegant. So I'd like to ask, is there any better way of doing this?

Transvestite answered 20/7, 2012 at 7:9 Comment(7)
note that the usual use of variable ||= value is to set a default value to an already defined variable, using it to define variables that may or may not exist is IMHO poor practice.Combatant
The try-except solution is, I believe, the most Pythonic. Sure, it's not as concise as a single operator, but it's clear and readable.Barchan
myVar = myVar if myVar else "new" - be aware that this is python and not ruby, and a lot of things are considered to be False in a boolean contextTourney
Wait... why would you want to do this?Cumbrance
@JoelCornett As I mentioned in the question. If the "new" part is to trigger a query to the DB. The var a will only be set once. It safes the resource and improves the performance.Transvestite
I've been doing my share of Python and Ruby programming and I have a hard time thinking about an example where you want this functionality with a local variable. Sure, you would use it with an instance variable in Ruby, but this translates to python as thing.foo = thing.foo or 'something else'. Do you have an example of where you want to use it?Communication
@StefanKanev Honestly, I don't have a real-life example for this. I just came across this ruby expression. I found it's neat. Just curious. I may need the same mindset some day.Transvestite
F
3

How about?

try:
    a = a
except NameError:
    a = "new"

It's not very short but does clearly (at least to me) explain the intent of the code.

Fled answered 20/7, 2012 at 7:40 Comment(2)
Not quite. In the question, a is set to None, and then a ternary expression is used to set a to "new". And the ternary expression as written will fail if a is [] or anything else that evaluates to False. As mentioned above, you need to do a is not None.Fled
You're right, I read the question wrong, hence my comment to it, where I say that that part is a good solution :)Barchan
N
5

Using an undefined variable as a "default" is a bit of a code smell. As you discovered, it fails because you can't do a = a if a doesn't already exist. It's better to initialize your variable with some default value (like None) and then check for that. So you pretty much found the solution. The only thing is that instead of leaving your variable uninitalized and trying to use "does not exist" as the "missing" value, you should explicitly initialize it. So, instead of doing this:

 # code with no initialization. . .
 a ||= blah

do this:

a = None
# code
if a is None:
    a = blah
Nesta answered 20/7, 2012 at 7:15 Comment(2)
The advantage of a ||= blah is that you don't have to prepare the value beforehand and it's simple and short. That's kind of neat. But I guess you are right. This is the simplest way to do this in Python.Transvestite
You could shorten the last lines to: a = a if a is not None else blah, or if you know that a will be truthy a = a or blah.Beekeeper
F
3

How about?

try:
    a = a
except NameError:
    a = "new"

It's not very short but does clearly (at least to me) explain the intent of the code.

Fled answered 20/7, 2012 at 7:40 Comment(2)
Not quite. In the question, a is set to None, and then a ternary expression is used to set a to "new". And the ternary expression as written will fail if a is [] or anything else that evaluates to False. As mentioned above, you need to do a is not None.Fled
You're right, I read the question wrong, hence my comment to it, where I say that that part is a good solution :)Barchan
G
1

If you really wanted this behaviour you'd be best off in Python using a dict, eg: d.get('a', 'new'). Which means you could muck about with globals()/locals().get('a', 'new'), but that's generally not the way to do things in Python - each name binding should have an initial value even if it's some sort of sentinel value (such as None).

horrible example with globals() and using setdefault()

>>> a
Traceback (most recent call last):
  File "<pyshell#66>", line 1, in <module>
    a
NameError: name 'a' is not defined
>>> globals().setdefault('a', 'new')
'new'
>>> a
'new'
>>> a = 'old'
>>> globals().setdefault('a', 'new')
'old'
>>> a
'old'
Glandulous answered 20/7, 2012 at 7:22 Comment(4)
granted, it's a hypothetical, but probably d.setdefault('a', 'new') would be a better choice.Beekeeper
I think get() cannot work with globals() and locals(). After I did that, a is still not defined.Transvestite
@conan well the horrible example I've added appears to work (only tested with typing stuff in, in IDLE though)Glandulous
@JonClements setdefault() does work. but get() appears to work. I cannot access the var after using get(). >>> a Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'a' is not defined >>> globals().get('a', 'new') 'new' >>> a Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'a' is not defined Transvestite
A
1

Pythonic is that you should know it before using it especially in local/global vars rather than guess where it is. You can write this code but it is not pythonic

a = None
# other codes
a = a or 'new'

is all right

Autochthonous answered 20/7, 2012 at 9:8 Comment(0)
W
1

Consider using memoization, which is pretty Pythonic. Here's some Python 2 code:

# Backport of Python 3's lru_cache
# pip install functools32
from functools32 import lru_cache

@lru_cache()
def expensive_db_query(arg):
    print "Running expensive db query for", arg
    return arg[::-1]

expensive_db_query("spam") # prints "Running expensive db query for spam"
expensive_db_query("spam") # No output
expensive_db_query("eggs") # prints "Running expensive db query for eggs"

If you want to have the cache forget values after enough time has elapsed and query the database again, check out Ilialuk's lru cache.

Willena answered 17/3, 2014 at 16:28 Comment(0)
T
1

Here's a variant inspired by this answer on how to check if a variable is defined:

a = a if "a" in vars() or "a" in globals() else "new"

It's not as short but it's at least one line.

In [1]: a
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-1-60b725f10c9c> in <module>()
----> 1 a

NameError: name 'a' is not defined

In [2]: a = a if "a" in vars() or "a" in globals() else "new"

In [3]: a
Out[3]: 'new'

In [4]: a = "A exists!"

In [5]: a = a if "a" in vars() or "a" in globals() else "new"

In [6]: a
Out[6]: 'A exists!'

That said, I agree with BrenBarn that you should avoid the issue with an undefined variable by just declaring it with a=None before the block where you might not set it.

Thebault answered 30/11, 2017 at 10:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.