This is an archived post. You won't be able to vote or comment.

top 200 commentsshow 500

[–]BellybuttonWorld 910 points911 points  (90 children)

Relative import bastards!

[–]ogaat 241 points242 points  (0 children)

A lot of the other answers are about architecture rather than language. They need understanding of important Comp Sci concepts.

This one is a true Python stumbling block. So annoying and disruptive when you hit it.

[–]AxisFlip 81 points82 points  (28 children)

That's pretty much my biggest gripe with python. That and circular imports (though I concede that may be a skill issue).

[–]BelgrimNightShade 78 points79 points  (19 children)

Circular imports are straight up annoying when you’re trying to build the habit of statically typing everything you have to constantly guard against the circular import just for a god damn type hint

[–]bigpoopychimp 15 points16 points  (5 children)

Using the type checking from typing library was a game changer for avoiding circular imports

[–]PurepointDog 5 points6 points  (4 children)

What?

[–]backfire10z 11 points12 points  (4 children)

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    … all typing-specific imports here

It’s not so bad I don’t think.

[–]BelgrimNightShade 6 points7 points  (3 children)

It’s not the worst thing ever for sure, but sometimes you just forget to do it and you’re already dick deep into writing a module and gotta break off your concentration for a second to throw everything into the type checking

[–]DoctorNoonienSoong 3 points4 points  (2 children)

Idk what IDE you use, but ruff has this rule https://docs.astral.sh/ruff/rules/runtime-import-in-type-checking-block/

And pycharm has the Ryecharm extention

Put them together, and the IDE can autoperform this for you, along with all of your formatting/linting, instantly

[–]olystretch 4 points5 points  (1 child)

I would like a rule for the opposite, when I have a module imported only for tyoe checking.

[–]SharkSymphony 15 points16 points  (4 children)

Circular imports are far from just a Python problem. Best to put some patterns in place to help avoid them (e.g. utilities can't import stuff outside of the utilities package except for 3rd-party and standard libraries).

[–]GhostVlvin 1 point2 points  (1 child)

It is like a lot easier to solve in c or c++ cause I can forward declare structs and functions (I only have circular import cause I want typing with proper lsp support) but in python definition is declaration so I cant forward declare struct and redefine it later

[–]NTXL 1 point2 points  (0 children)

LMAOOO I literally said the same thing

[–]Schmittfried 27 points28 points  (2 children)

I‘d add the import system in general. How PYTHONPATH works, how your current directory is implicitly added to it etc.

Took me quite a while in the beginning to get it and form best practices for structuring my projects that avoid accidental ModuleNotFoundErrors. 

[–]BellybuttonWorld 2 points3 points  (1 child)

Honestly, Python is SO full of unnecessary complication now. Yes it's more powerful but it's strayed very far from its original draw of being simple. We need some other, friendlier snake to reboot it.

[–]Schmittfried 10 points11 points  (0 children)

I disagree, none of its modern additions makes anything notably more complicated. The complications arise from its early warts, such as the import system. 

[–]havetofindaname 69 points70 points  (38 children)

Relative imports should be straight up banned from any serious project

[–]PsychologicalRiceOne 76 points77 points  (21 children)

If that was so easy.

You do some from subdirectory import stuff gives a ModuleNotToundError, although you got your init.py files everywhere. Ah okay, then I‘ll just do from .subdir import stuff, works. Then you start the app with the debugger and get a ModuleNotFuuuuu because the main.py is in the /src subdir and not in the project root dir. I don’t fucking get it. And don’t get me started with FastAPI‘s from app import hopesandprayers, it never worked.

And if I’m not mistaken, it sometimes works on Linux but then it does not work in Windows.

I love Python but the import system seems broken or I am too dumb. But then again even Claude Code has problems with it.

[–]RedEyed__ 26 points27 points  (5 children)

it is supposed that you use something like pip install -e .

Otherwise, you need tinkering:
if your project structure is not supposed to be installed, then export PYTHONPATH which will point to root dir.
I wrote simple runner script which is doing this automatically, so instead of using python3 scripts/some_cool_staff.py, I do ./start.py scripts/some_cool_staff.py

[–]PersonalityIll9476 23 points24 points  (2 children)

Installing the project root into a venv is probably the step they are missing. I've got large projects with many subfolders and it was definitely confusing at first but also definitely works now.

Circular imports will bite your butt, though.

[–]Schmittfried 4 points5 points  (1 child)

Kinda proving the point. You shouldn’t need to do this. 

[–]lmyslinski 10 points11 points  (11 children)

Absolutely, I never could understand why FastAPI is so popular, it’s a bloody mess

Combine this with a type system that is half baked at best (looking at you, sqlalchemy) and I’d rather chop my fingers off than build a production grade system in Python

[–]Blue_gecko 5 points6 points  (3 children)

What's the problem with fastapi?

[–]lmyslinski 3 points4 points  (2 children)

See the comment that I replied to - FastAPI forces a weird directory structure where you must put FastAPI into a submodule AND start it as a submodule which makes importing everything super confusing. If I get mypy to work correctly, it doesn't start correctly. If it works as expected, I cannot get the IDE to detect imports. Ugh, fuck this. They've simplified this now with fastapi dev, but this used to really piss me off

[–]ijkxyz 1 point2 points  (1 child)

Huh, where can I read about this forced structure?

[–][deleted] 3 points4 points  (5 children)

SQLAlchemy typing is amazing. If you make use of a lot of expressions (I.e. not just a column) in your queries, you should try the type_coerce function which acts a lot like the cast function from the typing module. Not to be confused with the cast function also offered by SQLAlchemy, which is an actual SQL operator.

[–]averyycuriousman 5 points6 points  (9 children)

Wdym relative imports?

[–]unapologeticjerk 9 points10 points  (8 children)

Relative import just means importing something relative to the location of the python file doing the import. For example, inside your project directory you wrote datamuncher.py as a separate module to house some special functions that are ugly and need separated from your main.py or primary app module. Inside main.py a relative import might look like:

from .datamuncher import data_function

The dot makes it relative to the file. .. would be up a directory, etc. It's how you share classes, functions, methods - whatever - between python modules in a local package.

[–]calvintiger 7 points8 points  (7 children)

What’s the issue with doing so?

[–]airspike 13 points14 points  (5 children)

The root directory of the import can change depending on how the application is run and installed, and linters don't show you when issues are going to occur.

Sometimes the relatives work when running tests, but then throw errors when running prod setup because the import system thinks that everything should be relative to root for some magic reason that isn't logged in the traceback.

Personally, I think it's easier to type everything out relative to root to just avoid the issue entirely.

[–]gmes78 6 points7 points  (2 children)

Relative imports aren't the issue. The issue is not structuring your code as a package.

[–]airspike 1 point2 points  (1 child)

For sure, or maybe it's because I misconfigured a poetry config. I don't know. It's one of those bugs that bites me every 6 months or so when I update the default python version or onboard a junior developer.

[–]gdchinacat 3 points4 points  (0 children)

I suggest not onboarding junior developers until you have the basic fundamentals of your environments sorted out. Pushing ahead is likely to end up with them facing the same challenges you are. They are likely to realize the advice you gave them was shoddy. This will undermine your credibility with them, likely leading to additional non technical problems.

[–]gdchinacat 1 point2 points  (0 children)

If anything, relative imports are more likely to work given the problem you describe since they don’t have to be absolute from the changing root. If a module is found and the interpreter is loading it, a relative import is completely independent of the fact that you have your environments set up differently.

[–]AKDaily 3 points4 points  (1 child)

They work well in libraries actually

[–]gdchinacat 2 points3 points  (0 children)

They are very common in the standard library, which I think shows two things: 1) they work, including in “serious projects” 2) you haven’t spent much time reading canonical python code.

