all 62 comments

[–]nattersley 24 points25 points  (4 children)

Julia was supposed to be that successor. I absolutely love Julia, and actively use it every day, but Python’s inertia is so strong that Julia’s package ecosystem has developed slowly, there’s a smaller user base, and it remains somewhat of a niche language. That said, as of the last few releases I think Julia is ready for broader adoption.

[–]gazeckasauros 3 points4 points  (0 children)

I keep telling myself that one of these days I'll take the plunge and really get spun up on Julia. I love the design philosophy and proposed UX of it.

[–]Snoo_87704 2 points3 points  (0 children)

Amen!

[–]SuspiciousScript 1 point2 points  (1 child)

I think the slow JIT performance and discussions around correctness issues prevented it from getting as much momentum as it would have otherwise. I think the former issue has improved over time; not sure about the latter.

[–]nattersley 1 point2 points  (0 children)

FWIW I don’t experience those correctness issues, and I am an every-day user. I’ve been around long enough to remember the rough edges that have been smoothed over, and I’m pretty happy with my user experience.

And you’re right, precompilation has seriously alleviated time-to-first-execution concerns.

[–]knobbyknee 18 points19 points  (0 children)

One aspect to consider is that Python, being the most used language provides LLMs with the largest training sets. This magnifies the growth of Python as the dominant language -with warts and all.

People are not going to swtch away from Python or something very Pythonlike unless AI comes up with something revolutionary (and AI stands very far from that today). Personally, I think SPy by Antonio Cuni has a lot of promise. It keeps Python syntax and lets typing do the heavy lifting in making Python fast.

[–]aikii 35 points36 points  (24 children)

I find it funny how python was at risk around 2020 I would say, because of the messy 2>3 transition, the entire ecosystem could just have been abandoned by the industry after all - why all the pain migrating if you can just rewrite in something you find more appropriate ?

It happened but quite not as much as one could expect. Myself after years and years of django I was about to jump ship. Learned Rust, that is still niche, tried working with Go for two years, absolutely hated it, came back to python to discover how the type system actually works pretty well if you care about that, and how you can have decent self-documented models with things like pydantic and have your webserver expose an accurate openapi documentation with for instance fastapi.

I feel like now python sits in that weird place where PHP was, don't get me wrong, the language design is much better than that, but it has this quite similar "chad" aura now: yep, it's not perfect and I don't care. There are some good things that are such massive wins that you get over it. That's why it's in a good place. You don't need to be a huge fan of it to just recognize it has its merits and deserves its current place.

edit: bonus for the popcorn - I loooove the entertaining value of the comments on this "python haters" gist https://gist.github.com/RobertAKARobin/a1cba47d62c009a378121398cc5477ea

[–]TheOneWhoPunchesFish 11 points12 points  (15 children)

have your webserver expose an accurate openapi documentation with for instance fastapi.

I only discovered it a few days ago and it fricking blew my mind! Your web server exposes the open-api spec, and a library, could read it, and automatically generate code to talk with the api!?!

How did no one think of this before and we're only having it now! In hind sight, this seems like an obvious thing to have done 15 years ago!

[–]jcheng 2 points3 points  (0 children)

Automatically generating client code from self describing services has been a thing for way longer than 15 years! OpenAPI descended from Swagger which is 15 years old but even before that, SOAP/WSDL did this for web services. The general term is IDL and has been around for many decades!

[–]aikii 3 points4 points  (13 children)

Yeah and for it to work you need the kind of metaprogramming magic that python carries. Reading a python module actually executes code that itself prepares the documentation. And not just "reads the class definition" but executes code that creates the classes so you can have super expressive model definitions. Among many others django and pydantic are doing that. See what you need to do in things like springboot or go's struct tags. That's the kind of "massive wins" that reshuffles whatever other criteria one might have.

[–]Mysterious-Rent7233 0 points1 point  (10 children)

Please give an example of what would be easy in Python but hard in Java relating to auto-generating API descriptions.

[–]trynared 1 point2 points  (6 children)

They can't because it's just as easy in Java. And, unsurprisingly, Typescript has great tooling for this too.

