Just some traps and pitfalls that took me a while to get used to in Python.
List methods vs. built-in methods
# sort(), sorted(), reverse(), reversed() # sort() modifies the existing list and is a function of the list object # sorted() returns a new list and is a built-in function a = [5,2,8,1,9,3] sorted(a) # ok -> [1, 2, 3, 5, 8, 9] a.sort() # ok -> [1, 2, 3, 5, 8, 9] a.sorted() # ERROR # however, min() and max() are only built-in functions a.min() # ERROR min(a) # ok -> 1 # unless you work with numpy, where min() and max() do exist :S import numpy as np n = np.array([5,2,4,9,1,7]) n.min() # ok -> 1
# I have to call .join() on a string to concatenate a list = confusing. # concatenating a list of strings ", ".join(["apple", "banana", "cherry"]) # 'apple, banana, cherry' # even uglier, when list is not a string ", ".join([str(s) for s in [1,2,3,4]]) # '1, 2, 3, 4'
List of lists
# The wrong way a = [] * 5 # [, , , , ] a.append(1) # [, , , , ] # The right way a = [ for _ in range(5)]
'is' and '==' on ints
# Internally some int objects are cached. When you compare 2 ints, you should use ==. # However, using 'is' can also work in some cases. x = 2000 y = 2000 x is y # False -> good, because this should not be allowed anyways. x = 5 y = 5 x is y # True -> But this works.... # See also # https://stackoverflow.com/questions/306313/is-operator-behaves-unexpectedly-with-integers
Did you find any interesting or funny things in Python, that confused you the first time you saw them? Share them in the comments below.
Top comments (18)
Oh my god, the import/package system.
My senior thesis project was a Python project, and I was coming from the Ruby world. Dear lord, I never got used to the import system. The fact that you have to throw in something like this to import anything local astounded me:
Circular dependencies tripped me up quite a bit too, but I can probably put that down to lack of experience in Python. But man, coming from a world where you could just do this, it was a bit of a shock:
how about having the
__init__.pyfile in the foo/ ?
The path insert is fairly horrible but I don't believe it's recommended.
PYTHONPATHenvironment variable can be useful in a local project (e.g.
export PYTHONPATH=.in the root of your project), then using packages and absolute imports if you've got a lot of modules.
You can also import local modules just by using their name e.g.
import my_moduleto import
my_module.pyfrom the same directory.
I find the Ruby require system a bit too close to PHP for my liking. I prefer that Python and Java abstract away the file system. However, using Bundler to manage your dependencies and put them onto the Ruby path at runtime is better than anything Python has.
In Python3 it's a lot saner (still not perfect though)
To add to that last one. You can actually change manipulate the integer cache using the module ctypes. Do this, if you want to get fired from you job :D
Now we have replaced 7 by 13.
For the string.join, it makes sense, although both choice (
list.join) have inconvenient.
If you look at every single
.strip(), it returns a new string. It never mutate the original one.
listobjects, it's the opposite. It always mutates the original list, and always returns
So, if they implemented the
.join()method on the
listobjects, they would have had to break this "convention". If they didn't, this would have append:
(I'm not even sure it's possible change the type of an existing object)
So, the choice they made doesn't break any conventions and is logic, although it might not be the prettiest. IMHO, it's right choice. And in the zen's opinion too, I guess:
(guess they didn't respect this one for your last example though).
Good post anyway, learned something about python's
int, thanks! :+1:
Python does not forbid many things so try this:
:smiley: Never thought of this before... That's cool! Thanks!
Wow, thank you for your elaborate response! :)
Here's a gotcha I once saw someone post involving dictionary keys:
Yields this output:
It never bit me in any production code, but still, I thought it was interesting enough to share.
I find Python's name-bindings to be especially confusing at times.
In the following code:
The output will be:
Because the name
iis bound to the loop, and not to the function. To get a sequence of numbers, we'd have to do as follows:
make_f(n)creates a new binding. A new
nis bound at every call to
make_f(n), so the values are kept.
When I first started writing Python I couldn't get over the lack of braces. Having written a lot of Ruby and read a lot of unreadable Perl I now think it's a great feature. Readability as a language feature!
I also had a challenge with it until I realized that I basically indented this way anyways with other languages for readability. I am now to the point that all the extra stuff just feels like noise. I have to do a fair amount of JS and I feel like I have to make an effort to ignore all the brackets and semicolons.
Another thing that I learned recently
Because a and b are referencing same memory
It's the case for every single thing in python except for
str. These last three ones are passed by value (meaning we copy their value), everything else is passed by reference (meaning, as you said, the pass the "reference to the memory", the value isn't duplicated).
Have a look at this:
If you don't want a list to be passed by reference, you can do that by calling
The same works with
dict. And obviously, for your custom object, you have to implement it yourself.
This copy methods on
dictare only shallow copies. That means you don't duplicate the values too, they are passed my reference:
But if you want to do a deep copy:
Note: when you pass a variable by reference, it's a 2 way thing of course. If you edit the original on, it'll change the reference, and if you edit the reference, you change the original too.
Basically, all types in Python are passed-by-reference. The distinction is between mutable and immutable types. I recommend reading Python's Data Mode. The first couple of paragraphs are really helpful.
When to use self in classes and methods