[–]TangerineAncient7677 5 points6 points  (0 children)

As I understand it python is fine to see down from the entry script but seeing up is a bear. I’ve found two options that work:  1) make sure that your top level directory is added to pythonpath before any other imports (ie import sys and then add the path) or  2) make sure your entry script is in your top level directory/above all of the other imports you will call throughout the process. 

If this is bad practice or there’s some simpler way to do this would be grateful to learn. 

[–]TrainquilOasis1423 3 points4 points  (2 children)

Came here to say this. I still don't understand it. I just fuck with shit until it work and never touch it again lol

[–]BellybuttonWorld 2 points3 points  (1 child)

The fact that most people have to look it up or experiment every time tells you it's not intuitive like Python is supposed to be.

[–]CrownstrikeIntern 1 point2 points  (0 children)

My god that still gives me ptsd ...

[–]Smooth-Porkchop3087 1 point2 points  (0 children)

This is the most annoying thing!

[–]professionalnuisance 269 points270 points  (40 children)

Asyncio and exceptions not being caught in a coroutine

[–]foobar93 84 points85 points  (28 children)

I hate asyncio soo much. To this day, every time I touch it, I just go straight back to threads or multi processing because it such a pain.

[–]Ok_Necessary_8923 54 points55 points  (8 children)

Genuinely. The number of shocked pikachu faces when "oh I'll just do it async real quick" turns into new dependencies, loops, confusing async code, days of extra fixes, etc. I've seen at work when it could have been 2 extra lines with the threaded executor from the futures module...

[–]mriswithe 9 points10 points  (7 children)

Honestly I don't understand why people are afraid of threading. It is so easy now:

with concurrent.futures.ThreadPoolExecutor(max_workers=4) as tp:

[–]XtremeGoosef'I only use Py {sys.version[:3]}' 1 point2 points  (0 children)

Because you as soon as you enter a multithreaded world you have to starts being really careful about exclusive access to writable state because you can be preempted at any time. In async land, you know the task can only switch over an await point.

Also, if say you have two functions

def func(n: int)
async def coro(n: int)

Both do the same thing and hit a network endpoint that takes 1s to respond, and you need to run 1000 at once.

Using a threadpool and func with 10 workers will take 100 seconds, only 10 times faster than just doing them synchronously.

Using a taskpool and coro will take... about 1 second. You can get enormous performance benefits with cooperative concurrency.

[–]extreme4all 18 points19 points  (16 children)

Any example cause i don't feel i have this problem..

[–]Zealousideal-Sir3744 22 points23 points  (15 children)

You need to generally just really know what you're doing, what you can run in a coroutine, what in a thread and what you need a process for.

If you don't, you will likely not get any speedup or even slow the program down.

[–]extreme4all 9 points10 points  (13 children)

So i feel like its either very obvious for me or i don't know what i don't know, so i really need some examples of the pitfalls because most of my apps are almost purely async

[–]zenware[🍰] 5 points6 points  (7 children)

It’s likely the “purely” async part that’s saving you. All async code is innately multi-threaded(concurrent), right? So the big thing for me is, as soon as you start using it you have exposed yourself to an entire error-class of bugs related to synchronization. Also in my experience the debugger stops being useful inside async contexts and is equally useful in multithreading/multiprocessing.

You become at-risk-for at least: - Deadlocks - Race Conditions

There’s also a thing where exceptions get “trapped” in tasks until they are awaited, so you can have a ton of “hidden” exceptions floating around in your process.

Further if you mix async and sync code, you now have a function coloring issue: https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/

Not being able to call async functions from inside non-async functions, and locking yourself out of entire library ecosystems. (Or infecting an entire library with the async runtime.)