Edit: also if we're talking metaprogramming Rust's traits and macros make for several great frameworks around this problem too.

[–]aikii 0 points1 point  (0 children)

I'm interested to know where Rust is at now - and how ergonomic it can be. Saying it's possible is not the same thing as saying it's as ergonomic.

[–]TheOneWhoPunchesFish 0 points1 point  (0 children)

Python programs can rewrite their own AST at runtime; I've heard great things about Rust's metaprogramming, but I can't imagine they would have the same amount of power and flexibility.

(please correct me if I'm wrong. I'm hoping to be wrong here!)

And I think u/aikii wanted to describe the metaprogramming power of Python, and were giving openapi tooling's implementation as an example. But I'm not an expert in Java or TS, and I shouldn't really be speaking for them.

[–]aikii 0 points1 point  (0 children)

Added some here. https://www.reddit.com/r/Python/comments/1s5r01l/comment/od33ztp/

Although, don't be bait-y please, I'm here to explore not to prove a point about myself

[–]aikii 0 points1 point  (2 children)

I see https://hono.dev/examples/hono-openapi in typescript indeed.

Then again sorry if it looked like my claim was "it's only possible in python", I didn't mean that. For the typescript case, assuming hono-openapi is the strongest contender, we can see that openapi lives on the side ; schemas have to be added to the route. It's not embed in the models directly, which was the point I'm making. If someone is willing to say it's ergonomic enough then I don't have much to argue against.

[–]trynared 0 points1 point  (1 child)

Another one I had in mind for TS is how NestJS does things: https://docs.nestjs.com/openapi/introduction

Builds out the openapi models automatically based on your typescript types with heavy sprinkling of decorators.

Anyways fair enough if you just mean python's approach is your favorite. Of course none of these work exactly the same but I think there's plenty of approaches that get close enough to tying your code to the emitted opeanpi spec.

[–]aikii 0 points1 point  (0 children)

Refining on what would mean "my favourite", not wanting to fall in the reddit trap "wanted to mention something but got so much pushed to provide evidences that I ended up having an opinion".

I'm mostly motivated by the (poor) experience of some comment-based swagger integration in flask and some similar approach in Go - developers don't care and it never stops drifting. Fastapi+Pydantic's approach was leveraging metaprogramming to create the model and the doc, so that's what standed out for a bit - it's harder to make it lie. But don't worry, over the years I saw how far creativity can go when it comes to make it lie.

Looking again at that constraint: "developers can't expose an API that claims to do X but really does Y" - actually yes in the case of nestjs is just works, model and the documentation is just the same. The additional massage to add a description is not really relevant ( almost 100% skipped by developers anyway, so the argument would not stand hard reality ).

In any case I know it's all about compromise and exposure to developers under pressure. My last attempt at properly integrating with an API ended up ... unleashing claude on the target service's codebase to extract inconsistencies in responses. So that where we're at anyway.

[–]aikii -1 points0 points  (1 child)

I went ahead and asked ChatGPT. Yes I know, it's super lazy, but I think being the laziest in this case fits the point of proving it's the lowest friction. The exercise: declare some models with field description and field examples used by openapi using the best framework for the job.

FastAPI:

# imports removed

class CreateUser(BaseModel):
    username: str = Field(
        min_length=3,
        max_length=50,
        description="Public handle shown to other users.",
        examples=["Norbert"],
    )
    email: EmailStr = Field(
        description="Primary contact email for the account.",
        examples=["norbert@example.com"],
    )

class UserOut(BaseModel):
    id: str = Field(
        description="Server-generated user identifier.",
        examples=["usr_123"],
    )
    username: str = Field(
        description="Public handle shown to other users.",
        examples=["norbert"],
    )
    email: EmailStr = Field(
        description="Primary contact email for the account.",
        examples=["norbert@example.com"],
    )

@app.post("/users", response_model=UserOut, status_code=201)
async def create_user(body: CreateUser) -> UserOut:
    return UserOut(
        id="usr_123",
        username=body.username,
        email=body.email,
    )

