CPython uses reference counting to keep track of an object's liveness. It's a legitimate and widely used strategy, relatively easy to implement. But it's also a PITA if you're not used to having to deal with it.

The CPython API complicates reference counting somewhat: depending on where you are in the C API, references can be new, borrowed, stolen, or copied. Functions return new and borrowed references. A new reference means you now own it, and are responsible for decrementing the reference count when you're done. A borrowed reference means you get to look, but if you want to keep the object you'll have to increment the reference count. Stolen and copied references on the other hand are passed to functions as arguments. Stolen references mean you are no longer responsible for decrementing the reference count. If a function copies the reference, you're still responsible for your reference to the object.

In C, that's not so bad. You have to understand what every function is doing with your reference, but which behavior each function exhibits is chosen in a way that generally saves you lines of code. So there's less manual reference-count adjustment than there might otherwise be at the C level.

But for me, it definitely adds complication. I try to ensure my users never have to deal with reference counts, by dealing with them automatically. But all those possible side-effects on the reference counts from each function mean I have to deal with a tiny explosion of possibilities.

And, as I'm sure you're all aware, the more possibilities there are, the harder it is to check them all and their interactions with each other. In spite of a lot of thinking, I'm still not confident I've gotten all the refcnt bugs worked out on my end.