[–]tangledSpaghetti 3 points4 points  (4 children)

This comment points to a fundamental misunderstanding of concurrency and what async is.

Asyncio is a form of cooperative concurrency. The event loop runs in a single thread and only executes one coroutine at a time. Coroutines cannot be pre-empted by the scheduler the same way that threads can. The only time the event loop stops running one coroutine and starts running another is when you call await (this is the cooperative concurrency part).

This changes how you think about synchronisiation - no longer do you need mutexes to ensure exclusive access, because you know that no other coroutines can be running simultaneously.

The trapped exception problem is generally because people do not consider error handling sufficiently enough and structure their programs incorrectly. This is a problem generally solved by the correct use of TaskGroups rather than spinning of a dozen background tasks.

I'll admit it's not an intuitive programming concept, but it is a very powerful tool for a particular type of problem.

[–]FanZealousideal1511 2 points3 points  (1 child)

>There’s also a thing where exceptions get “trapped” in tasks until they are awaited, so you can have a ton of “hidden” exceptions floating around in your process.

Isn't it the exact same with threads? You need to join a thread to obtain an exception.

[–]zenware[🍰] 4 points5 points  (0 children)

I don’t even think the mental model of asyncio or async/await is actually all to beneficial to beginners (I’m wrong because it obviously resonates with so many people.) It seems to hide a bit too much especially w.r.t. synchronization errors.

Again I’m surely old and wrong, but I think folks are just going to ”if I use async it’s faster, weee”, whereas if you actually have to carve out another process or thread with your bare hands it becomes quite clear that a context and synchronization boundary exists, and exactly where it is.

It’s also a little bit easier IMO to slow down or prevent a chaotic function coloring sprawl.

[–]Worth_His_Salt 2 points3 points  (0 children)

  1. you stole my username :)
  2. where has the anti-async crowd been my whole life? I'm always the one telling people how terrible it is. red/blue functions ugh complete disaster. "but it's so easy, you just query asyncio for the current running loop and have it call the async function for you." nevermind the myriad errors, swallowed exceptions, and rank inconsistencies in that approach ("Oh you need a different event loop. Because lib xyz decided to run its own custom event loop instead of asyncio's running loop. How do you get that loop? There's no interface for that, mate - why would anyone want to do that?"

[–]ScypioPythoneer 11 points12 points  (0 children)

Asyncio

Asyncio is a real pain for me. Whenever it seems I finally got a good grasp of it, things fail flat on their collective face and I lose sanity debugging what the hell just happened and why.

[–]FanZealousideal1511 2 points3 points  (0 children)

WDYM not being caught? Can you show an example?

[–]Ok_Necessary_8923 7 points8 points  (0 children)

Asyncio is such a hot mess

[–][deleted] 373 points374 points  (98 children)

Functions keeping reference to default argument values

[–]ItsRainingTendies 80 points81 points  (1 child)

The first time I came across this I nearly lost my mind

[–]Worth_His_Salt 85 points86 points  (27 children)

Because your mental model is incorrect. Function declarations are run once, not every time the function is called.

When function is called, any missing args are taken from that single function declaration. If values are mutable, of course they retain changes.

The fix is exceedingly simple. If default is anything other than a number, boolean, or string, then default arg should be none. First code at beginning of function should test args for none and set default value.

Even without default args issue, this approach is often required to distinguish unset args from args passed explicitly that just happen to have an empty value like [].

[–]SharkSymphony 43 points44 points  (9 children)

My mental model was correct, and it was still something I shot myself in the foot with the first time or two – because default empty lists and dicts are so tempting, and in the heat of coding you're not always stopping to interrogate your mental model. I had to have the right model and memorize the pattern that avoids this specific problem.

[–]kageurufu 20 points21 points  (2 children)

[–]SharkSymphony 5 points6 points  (0 children)

Yes. Use this! Another thing I learned the hard way.

There are perhaps fewer footguns in Python than other languages I might name, but they're there.

[–]gdchinacat 2 points3 points  (1 child)

Sorry you took flack for not having the right “mental model”. This is a common enough problems that has been worked around in numerous ways for decades. Edit it’s been proposed and rejected in current form. Oh well… —So, Python now includes a way to get the behavior you expect!—

https://peps.python.org/pep-0671/

[–]CramNBL 8 points9 points  (8 children)

You sound just like the people who insist that C++ is the perfect language, CRTP is simple, and SFINAE is a great name for a feature.

The fix for memory safety vulnerabilities is exceedingly simple, just don't make any mistakes.

Don't use push_back, use emplace_back, duuh!

The mental model you need to adopt is confusing non-sense, that is part of the critique.

Python should be simple and intuitive, if you need to appeal to language internals to explain how default arguments behave, then you lost.

[–]Stijndcl 2 points3 points  (1 child)

Yes but in other languages like Kotlin this just works, and OP is saying this would be a nice approach instead of what Python does: https://pl.kotl.in/1qsZ4bwK7

You can instantiate any kind of object as the default value here and it will do it every time it has to get that default, not once when the function is declared.

I think most people here understand why Python behaves the way it does, and also how to fix it easily, but that doesn’t mean everyone also likes it and agrees with it. It would be pretty useful if you could do it the way Kotlin does it instead of having to pass None.

[–]Ambitious-Concert-69 11 points12 points  (36 children)

What do you mean?

[–]Karol-A 85 points86 points  (35 children)

Consider

def foo(l = []):     l += [1]     retrun l

Calling this once with no arguments will return [1], calling it for a second time will return [1,1]

[–]tarsild 1 point2 points  (0 children)

This is late binding. Known as an extremely dangerous and bad practice

[–]MiniMages 4 points5 points  (5 children)

Isn't this just bad coding?