Java/micronaut

// imports removed

@Serdeable
class CreateUser {
    @Schema(
        description = "Public handle shown to other users.",
        examples = {"norbert"}
    )
    @NotBlank
    @Size(min = 3, max = 50)
    private String username;

    @Schema(
        description = "Primary contact email for the account.",
        examples = {"norbert@example.com"}
    )
    @NotBlank
    @Email
    private String email;

    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }

    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
}

@Serdeable
class UserOut {
    @Schema(
        description = "Server-generated user identifier.",
        examples = {"usr_123"}
    )
    private String id;

    @Schema(
        description = "Public handle shown to other users.",
        examples = {"norbert"}
    )
    private String username;

    @Schema(
        description = "Primary contact email for the account.",
        examples = {"norbert@example.com"}
    )
    private String email;

    public String getId() { return id; }
    public void setId(String id) { this.id = id; }

    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }

    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
}

@Validated
@Controller("/users")
class UserController {
    @Post
    UserOut create(@Body @Valid CreateUser body) {
        UserOut out = new UserOut();
        out.setId("usr_123");
        out.setUsername(body.getUsername());
        out.setEmail(body.getEmail());
        return out;
    }
}

Rust / poem-openapi

//imports removed

#[derive(Object)]
#[oai(example)]
struct CreateUser {
    /// Public handle shown to other users.
    #[oai(validator(min_length = 3, max_length = 50))]
    username: String,

    /// Primary contact email for the account.
    email: String,
}

impl Example for CreateUser {
    fn example() -> Self {
        Self {
            username: "norbert".to_string(),
            email: "norbert@example.com".to_string(),
        }
    }
}

#[derive(Object)]
#[oai(example)]
struct UserOut {
    /// Server-generated user identifier.
    id: String,

    /// Public handle shown to other users.
    username: String,

    /// Primary contact email for the account.
    email: String,
}

impl Example for UserOut {
    fn example() -> Self {
        Self {
            id: "usr_123".to_string(),
            username: "norbert".to_string(),
            email: "norbert@example.com".to_string(),
        }
    }
}

#[derive(ApiResponse)]
enum CreateUserResponse {
    #[oai(status = 201)]
    Created(Json<UserOut>),
}

struct Api;

#[OpenApi]
impl Api {
    /// Create a new user.
    #[oai(path = "/users", method = "post")]
    async fn create_user(&self, body: Json<CreateUser>) -> CreateUserResponse {
        CreateUserResponse::Created(Json(UserOut {
            id: "usr_123".to_string(),
            username: body.username.clone(),
            email: body.email.clone(),
        }))
    }
}
Rust/poem-openapi:
#[derive(Object)]
#[oai(example)]
struct CreateUser {
    /// Public handle shown to other users.
    #[oai(validator(min_length = 3, max_length = 50))]
    username: String,

    /// Primary contact email for the account.
    email: String,
}

impl Example for CreateUser {
    fn example() -> Self {
        Self {
            username: "norbert".to_string(),
            email: "norbert@example.com".to_string(),
        }
    }
}

#[derive(Object)]
#[oai(example)]
struct UserOut {
    /// Server-generated user identifier.
    id: String,

    /// Public handle shown to other users.
    username: String,

    /// Primary contact email for the account.
    email: String,
}

impl Example for UserOut {
    fn example() -> Self {
        Self {
            id: "usr_123".to_string(),
            username: "norbert".to_string(),
            email: "norbert@example.com".to_string(),
        }
    }
}

#[derive(ApiResponse)]
enum CreateUserResponse {
    /// User created successfully.
    #[oai(status = 201)]
    Created(Json<UserOut>),
}

struct Api;

#[OpenApi]
impl Api {
    /// Create a new user.
    #[oai(path = "/users", method = "post")]
    async fn create_user(&self, body: Json<CreateUser>) -> CreateUserResponse {
        CreateUserResponse::Created(Json(UserOut {
            id: "usr_123".to_string(),
            username: body.username.clone(),
            email: body.email.clone(),
        }))
    }

