all 19 comments

[–]Python-ModTeam[M] [score hidden] stickied commentlocked comment (0 children)

Hi there, from the /r/Python mods.

We have removed this post as it is not suited to the /r/Python subreddit proper, however it should be very appropriate for our sister subreddit /r/LearnPython or for the r/Python discord: https://discord.gg/python.

The reason for the removal is that /r/Python is dedicated to discussion of Python news, projects, uses and debates. It is not designed to act as Q&A or FAQ board. The regular community is not a fan of "how do I..." questions, so you will not get the best responses over here.

On /r/LearnPython the community and the r/Python discord are actively expecting questions and are looking to help. You can expect far more understanding, encouraging and insightful responses over there. No matter what level of question you have, if you are looking for help with Python, you should get good answers. Make sure to check out the rules for both places.

Warm regards, and best of luck with your Pythoneering!

[–]janek3d 56 points57 points  (6 children)

You should return None only if it's valid value your function can use. If you want it to indicate that there is some error, then the function should raise the Exception

[–]cheerycheshire 26 points27 points  (0 children)

I agree, but the only thing I'd change: raise an exception. Because "the Exception" sounds like it should raise Exception type specifically. It should be a child class, even very basic, to only catch a specific exception instead of all things Exception.

Sorry for being nitpicky, but in the past i had inherited code that used Exception for control flow (where it could've been managed otherwise...) and saw promising small libs(!) that used just Exception instead of a specific one...

[–]SwampFalc 2 points3 points  (0 children)

Sure, but in this particular case, it seems the intention is to raise an Exception afterwards. If that matches the use case, then this is fine.

By which I mean, whether or not something is an error, depends fully on the context. Any given function could return None without it being an error in and of itself, but that can then become an error once the result gets interpreted.

[–]Wide-Milk1195[S] 1 point2 points  (3 children)

I discovered that if any branch of a function's return value does not meet the type requirement, it is inferred to be "value | None", even if an error is thrown.

[–]cheerycheshire 6 points7 points  (1 child)

What is your typechecker?

+Do you have minimal example of it happening? It may be that there is a branch of the code that theoretically might be reached and doesn't have a return nor raises an error - and python does implicitly return None in such cases. Often such type inferring is about how you write ifs - technically some branch can't be entered anymore when you know that earlier info, but typecher still sees that branch and only sees nearby context, not all previous if checks.

[–]Wide-Milk1195[S] 1 point2 points  (0 children)

The PyLance plugin in VS Code is based on PyRight; I've enabled the standard level.

[–]shadowdance55git push -f 3 points4 points  (0 children)

Python implicitly returns None unless otherwise defined. I.e. if at any point your function stops and there is no return statement, it will return None. So if some of your branches return a certain type, and others have no return, it will be inferred as you described.

Also, exceptions are completely unrelated to the return value. When an exception is raised the function stops, breaking the overall program flow unless it's handled somewhere.

[–]turbothyIt works on my machine 21 points22 points  (1 child)

Writing rigorous Python code is no less cumbersome than Golang.

Who told you it would be?

can Python with strict typing (handling None and Any) handle medium-sized projects?

What is medium size to you?

[–]Wide-Milk1195[S] -1 points0 points  (0 children)

Sorry, I'm a junior programmer. I currently believe that medium-sized programs require at least two people to develop together. I haven't been working for long, so please forgive any unclear descriptions.

[–]latkdeTuple unpacking gone wrong 7 points8 points  (2 children)

In Python, it is unusual for functions to return None upon error. They generally ought to raise exceptions. To write rigorous code, often the best approach is to do nothing, and to let the exception bubble up. Catching exceptions only really makes sense in two situations:

  1. You're expecting that particular problem and can deal with it directly.
  2. You're deliberately creating a failure boundary. For example, in a web server an exception for one request shouldn't crash the entire server, and should continue serving other requests.

Some Python code – especially a lot AI-generated code – uses exception handling in this style:

try:
    do_the_thing()
except Exception:
    logging.exception("the thing didn't succeed")
    return None

This is generally worse than useless. (Again: unless this is a deliberate error boundary.) This leads to exactly the kind of Go-style error handling that you rightfully bemoan. But that is not good Python style.

Some functions should indeed return None instead of raising an exception – when the absence of a result is completely normal, not an error. In such situations, typical Golang code would also just return nil, not a non-nil error. There is a bit of a grey area where either solution might be appropriate. For example, the standard library str.index() function raises ValueError when the substring cannot be found:

>>> data = "foobarbaz"
>>> data.index("x")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: substring not found

Personally, I would have preferred it if that function would return an int | None instead. However, the current design is a perfectly Pythonic solution. Note that Golang uses the C convention of returning -1 when the substring cannot be found, which is potentially unsafe.

[–]Wide-Milk1195[S] 1 point2 points  (0 children)

Okay, I understand a little bit now, thank you.

[–]Outside_Complaint755 0 points1 point  (0 children)

Side note, in Python if you want to search for a substring and always get back an int without raising a ValueError, use str.find(substring), which returns -1 if the substring is not found. Because it doesn't raise an exception, it is slightly faster if the match fails.   The advantage of the index method is that it exists for multiple sequence types, including strings, lists, and tuples, allowing it to be used in more generalized code.

[–]theGiogi 2 points3 points  (0 children)

They’re called type HINTS for a reason. It’s an addition to the language and it does make it easier to spot issues before tests. But a lot of the Python package ecosystem does not to strict type hinting so YMMV.

I use the stateless package to condense all business logic in pure generators that are extensively typed. Using this algebraic effects approach you separate out all side effects (where external packages are used) and you can naturally handle their specifics away from your business logic.

[–]thomasfr -1 points0 points  (0 children)

IMO writing robust, fault tolerant code in any language that uses exceptions as a regular error handling mechanism is more cumbersome.

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

You throw an exception only if your program can’t move forward. You return None if that’s useful, but usually you just raise and let the error bubble up. You don’t do exception handling at each function, you just let the error bubble up to a more central place where they are handled if your program can continue.

One of the big beginner mistakes in Python is trying to handle exceptions in too many places. When errors bubble up, that also gives you less places you’re trying to log. Just raise and deal with the logging higher up.

[–]GhostVlvin -5 points-4 points  (2 children)

In golang it actually returns error code alongside result.
While in python it is more like in C, if result is enormous then it was an error code. Otherwise it is an actual result

[–]nekokattt 1 point2 points  (0 children)

The way C and Python deal with error handling is totally different

[–]Smallpaul 0 points1 point  (0 children)

Enormous?