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?

Conan
  • 561
  • 5
  • 17
  • 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. – tokland Jul 20 '12 at 07:20
  • 1
    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. – Lev Levitsky Jul 20 '12 at 07:22
  • `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 context – Karoly Horvath Jul 20 '12 at 07:22
  • Wait... why would you want to do this? – Joel Cornett Jul 20 '12 at 08:08
  • @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. – Conan Jul 20 '12 at 17:56
  • 1
    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? – Stefan Kanev Jul 20 '12 at 18:38
  • @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. – Conan Jul 20 '12 at 19:44

6 Answers6

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
BrenBarn
  • 242,874
  • 37
  • 412
  • 384
  • 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. – Conan Jul 20 '12 at 07:26
  • 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`. – Jeff Tratner Jul 20 '12 at 07:33
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.

casevh
  • 11,093
  • 1
  • 24
  • 35
  • 1
    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`. – casevh Jul 20 '12 at 07:57
  • You're right, I read the question wrong, hence my comment to it, where I say that that part is a good solution :) – Lev Levitsky Jul 20 '12 at 08:23
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'
Jon Clements
  • 138,671
  • 33
  • 247
  • 280
  • granted, it's a hypothetical, but probably `d.setdefault('a', 'new')` would be a better choice. – Jeff Tratner Jul 20 '12 at 07:35
  • I think get() cannot work with globals() and locals(). After I did that, a is still not defined. – Conan Jul 20 '12 at 08:08
  • @conan well the horrible example I've added appears to work (only tested with typing stuff in, in IDLE though) – Jon Clements Jul 20 '12 at 08:09
  • @JonClements setdefault() does work. but get() appears to work. I cannot access the var after using get(). ```>>> a Traceback (most recent call last): File "", line 1, in NameError: name 'a' is not defined >>> globals().get('a', 'new') 'new' >>> a Traceback (most recent call last): File "", line 1, in NameError: name 'a' is not defined ``` – Conan Jul 20 '12 at 08:15
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

angeloce
  • 11
  • 1
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.

Community
  • 1
  • 1
kuzzooroo
  • 6,788
  • 11
  • 46
  • 84
0

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.

Raniz
  • 10,882
  • 1
  • 32
  • 64