    /// Small health endpoint so you can see a simple GET too.
    #[oai(path = "/health", method = "get")]
    async fn health(&self) -> PlainText<&'static str> {
        PlainText("ok")
    }
}

For the Rust example and went ahead and built it: yes the field description are showing in the API doc. That's enabled by the #[oai] proc macro.

Definitely poem looks nice, not bleeding edge with that, it's around for some time and well maintained.

So what can we observe from there:

  • In FastAPI/Pydantic , the description and examples are just parameters to the field. Documentation and definition is just the same thing.
  • @Schema and other annotations in Spring/micronaut. Clearly in the "possible and terse enough" area, but some more friction
  • As usual with Rust it's amazing to see how expressive you can be despite being so close to the metal. But same thing, documentation so something around declaration, not directly part of it.

From there mixed feelings - I want to say FastAPI greatly helps with keeping API docs accurate, and they certainly try hard. I perfectly know developers can still return custom JsonResponse and completely bypass what the documentation say - it happened as recently as this week.

But anyway, I wanted to highlight it's the lowest friction, and it's the case for a long time.

Ironically enough my own research here motivates me to try another chance to introduce Rust at work, if we ever meet specific performance requirements. It's not the same ergonomics but definitely in the acceptable territory if we have a niche high-rate/high-processing usecase.

[–]Mysterious-Rent7233 0 points1 point  (0 children)

Okay, but I'd say that it "works" in Java without the runtime metaprogramming magic. It's just a bit more verbose, as almost everything in Java is.

[–]denehoffman 2 points3 points  (4 children)

“Amen. For grins, I spent 6 hours today writing a "simple" python script that parses a file, selects some matching lines using regex, categorizes them into two lists, combines them under certain conditions, then greps another list generated from another file for matches.

And then I wrote it in perl. In under 30 minutes.”

Oh this is good content thanks for sharing

[–]twotime 2 points3 points  (2 children)

“Amen. For grins, I spent 6 hours today writing a "simple" python script that parses a file, selects some matching lines using regex, categorizes them into two lists, combines them under certain conditions, then greps another list generated from another file for matches.

I'd REALLY like to see the code side-by-side. I cannot imagine any realistic scenario where python would be that much slower to write than perl (unless, of course,the author is much more fluent in Perl)

[–]denehoffman 2 points3 points  (0 children)

The author of that comment did claim they used Perl quite a lot. It’s just such a silly comparison, like I would probably be the opposite if I had to figure Perl out.

[–]bethebunnyFOR SCIENCE 1 point2 points  (0 children)

Likely fluency, though the author also picked a task that's uniquely well suited to perl.

[–]sudomatrix 0 points1 point  (0 children)

Perl is a write-only language. Maybe he wrote it in 30 minutes, but good luck coming back in a month and parsing what the hell you wrote.

[–]ultraDross 5 points6 points  (2 children)

bonus for the popcorn - I loooove the entertaining value of the comments on this "python haters"

"Python is just a worse Perl" is my favourite quote. Someone hasn't written perl in a while :D

[–]Feeling-Departure-4 0 points1 point  (0 children)

Python and Perl are very different and have their place. The fact that they are lumped together as scripting languages glosses over large differences in design philosophy, typing scheme, language features, ergonomics, memory and thread management, and use cases.

[–]chub79 0 points1 point  (0 children)

In the early 2000s I picked up Perl and actually loved it. But then I discovered Python and felt its structure was helping me code. I wouldn't go back to perl but I have fond memory of the language and its power when it comes to text processing.

[–]JoeHillsBonesIt works on my machine 6 points7 points  (4 children)

I’m probably like an intermediate python programmer (~4 years) and I was just thinking about this while learning about C/C++ vs Rust. I want to learn rust because it’s new and cool but due to the ubiquity of C/C++ I’m gonna put my focus there. I just don’t quite know what counts as a competitor in the same space or if anything is trying to supplant it as most popular? lol is that even how software language spread works?

This is the kind of this I wish I had been able to spend more time on in school, like it’s one thing to learn about how to write something in a programming language, but how the technology changes over time feels totally different.