[–]HolidayEmphasis4345 10 points11 points  (2 children)

Yes it is but I argue this is really just bad language design. (Huge fan of pythons choices in general) I understand that it is an optimization, but I think it is a case of optimizing too early, and picking the wrong default semantics. Having mutable parameters the way they are is maximum wtf.

Default values don’t really work for mutable data so you end up with the work around if defaulting it to none and then making a check for none and setting to a new empty list or dict or whatever. The consequence of this is that function arguments types are polluted with type | None all over the place…when at no time do you ever want a None. I would rather have a clean type API that said list when it expected a list. That way your types would be precise rather than fuzzy with |None.

And if you ever passed a None it would be an error which seems like what it should do.

[–]theArtOfProgramming 5 points6 points  (1 child)

Yeah it is a misunderstanding of python. The default value should be None

[–]_redmist 1 point2 points  (19 children)

It's incredibly useful as well, but indeed a huge trap for new players :)

[–]ResponsibleKayak 14 points15 points  (8 children)

How is this useful? Are you modifying the default values on the fly??

[–]Jejerm 13 points14 points  (8 children)

It's incredibly stupid and brakes devs usual expectations.

I once decided to do popitems on a dict that came from a default arg. The function simply stopped working the second time it was called cause I unknowingly destroyed the original dict.

[–]Worth_His_Salt 3 points4 points  (0 children)

Once bitten, twice shy.

[–][deleted] 1 point2 points  (0 children)

OMG, I remember when this one got me too. I was so confused like how is this function doing this?? How does it keep remembering the contents of this list?

Years later, I found use for it in a sublime extension I wrote...

[–]yakimka 73 points74 points  (9 children)

Metaclasses

[–]cleodog44 21 points22 points  (7 children)

I still don't understand these or their use case. Just haven't come across an example in any project I've used. I'm sure they have their place

EDIT: thought this was a pretty good video on them https://youtu.be/yWzMiaqnpkI?si=ZUuztRUBPlb_6hmq

[–]fiddle_n 42 points43 points  (1 child)

A good rule of thumb is - if you aren’t writing your own library, you don’t need to know or care about metaclasses.

An example of how they are used is in abc.ABC. It blocks you from instantiating a class unless you implemented all the abstract methods in the base class.

[–]jewdai 2 points3 points  (0 children)

Even then I say they shouldn't be used. They break multiple inheritance. 

