10 Nooby Mistakes Devs Often Make In Python

54,135
61
Published 2024-06-11
Here are 10 nooby mistakes that devs often make in Python. Some are more dangerous than others, but all in all, it's good to know about them, and how to avoid them!

▶ Become job-ready with Python:
www.indently.io/

▶ Follow me on Instagram:
www.instagram.com/indentlyreels

00:00 Learning Python made simple
00:05 Intro
00:14 #1 Value is not the same as identity
03:04 #2 Bad looping
05:18 #3 Modifying strings
08:19 #4 Opening files
11:17 #5 Bad type checking
14:34 #6 Enumerating
16:03 #7 Referencing
17:06 #8 Shallow copy
19:31 #9 Bad exceptions
22:11 #10 Bad naming
23:43 Summing it up

All Comments (21)
  • @valcubeto
    00:00 If I'm not wrong, Python has a predefined list of objects, including numbers from 0 to 255, that's why when you create variables with these values, they'll have the same id
  • @niimiyo13
    Dude forgot to mention the most important difference between "is" and "==", so I will. `is` is a Python command. It does what the interpreter tells it to do (similar to "for", "if", "while" and "in") and cannot be changed normally. `==` is a OPERATOR and each class has it's own definition of what "equals" even means, by defining it with the `__eq__` method. That's why objects from different classes can equal each other. The most important difference between `==` and `is` is that the first can be a different logic for each class, which means someone can implement a class that always equals everything or never equals anything (or use really any logic) while `is` will always check for the objects id, not to check if both objects equals but to check if they are the same object (usually by checking the memory address that holds the values). Now, for a beginner this usually doesn't make much difference and for most cases `==` is encouraged, but you should ALWAYS use `is` when checking for None values, since `is` can only match a None if it really is None and will never use a different logic that can falsely match None.
  • @shaolo6006
    ...been using python for years, never knew about the enumerate() start argument. Thanks!
  • @alinchirciu5891
    "a lot of beginners" oh... come on ... There are enough experienced programmers who make these mistakes
  • @valcubeto
    7:48 Optimization trick: if you already know the final size of your list, you can use `[None] * len` to already have space saved in memory, but you can't use methods like `push` (because your list is already filled with None values), you have to manually index it.
  • @ttuurrttlle
    19:35 I feel like the poor exception handling isn't as much a "nooby" mistake as it is a flaw in python's design philosophy. Well, ok it is a nooby mistake, but that's because python makes it tedious to find all the throwable exceptions an operation might do. Either in docs, or in lsp's too. Why you can't just get static analysis of "what are all the possible exceptions this function can throw?" I do not know. I have to imagine it's possible and I've just never seen it.
  • @lilolero4841
    Numer 11: not using the programing thigh high socks
  • @gjioeviojve
    1:50 For integers, Python creates objects for numbers -5 to 256 when it starts, so references to these will use the same object. I believe these values are chosen as they are commonly used. For strings, when creating a string literal of length <=4096, it will do some magic such that future identical string literals point to the same object. Look up "String Interning" for more on how it does this, and when it doesn't do this. (e.g. it does not do interning for strings which are not string literals)
  • @iggyiggz1999
    Regarding exception handling: I understand why you'd use specific exception types if you handle them all in different ways. You should probably specify KeyboardInterrupts for example. But if all you are doing is logging the error, maybe display an error message and closing the program, what is the benefit of writing code for every exception individually? That just seems like a lot of wasted time and extra lines for no reason. What does it matter what kind of exception it is if I am just gonna handle it the same regardless?
  • @dpgwalter
    3:04 you can also loop through the list backwards to avoid issues when changing the list as you loop through it. It doesnt sound like it should work but if you play out the logic it will.
  • @Asopotaan
    This was really helpful. Hope you do another one in the future.
  • I really enjoy your videos and find them very informative. I was wondering if you could create a more advanced video on common mistakes in Python, particularly related to code design. I've noticed that many people tend to duplicate code excessively. Specifically, I'd love to learn more about why inheritance is often not recommended and why composition is considered a better structure in many cases. Thanks in advance if you decide to make such a video! 😄
  • @oida10000
    Note it is ok to change the elements of a list while iterating to it, something like this for i, n in enumerate(nums): nums[i]=10*n is fine, eventhough in this case (overwriting) list comprehension is better: nums=[10*n for n in nums].
  • The second example can be a bit misleading for the viewers. Granted one should avoid modifying a list in place. But the resulting list after deleting "B" does contain "C". One can verify that, when the for loop is done, items = ["A", "C'", "D", "E"]. But indeed it's still recommended to create a copy or declare a empty list to populate.
  • @D0Samp
    Building immutable strings that way is actually faster than you would expect on CPython. There's an optimization that allocates extra storage to a string object when it's built by repeated concatenation so copies can be avoided in most cases, but only if there is at most one reference to it, like here where it's a local variable. But even having that string part of a list already breaks this optimization, and it's not available e.g. on PyPy. Otherwise, if you have a small and fixed number of strings to join, format strings are the fastest since Python 3.6 because you skip the construction of a temporary list or tuple.
  • If you use the Exception e catch-all, you can actually use "raise" to make sure the exception you got but don't/can't handle gets re-raised to the code that called your function. This can be handy sometimes. Edit: A somewhat contrived use case: You catch FileNotFound, but you don't explicitly catch IO errors. Maybe your code doesn't need to know that IO errors are a thing. In both cases, you want to act on the information that the file was not processed correctly. Maybe this is not a hard error in your library. You pass the catch-all exception to the calling function using "raise" after logging or something similar. Perhaps you want to revert your DB update that was probably corrupted, but you don't really care about the specifics of the error raised? Re-raising lets you do just that. I use this to great effect when using complicated libraries like BeautifulSoup.
  • @diegosorte
    Thank you for the video! Regarding mistake #6, using isinstance can be useful to check the type of a variable, by after your explanation it is still not clear to me how to check if two variables have the same type. If I know in advance what type to check, I could do: if isinstance(name, str) and isinstance(number, str): ... But if I do not know to what I need to compare, then something like this would be required?: if isinstance(name, type(number): ... In that case, if I'm going to use the "type" function anyway, wouldn't it be better to just compare the types?: if type(name) == type(number): ... 🤔
  • @casperghst42
    One of the thing with drives me crazy with Python is that it does adhere with namespace, for example with the with ... file, you create a variable within the block, but later outside the block you address the variable (print(content)). If you would do something similar in a language like C, C++ or Java then you would get an error as it would not be a know variable.
  • @MeinDeutschkurs
    Is it possible to append during iterating, to get extra iterations?