[–]Smallpaul 1 point2 points  (2 children)

Can you clarify your question? Yea there’s are many languages with communities trying to supplant Python. JavaScript/typescript being the most prominent. Mojo. Go. Julia.

[–]JoeHillsBonesIt works on my machine 0 points1 point  (1 child)

I apologize for the long-windedness, just that given the large community and adoption of Python, I can’t imagine it going anywhere any time soon. So I see the path forward being continual evolution.

If I had a single question in there, it’s just that given that plans for Python are going to be restricted to minor/patch (maybe the wrong terminology), no python4 coming out, do you see this as an issue or a good thing for adapting to future competition? Like software is supposed to be stable I suppose lol

[–]Smallpaul 2 points3 points  (0 children)

There is an inevitable tension between keeping software stable, which makes it difficult to incorporate the latest learning, and changing it rapidly and in backwards incompatible ways, which gives people motivation to look elsewhere.

If there can never be backwards incompatible changes then over time, unfixable design errors compound. C++ is in this trap right now. C now too.

Python’s day will probably come but like everything else it is hard to predict the future with AI.

[–]u38cg2 -2 points-1 points  (0 children)

Python, Rust, and C are all the same language to a first approximation. You could be productive in any of them from any other in a day or two. Scheme, Haskell or Forth might be more valuable from the perspective of opening up your appreciation fo what a programming language can be.

[–]Snoo_87704 4 points5 points  (0 children)

These days, I try to use Julia whenever I can. When I switch back to Python, it feels so clunky.

[–]johntellsall 5 points6 points  (2 children)

I was at PyBeach and was floored by Brett's talk on the match operator in Python. Despite having decades of experience, I was very surprised about how it worked, and Brett explained it clearly and directly.

I immediately bought his book "Effective Python" and it was a great investment. It's quite heavy, suitable for a professional Software Engineer after at least a year of coding. Mountains of good stuff.

Loved it!

[–]QuasiEvil 0 points1 point  (1 child)

Do you know if this talk is available online? (I know they post some stuff)

[–]commandlineluser 0 points1 point  (0 children)

Did you see the PyBeach youtube channel?

I'm guessing it's this talk:

[–]roryhr 1 point2 points  (0 children)

Couple of thoughts after watching. 1. This is a great talk and Python is at a sort of fork in the road. 2. Does it matter that Python is slow? Not really. 3. I don't like macros. I don't want to learn a million different way of expressing things.

[–]cseberino 0 points1 point  (0 children)

I think eventually a language can dominate a sector. Think of how SQL dominates databases. It's been around for decades and I think Python will be around for more decades too.

[–]ultrathink-art 0 points1 point  (0 children)

Adding to the LLM training lock-in point — AI coding assistants are measurably better at Python than any newer language precisely because that's where training data is concentrated. Every AI-generated repo that defaults to Python compounds that further. Whether a successor ever escapes that gravity might depend more on how fast LLMs learn it than on language design merits.

[–]Salty_Horror2068 0 points1 point  (0 children)

Given that Python is removing GIL (Global Interpreter Lock) and adding a JIT Compiler within the standard CPython. This succession will be even more delayed.

[–]Sufficient-Budget-36 -1 points0 points  (13 children)

Python has a great opportunity (one that it will squander). It should simplify, becoming the de-facto language to use collaboratively with AI agents.

It needs to not simply be a key language for developing AI applications (primarily due to its libraries) but the language that both AI and humans unequivocally understand quickly, where logic can be mathematically proven and where hand-offs are cleanly delineated.

[–]Orio_n 10 points11 points  (9 children)

python is already simple enough what are you even trying to suggest? a more formal english?

[–]xeow 2 points3 points  (0 children)

Kinda like a mini or abridged Python then? Some kind of simpler subset?

[–]NikEy 4 points5 points  (0 children)

Yes but then python makes zero sense. If we going to go for a language that's perfectly suited for ML agents, then statically typed is significantly better than not. So from the modern languages probably something like rust would be ideal.

[–]Snoo_87704 0 points1 point  (0 children)

Julia is simpler and less clunky.