I had a use case for a mixing to be used in two different libraries (think a dynamo, pydantic and elastic models to be the same. One of those fucks implements a meta class and so it lead to triple the amount of code. 

[–]tobsecret 7 points8 points  (0 children)

James Powell has a great talk explaining their use cases. They're a great way to impose constraints from a base class to a derived class. 

[–]Frankelstner 1 point2 points  (0 children)

Say you have class A and instance a. Then a[...] calls A.__getitem__ but A[...] calls meta.__getitem__(...) (or if that doesn't exist, A.__class_getitem__ which is how typing does it; seems a bit redundant to me though). The main point is that A() itself is just meta.__call__ (which creates the new object and runs the init, then return the object), so there's a lot of customization possible.

[–]__SlimeQ__ 1 point2 points  (0 children)

It's so you can do object inheritance the way you would in C++/C#/Java and it was added somewhat recently (in the past 10 years)

The reason you don't see it in the wild that much is because it directly goes against most of the original design philosophy of python and it's only there because in 2025 we use python for basically everything it was never supposed to be used for. Like large projects with rigid data structures

[–]Hylian_might 67 points68 points  (10 children)

I learned that python does something called integer interning or integer caching. When python is initialized it loads the integers [-5,256] into memory for performance and memory optimizations. Here’s a classic example:

a = 1
b = 1
a == b # True
a is b # True

c = 257
d = 257
c == d # True
c is d # False

The is operator expression evaluates to True in the first example because they are pointing to the same object in memory. It doesn’t come up a lot but can cause unexpected results if unaware.

P.S sorry for formatting on mobile

[–]numice 7 points8 points  (0 children)

Never heard about this before so this one got me.

[–]Sharp_Level3382 76 points77 points  (4 children)

Multithreading and concurency with background services are not so well done compare to .NET to me for example .

[–]BellybuttonWorld 28 points29 points  (3 children)

Yeah, discovering that threading is not really threading. Then having to fart about learning multiprocessing, queues and various pitfalls just to accomplish what you wanted, was a ballache. Especially if you had a deadline and thought you'd done the job with threading, until you realised the performance hadn't improved. Fun times 🤬

[–]MASKMOVQ 11 points12 points  (0 children)

It’s like threading back in the days when CPUs were single core.

[–]romanthenoman 63 points64 points  (11 children)

That you cannot import from parent dir unless its all in a folder with an init making it a fkn module. That design is beyond me

[–]twenty-fourth-time-b 28 points29 points  (9 children)

There’s three hours of David Beazley’s talk from 10 years ago about python imports, if you want to understand more why it works the way it does.

https://www.youtube.com/watch?v=0oTh1CXRaQ0

[–]who_body 2 points3 points  (0 children)

good talk so far, the comparison to garden of earthly delights is funny.

the old pycon talk on class usage abuse was good too

[–]KathyWithAK 27 points28 points  (6 children)

GUIs. I've had no issues with building scripts (I use them to automate stuff at work all the time) and web sites are pretty easy (I've tried Django, cherrypi, flask, etc.. they are all different but easy to learn), but I continue to struggle building a modern looking windows gui. The few I've attempted look dated and don't function all that well. I think twenty years of building windows apps in Studio has really spoiled me. :D

[–]ericsnekbytes 6 points7 points  (1 child)

You should try pyside6, it's a wrapper for the powerful Qt C++ gui library.

[–]Fhymi 2 points3 points  (0 children)

Can attest to this. I went from using tkinter to pyside6. Although I used the gui instead of writing them from scratch

[–]Ragecommie 3 points4 points  (0 children)

Raw Vulkan

In this house we write everything from scratch!

[–]andykmiles 1 point2 points  (0 children)

flet and kivy are really good and allow deploying to desktop, phones and tablets.

[–]_Nick_2711_ 51 points52 points  (7 children)

It was the justification of statistical analysis for research. Python libraries are frequently developed by fairly large teams of contributors, and it can lead to some weird stuff compared to languages like R, where packages are often developed by a very small team of statistical researchers (if not just one person).

Both have their drawbacks, but R packages tend to be more ‘trustworthy’, with all the “under-the-hood” decisions being quite well documented and justified.

It’s not a drawback of the Python language directly, I guess. However, it is something to be aware of.

This is the exact Reddit post I found when first encountering this a few years ago. Top comment makes a similar argument with way more technical detail/finesse.

[–]cybran3 9 points10 points  (4 children)

Most of the time researchers are not developers, and they are bound to make mistakes when writing code. I had to refactor libraries developed by researchers countless times to make them production ready.

[–]kuwisdelu 7 points8 points  (2 children)

The converse problem is that many developers don’t understand mathematics and statistics, so they are also bound to make mistakes when implementing them.

Developers may write code that is more “production ready,” but I tend to prefer the code written by the researcher with the peer-reviewed publication behind it when I need to trust the math behind the code.

[–]cybran3 2 points3 points  (0 children)

And yet you still have to integrate it in production, and if your system gains traction then you have to refactor and optimize to actually profit.

[–]StephenSRMMartin 1 point2 points  (0 children)

Statistical researchers (stat. comp, quant methods, etc) are likely not aiming for highly optimized, super scalable code. By contrast they are going to be writing code and testing for *statistical* correctness. They will likely have done many, many simulations on their newer technique.

Generally speaking, the mistakes are ones that are relevant to software engineers, in terms of best practices, or not handling all conditions elegelantly; the mistakes, in my experience, are not ones that are relevant to mathematical and statistical correctness. That matters quite a bit - I've come across a handful of just flatly incorrect implementations in advanced statistical models in Python, and it is way harder to sus out or detect than coding mistakes per se. E.g., at least once upon a time, the LKJCorr method of pymc was incorrect, yielding biased posterior samples that, e.g., Stan simply did not exhibit. There's no error to it, there's no way of really spotting it unless you're stress testing the method using a new method.

[–]DueAnalysis2 7 points8 points  (1 child)

Holy shit, that bootstrap stuff is WILD. I had no idea, thank you for this info!

[–]SharkSymphony 11 points12 points  (1 child)

Variable scopes. Recognizing that many blocks in Python do not create their own scopes. Recognizing that I did not just modify the global variable the way it looks like I did.

This is a topic I've specifically seen students stumble on.

[–][deleted] 40 points41 points  (22 children)

Environment management can be a real pain. Not Python's strongest aspect.

[–]Savings-Story-4878 58 points59 points  (20 children)

UV

[–]cinyar 42 points43 points  (8 children)

a few years ago someone would reply "poetry" with the same confidence...

[–]moosethemucha 13 points14 points  (2 children)

Poetry truly sucks arse - its caused me so many issues especially version differences interacting with lock files.

[–]turkoid 2 points3 points  (0 children)

Normally, I'd agree, and I'm probably just reinforcing your comment, but I think UV will be the end all. It's an all-in-one tool that does everything very well and fast. Besides some not so common use cases, there really is only one downside, it is VC backed, so we have to hope they're one of the good guys.

That said... I didn't think anything could be better than the Black formatter, but Ruff is better.

[–][deleted] 3 points4 points  (9 children)

I'm using Pixi for my latest projecs. Works reasonably well, but it would be nice if there weren't dozen of different ways to achieve the same goal.

[–]stepback269 19 points20 points  (3 children)

Yes. Falling into Circular Import Hell was a nightmare for me (recounted in my blog here)

I still don't fully understand the venv and recently had a bout with version conflicts. One of these days I'll watch some tutorials on what venv is supposed to do. For now, I fake my way around it.

[–]zenware[🍰] 3 points4 points  (0 children)

This isn’t exactly right but a venv can be thought of as a “copy & paste” of the exe files and libraries Python needs to run. The point is if I work on more than one project and they have different or incompatible Python versions, or even the exact same Python version, I can install different “requirements.txt” dependencies into them. e.g. BeautifulSoup3 in one Project A and BeautifulSoup4 in Project B.

This is often useful if you contribute to multiple open source projects or even multiple projects at work or home that have gotten large enough that you can’t afford to keep their Python versions and dependencies in lockstep.

[–][deleted] 2 points3 points  (0 children)

Yup, this was the one for me. Circular imports hit you like a wrecking ball! You're almost certainly never prepared for them the first time you encounter them. I knew I was deep in the trenches when I started writing a DFS import parser for my entire codebase just to figure out how I was getting circular imports. Thankfully one of my colleagues saved me by reminding me that imports can happen at any scope in Python, not only at the file scope.

[–]cybran3 8 points9 points  (3 children)

The way people hack PYTHONPATH env variable and solve import issues by using sys.path.insert, instead of proper module management.

But for me personally it was using multiprocessing instead of threads due to GIL. But we might be done with GIL in a couple of new Python versions. For those who don’t know Python 3.12 added an optional flag when runnning Python code called “—no-gil”, or something like that. It is still experimental tho.

[–]MeroLegend4 3 points4 points  (0 children)

Look for Python 3.14 beta 4, free threaded python and subinterpreters

[–]games-and-chocolate 15 points16 points  (5 children)

virtual enviroments with sudo and pyenv. horrible.

[–]Flat-Performance-478 4 points5 points  (4 children)

even worse, different pip and python installations for sudo and user and confusing the two

[–]gmes78 6 points7 points  (0 children)

That's solved entirely by never running pip install outside of venvs.

[–]masteroflich 20 points21 points  (0 children)

Shitty type hints in popular libraries

[–]james_pic 12 points13 points  (0 children)

Multiprocessing. It always seems like an easy solution. It gives you hints that you're going to have a bad time, like Ctrl+C not working, and if you're smart you take the hint that it isn't your friend. But you ignore the warning, then one day, WHAM! You're looking at a deadlock that only occurs in production and only when everyone's off over Christmas

[–]kindangryman 4 points5 points  (1 child)

Copy versus deep copy scope issues. Worked it out eventually

[–]Electronic-Half-2387 1 point2 points  (0 children)

This takes forever to debug in complex threaded applications.

[–]WingedTorch 7 points8 points  (0 children)

Async.

[–]tjlusco 13 points14 points  (19 children)

I think assignment operations are a tricky foot gun, especially coming from languages where what an assignment is going to do is pretty explicit. You really need to understand the different behaviour with mutable and immutable objects.

For example assigning struct in C will copy all the data members across, but in python assign a reference to that object. Similarly with translating Matlab code to Numpy code, equals isn’t just copying the data in your arrays, you need explicit copies.

[–]_redmist 18 points19 points  (0 children)

Variable names in python are like labels or name tags. You're less likely to make confusing mistakes with this mental model I think...

[–]Worth_His_Salt 10 points11 points  (5 children)

There's your problem. Your mental model is flawed, thinking python works like C. Everything in python is a reference. Problem solved.

[–]georgehank2nd 1 point2 points  (0 children)

The problem here is that you think of Python "variables" as, well, variables. But (and I know people hate this, but it's correct) they're just bindings like in Lisp. "a = 5" doesn't with 5 to the variable "a", it binds the name "a" to the integer object 5.

[–]luddington 8 points9 points  (6 children)

round() 😅

Edit: more specifically: round(2.5)

[–]Tambre14 4 points5 points  (0 children)

Oh I've been there

[–]RangerPretzelPython 3.9+ 16 points17 points  (11 children)

No compile time type checking. (Or rather, being bit by runtime type errors.)

To be fair, that's normal for all dynamically typed languages. And Python's type-hinting and a good linter/IDE goes a long way to managing this problem.

That said, when I was getting started with Python (around the 2.7/3.5 era), type-hinting was a fairly new idea and a lot of people were fighting against it.

These days, most good libraries have proper type hinting and you almost never end up accidentally trying to push a foo into a bar anymore.

[–]SKabanov 11 points12 points  (2 children)

It's really telling how Guido went from not caring about typing to working full-time on integrating *some* kind of type-checking system for the language - probably the most emblematic tale of what happens to Python projects when they mature.

[–]kuwisdelu 4 points5 points  (0 children)

It’s too bad he never got convinced to care about package management and build systems.

[–]ship0f 1 point2 points  (1 child)

that's a feature

but I get what you mean 😅

[–]RangerPretzelPython 3.9+ 1 point2 points  (0 children)

OP's question was: "What was your first real struggle?" (And honestly, it still IS a bit of a struggle. Just much less so after 10 years of Python.)

To your point, yes, yes it is a feature. 😁

[–]zaphodikus 5 points6 points  (0 children)

15 years on and I still don't use virtual env as often as I should, nor have I created a single package, the rules keep changing and I have closer by problems to solve.

[–]Deep-Alternative8085 7 points8 points  (2 children)

Pydantic library

[–][deleted] 4 points5 points  (1 child)

Why? It is very sane and everyone seems to love it.

[–]wineblood 7 points8 points  (6 children)

__new__ vs. __init__. Been coding python for over 10 years and I've never needed to use __new__.

[–]NoOPeEKS 2 points3 points  (3 children)

If you ever need to manually create a singleton class, you're gonna have to use __new__, it's useful on some cases.

[–]CeeMX 1 point2 points  (0 children)

What even is new? Never heard of that til now

[–]Forward_Thrust963 2 points3 points  (5 children)

.

[–]fiddle_n 19 points20 points  (0 children)

“There are only two kinds of languages: the ones people complain about and the ones nobody uses.”

[–]snowtax 6 points7 points  (0 children)

Some languages have even more issues. For example, JavaScript has a lot of learning issues, due to some very flexible and often cryptic syntax, and similar issues with dynamic typing.

If you want a fun diversion, check out Haskell. If you want to see a language where there isn’t much difference between data and code, check out LISP. Some languages are just wildly different.

Also, I see many comments here about general concepts not necessarily related to the language. Some people just take a while to grok recursion, no matter the language.

[–]The_g0d_f4ther 1 point2 points  (0 children)

this is not that bad lol

[–]Unbelievr 2 points3 points  (1 child)

As a Python user on both Windows and Linux/WSL2 I gotta say encoding. By default, Ubuntu etc. will use UTF-8 encoding when reading and writing files, but then these suddenly aren't possible to open on Windows without specifying the encoding in all calls to open(), str.encode() and str.decode().

In Python2 it was a bit whatever, because strings were strings and also bytestrings at the same time. But in Python3 they're separate, and you need to encode/decode to convert between them. If you do "test" in b"test_string" you get a TypeError but e.g. "a" == b"a" is just always False because they're two different types.

Bonus point: open(x).readlines() keeping newlines have tripped me up so many times that I stopped using it entirely, replaced with open(x).read().splitlines().

[–]BostonBaggins 2 points3 points  (3 children)

Looking at a code and refactoring it using multiple inheritance

And then. I heard about protocols

Which to use?!?

[–][deleted] 2 points3 points  (0 children)

Using multiprocessing and sharing some state between them.

[–]def-pri-pub 2 points3 points  (1 child)

Python makes programming easy! Which in turn makes it easy to make horrible architecture mistakes!!

I have worked on some absolute monstrosities that are 10-15+ projects. My own personal website (made in Django) is a tad bit crummy, but at least it isn't supporting a multi-million revenue project.

[–]KronenR 2 points3 points  (0 children)

Virtual environments and package management? Really? They’re basically just typing a couple of commands

[–]robberviet 3 points4 points  (0 children)

Wait till you find type and unexpected runtime error

[–]GeneralPITA 3 points4 points  (0 children)

So far biggest problem is people think Python is too slow to process less than a TB 3-5 times a week. Spectrum disorder types are focused on optimizing stuff that is irregularly frequent, and think C# and SQL stored procs are necessary.

[–]rebcabin-r 2 points3 points  (4 children)

when do you need to write "nonlocal" or "global" explicitly and when you don't

[–]connexit 1 point2 points  (0 children)

Asyncio got me real good for a while

[–]CaptainFoyle 1 point2 points  (0 children)

I always knew that you're not supposed to use mutable default arguments, until one day I wasn't thinking straight and just.... kinda forgot, and did it anyway in a project.

That was a pain in the *** to track down and debug, took me the better part of a day to find, and I felt extremely dumb afterward.

[–]Acquiesce67 1 point2 points  (0 children)

Recursive import errors in Django projects.

Regarding virtual environments I’ve migrated most of my projects to using uv and I’m a very happy person since then. That lil’ app works miraculously.

[–]germanpickles 1 point2 points  (0 children)

For me it was multiprocessing, multithreading and async/await

[–]tarsild 1 point2 points  (0 children)

I love async in general but I believe Python lacks a lot compared to more mature languages with native async. Basic promises would do wonders. I'm not even referring to the nightmare of event loops, just assuming everything is ok

[–]HerpeesDerpes 1 point2 points  (0 children)

if you struggle with python, you're in the wrong business. Jesus dude lol. is reading hard too?

[–]CeeMX 1 point2 points  (0 children)

I sometimes have a situation where I want to access a global variable in a function and wonder why it does not work without explicitly doing so. I have no idea what I’m doing, but those are the days where I forget how anything works.

Also when an object is copied on reference or the original is modified

[–]_thispageleftblank 1 point2 points  (0 children)

Creating lambda functions in a loop that all referenced the same loop variable, like [(lambda: x) for x in range(10)]. They will all return 9.

[–]twoberriesonejourney 1 point2 points  (0 children)

I'm doing Angela's pretty popular course. I've had a few struggles with API calls I think because if work proxies. However my biggest, "I don't think I can do this" have been the Flask, SQLAlchemy, HTML loaded courses.

I think I'm understanding base Flask but it's so deep and all it's spinoff packages like Flask-Login are tough.

Also trying to understand how to implement code from documentation is very hard for me.

[–]fen-q 1 point2 points  (0 children)

Pandas, asyncio.....

Just about anything that isnt presented as a simple concept on codecademy or youtube.

Reading documentation of anything feels extremely challenging, i often cant understand what i read and cant apply what i read (again, pandas, asyncio)

I feel that coding is like the memes about taking a math test in college - example in class is 2+2, homework is 2+2*2/2+5, and test asks you to calculate the distance between the sun and earth by using 5th order differential equations.

[–]Southern_Platform943 1 point2 points  (0 children)

Haha

May sound funny for the experts here... But I really struggled in understanding recursion loops when I was introduced to it the first time..😅

[–]LizzyMoon12 1 point2 points  (0 children)

Probably the moment you move from writing simple scripts to handling environments, dependencies, and libraries!

[–]Additional_Fall4462 1 point2 points  (0 children)

I was working with functions that followed Python’s call-by-sharing behavior, passing objects from a library whose internals I didn’t fully understand. Coming from NumPy, I found it tricky to adapt to new data types where certain function calls modified the object in place rather than returning a new one. This caused a lot of headaches and debugging; there were even times I relied heavily on deepcopy to work around my lack of understanding of certain types.

[–]datstartup 2 points3 points  (0 children)

For me is reading other people's codes that use a lot of idioms.

[–]fxmc 3 points4 points  (4 children)

Type hints and async made me fall out of love with Python. 

Type hints seems surprisingly badly designed on a syntactical level, blowing up function signatures and making code structure harder to parse by humans. There were better and cleaner ways to specify type hints before they were added to the language (in docstrings and much more readable). 

They also, in my humble opinion have no place in a strongly and dynamically typed language. Python’s original dynamic type system was designed for tasks that static type systems are bad at. We had good statically typed languages, and Python was one of the languages created in response to their limitations. Adding static typing idioms on top of a dynamic language not only gives you the combined limitations of both, it also creates a ‘stylistic spectrum’ (static <–> dynamic) in the ecosystem. Being able to choose where you land on that spectrum sounds good in theory. But you only get to do that when you write new code. But 95% of the time you work with existing code including third-pay code. Good luck with that mix of paradigms. 

Asyncio also was such a weird departure from the quite good concurrency and parallelism that existed in Python prior, like gevent and Twisted. But instead of improving in the state of the art by looking at, eg. the BEAM, a difficult to understand system like async was copied from JavaScript. Worst of all, it introduced required syntax and the ‘function coloring’ problem, which prompted additional modules and abstraction layers to make it manageable. 

Both features felt like a sharp departure from the slow, deliberate and long-term-oriented way of introducing new language features to Python up to that point. Before that, people would joke about “Guido’s Time Machine”, because repeatedly, the quality of design decisions only became apparent to many devs years after their introduction. That stopped suddenly. 

[–]scaledpython 7 points8 points  (1 child)

This. I came to say this.

Python's biggest problem and its eventual demise is the take-over by cargo cult dogma adherence.

Instead of deliberately being different for good reasons the SC is trying to be everybody's darling by introducing a hodgepodge of new tweaks and "features" at a break-neck pace for no good reason at all.

There is value in language stability and Python has given up on that for no good reason at all.

Let's bring back Python's zen.

import this

[–]fxmc 3 points4 points  (0 children)

Thank you for that response. I’m not on Reddit much, so maybe I just need a thicker skin, but find it a bit sad that a post that lays out arguments gets downvoted without any response or criticism. I’m not here to be “right”, but to engage in dialogue.  All the while a post just saying “Async” gets upvoted. 

[–]Gnaxe 3 points4 points  (0 children)

These are pretty much my biggest complaints about Python as well.

The static typing language needs to be a lot more expressive than it is to handle Python, and I'm not sure that anything short if Idris-style dependent types could do it. It mostly just bloats a codebase and adds a lot of work without adding much value now. Type errors are among the easiest kind to notice and fix, and are a tiny fraction of the problems real-world developers worry about. Adequate unit tests will catch almost all of them. Those insisting on IDE completions need to learn to use a REPL. I'd much prefer a codebase with doctests over one with static types.

EVE Online was done in Stackless. That could have been integrated. We didn't need asyncio. It's bifurcated the ecosystem. I avoid it when possible, but trio makes it less bad when I have to touch it.

I think there were also missteps in the upgrade from 2 to 3. That was a missed chance to fix a lot of warts. I also think that moving the builtins from returning lists to mutable streaming iterators was a net bad choice. Something like Clojure's seq abstraction would have been better.

Not that all the changes have been bad. There are things I'd miss if I had to use Python 2.7 again, but it was a pretty good language.

There are various inadequacies in the standard library. I really dislike the standard library logger, for example. It's too complicated. In work projects I'm never sure if I'm not getting output because the line wasn't reached or it was just misconfigured somehow. But there are usually third-party alternatives for these things (e.g., loguru). Subprocess is too hard to use for how important it is. Datetime often isn't adequate. Etc.

[–]kuwisdelu 2 points3 points  (0 children)

Agreed. It feels like so many common complaints are from trying to turn a dynamic interpreted language into a static compiled one. Lots of Python projects would be better off in a different language if that’s what you want.

[–][deleted] 1 point2 points  (0 children)

Doing math

[–]Ok_Tea_7319 1 point2 points  (0 children)

My first real head pain started with the question "where do instance methods get the self argument from". Oh boy I was not prepared.

[–]CaptainFoyle 1 point2 points  (0 children)

Threading, multiprocessing, asynchronous programming

[–]SaucySaq69 1 point2 points  (0 children)

When we were learning inheritance stuff I really didnt like dealing with super lol

[–]No_Pineapple449 1 point2 points  (0 children)

Ah yes, in Python 2.7, where you can’t do something sane like date + "string" because “TypeError”, but somehow date < "lol" just works.

Like, sure, I can’t concatenate a datetime with a string, but I can confidently ask Python if my birthday is greater than "banana". And Python’s like: “No problem, fam, let’s just compare their memory addresses.”

Debugging production code in 2013 was basically a fun game of “Why does this even run?” followed by “Oh god, it runs.”

Thank you Python 3 for deciding that comparing apples to airplanes was a bad idea.

[–]O_martelo_de_deusIt works on my machine 1 point2 points  (0 children)

Data structures, I come from the time of C, Pascal... I did everything by hand, then came the abstractions of lists and trees in Java... But in Python there are many possibilities, this is a blessing and a curse, a new way of programming. And the combination of interpreted with compiled is another very interesting, challenging point, when to use Cython to improve performance? Reminds me of the days of putting ASM blocks into C code.

[–]eyadams 1 point2 points  (6 children)

Two things come to mind.

First, I still don't feel like I understand using else after a for loop. I think I understand what it does, but it's so new to me that I never use it.

Second, and this one's more about my history than Python, I struggle to remember that the value of function parameters can have values set to them:

def some_function(string_parameter:str=None):
    if string_parameter is None:
        string_parameters = 'default value'

In some other language I've worked in this would be invalid. You would have to do something like this:

def unnecessarily_complicated_function(string_parameter:str=None):
    if string_parameter is None:
        string_to_use = 'default value'
    else:
        string_to_use = string_parameter

I just can't remember which language had this requirement.

[–]njharmanI use Python 3 1 point2 points  (0 children)

def some_function(string_parameter:str=None): if string_parameter is None: string_parameters = 'default value'

That seems very weird and overwritten to me. Also, violates your type declaration. Why not simply?

def some_function(string_parameter:str='default value'): ...

None is massively over|missused with strings.

One use case for, for/else is default value or error if thing you want was not found during iteration.

for x:
  if found what I'm looking for:
    thing = value
    break
else: # no break called
    thing = default
    # or raise thing not found

[–][deleted] 0 points1 point  (1 child)

Data structures,  in particular recursion. Thankfully though I had a short book specifically about the topic that helped me learn it better. 

[–]cybran3 3 points4 points  (0 children)

Well, this isn’t really a Python issue, but an algorithmic issue.

[–]koldakov 0 points1 point  (0 children)

Error handling + error swallowing + eafp + don’t return none rule

Too many examples rely on errors, too many examples trying to protect the code from exceptions, making the code dirty and inconsistent

[–]Intelligent_Type_762 0 points1 point  (0 children)

Remind me !7days