### 3 Tricky Python Nuances

#### jess unrein on January 16, 2019

This blog post is adapted from a talk I gave last week at ChiPy, the Chicago Python User Group. For more great content like this, I highly recomm... [Read Full]

Good info!

I got caught by number 2 in my previous project, I was trying something like this:

``````def some_function(some_date = datetime.utcnow()):
...
``````

I'd get the same date everytime `some_function` was called.

Changed to something like this:

``````def some_function(some_date = None):
if some_date is None:
some_date = datetime.utcnow()

...
``````

Oof, that's a keeper!

1) you should use a linter like pylint to avoid mistakes like this
2) is can be confusing for small integers because they share references
1 is 1 = True
158896587456 is 158896587456 = False

1.) I mean. I almost always find βyou should use a linter and then you can avoid learningβ to be an unhelpful piece of advice. Also, a linterβs not going to tell you why a nested array is behaving unexpectedly.
2) And true. Python can definitely get a little weird around the edges when you get to arbitrarily large numbers. I hope youβre not hardcoding and checking for identity for integers like this in your code.

You clearly understand references very well! These short examples are, of course, aimed at introducing a concept and encouraging people to learn more. Thanks for providing some trivia!

I tried 158896587456 is 158896587456 on Python 3.6.1 and it answers True :/
(Even with huge numbers it does answer True)

Sorry, this is true when (at least) one of them is stored into a variable:

``````>>> a = 1
>>> b = 1
>>> print(a is b)
True
>>> a = 123456789
>>> b = 123456789
>>> print(a is b)
False
``````

Indeed ! But it's weird that 123456789 is 123456789 = True, isn't it ? As if Python answers True for a is b if "a is the same as b" before testing if they are the same. Is there a reason for that ?

(123456789+0) is 123456789 = False
but 123456789 is 123456789 = True

Anyway thanks for the example and the answer !

Optimizer gets better and better. In 3.7, even 123456788+1 is 123456789 gives True. It's simply that Python realizes it can evaluate constant pure expressions before compiling to bytecode. And it folds constants, of course.

`>>> compile('123456788+1 is 123456789', '', 'eval').co_consts`

You'll notice there's only one 123456789 there, not two. And there are no 123456788 nor 1.

My favorite quote I've ever heard about Python is this: "Just like Stephen Wright, Python's greatest strength is how well it does one-liners" and I shamelessly use it all the time in posts and comments π. List, dict, set, and generator comprehensions are great strengths even when compared with lambdas or zip() functions. Great article as always Jess!ππ

Good points about lists and default argument. I disagree with your take on `is` v `==`. It is an anti-pattern to test booleans with `is` or `==`. They are already booleans, so there is no reason to write `if x is True:`, just write `if x:`.

Also, Python is happy to use any value in boolean contexts, which makes it even less common to handle booleans explicitly as you suggest.

The only really common case of using `is` in Python code is to test for `None`, as in `if x is None:`. This is clear, explicit, and performant, because `None` is singleton, and there is no overloading for the `is` operator, so it is faster than `==` which can be overloaded via the `__eq__` special method. Other than that, uses cases that actually require or recommend the use of the `is` operator are extremely rare.

I have worked in a couple teams where my engineering managers had a real stick in their craw about always using `is` comparators with booleans to the point of rejecting PRs that didn't follow this pattern. But you may be right that the more Pythonic way would be to write `if x:` when checking for Truth. It's a pattern that I've had drilled into my head that might, in fact, not be as idiomatically Pythonic as I had been led to believe.

However, to your point, if you want to check that something is explicitly `False` (as opposed to something being `False` or `None`) it makes sense to me to use an explicit comparator rather than relying on `if not x:` as `if not` reads `None` as a falsey value. Granted, the use case here is pretty narrow, so it's not super useful.

Thank you for the clarification!

Great article. Makes sense.

However, it looks like you posted the `list_append()` examples without testing them beforehand, for example, these are equivalents:

``````print(list_append(5, 7))
print(list_append(element=5, input_list=7))
``````

and you cannot call `.extend()` on an integer.

Oops, this is my bad retyping something from a screenshot of a slide, so I couldn't copy paste. Thanks for the catch! It should read `print(list_append([5, 7]))`, as the example below it does, and I've amended the text to reflect this.

Wow, thanks for the article and your concise explanations! :) I didn't know the first nuance before. And it's by sheer luck that it didn't hurt my work already, since I used the * notation for lists before - luckily without changing any of the referenced objects afterwards.

And I nearly forgot about the second nuance (shame on me).

I'm certain #2 (and maybe in concert with #1) cost me hours of my life before I learned what was happening. Great list of gotchas!

Thanks for the great article!

Have learned something, ty

Great insight. I cross-linked to this at the end of Dead Simple Python: Data Types and Immutability.

I havenβt seen that one. Iβll have to check it out!

OMG. Every "nuance" you've written about has something (detail or the main topic) you got completely wrong. Just briefly:

1. "references" are simply names for objects, and have nothing to do with pointers (though, of course, if you're writing Python in C, you would probably implement them using pointers, but there's absolutely nothing in the language specification that says it must be so). The reason why [[0]*5]*5 constructs only two lists is simple: you only have two pairs of brackets there (and you evaluate each only once). If you need 6 lists, you need to execute brackets (or list call) six times. You can do it sequentially or in a loop/comprehension, but surely not via an arithmetic operator. (Imagine if 6*7 really constructed six different sevens and then added them.:)

2. "when the function is first declared" is wrong. Default is evaluated every time def statement is executed. You can put your def in a loop, and you'll see the default evaluated every time. Python does have declarations (global, nonlocal and annotations), but def is not one of them. It is simply an executable statement.

3. id values can be reused for objects with nonoverlapping lifetimes, so checking id(a) == id(b) doesn't have to be the same as a is b. More importantly, checking if a is True is quite different than checking if a. It's as if you said "my engineering managers had a real stick in their craw about always using * operators with integers to the point of rejecting PRs that didn't follow this pattern". Sometimes you use *, sometimes you use +. They simply do different things.

code of conduct - report abuse