The Love-Hate Relationship

<aside> 💡

Python is great to write but frustrating to maintain. Its flexibility makes developers very productive, but that same flexibility makes codebases inconsistent and difficult to scale.

</aside>

I love writing Python. I hate working with Python.

There’s a plethora of packages supporting both open-source and enterprise use cases, and Python hits a sweet spot for expressiveness. I feel incredibly productive when writing Python.

But the language is too forgiving, where what feels simple at first can often lead to complexity down the road. Python’s low barrier to entry has led to widespread adoption across many technical roles, each with its own distinct way of writing Python. Unlike languages with stricter conventions, Python’s flexibility results in wildly different styles, making codebases hard to understand and maintain as they grow.

A memorable example? Debugging Pandas operations inside a Flask API deployed in a production PySpark environment—and the Flask API was deployed in debug mode!

Which brings me back to a recurring question: How does a Python codebase get to this point?

There’s a clear distinction between writing Python and writing production-grade Python in an organization.

Understanding When Python Becomes Painful

Python has well-documented pain points, and we’ve all seen the same Medium articles rehashing them:

I don’t want to rehash the same points we’ve already read. I’m also not here to convince you to run a type checker (though it’s a great idea), nor do I believe an internal Python style guide is a silver bullet.

Instead, I want to share my own perspective on when and how Python transitions from a powerful and productive tool into an unmaintainable nightmare. From there, I’ll explore ways to mitigate this—and I hope my perspective gets you thinking about how these issues apply to your own organization.

Observation #1 - Different Python Personas

<aside> 💡

Python is used across many disciplines, each with different workflows, priorities, and tolerances for failure. These differences create friction when scaling Python across an organization.

</aside>

Python is easy to adopt and highly expressive. It’s popular across many industries and roles—software engineers, data engineers, data analysts, data scientists, ML engineers, researchers, bioinformaticians, Leetcoders—each with their own Python ecosystems, tooling, and workflows.

At a high level, it’s easy to see why organizations push for Python as a common language—it should, in theory, allow teams to collaborate more effectively. But in practice, each profession has its own way of using Python, its own tooling, and its own perspective on the software development lifecycle, tolerance for failure, and package maintenance. As the organization scales and Python usage picks up, that’s where the friction begins.

A data analyst may only interact with Python through Jupyter notebooks, running everything cell by cell in Pandas dataframes. An ML engineer might rely on specialized Python bindings over C libraries to train models. A backend Python engineer may have never even heard of a dataframe, instead focusing on using FastAPI to build high-performance asynchronous APIs. And a Leetcoder? They’re too busy boasting about how unreadable they can make their “optimal” solution in as few lines as possible.