Mama told me not to come.

She said, that ain’t the way to have fun.

  • 0 Posts
  • 25 Comments
Joined 2 years ago
cake
Cake day: June 11th, 2023

help-circle

  • Then make it explicit:

    if l is None:
        raise ValueError("Must provide a valid value for...") 
    

    Having an attribute or type error rarely provides the right amount of context to immediately recognize the error, especially if it’s deep inside the application. A lot of our old code makes stupid errors like TypeError: operator - not defined on types NoneType and float, because someone screwed up somewhere and wasn’t strict on checks. Don’t reply on implicit exceptions, explicitly raise them so you can add context, because sometimes stacktraces get lost and all you have is the error message.

    But in my experience, the practical difference between [] and None is essentially zero, except in a few cases, and those should stand out. I have a few places with logic like this:

    if l is None:
        raise MyCustomInvalidException("Must provide a list")
    if not l: 
        # nothing to do
        return
    

    For example, if I make a task runner, an empty list could validly mean no arguments, while a null list means the caller screwed up somewhere and probably forgot to provide them.

    Explicit is better than implicit, and simple is better than complex.






  • you necessarily provide the reader with fewer hints as to what is actually in that variable

    Then make it explicit. I much prefer this:

    def do_foo(bar: list | None):
        if not bar:
            return
        ...
    

    This one communicates to me that the function only makes sense with a non-empty list.

    To this:

    def do_foo(bar):
        if len(bar) == 0:
            return
    

    This just tells me bar is something that has a length (list, dict, str, etc).

    And this is way worse:

    def do_foo(bar: list | None):
        if len(bar) == 0:
            return
    

    This tells me we want an exception if bar is None, which I think is bad style, given that we’re explicitly allowing None here. If we remove the | None and get an exception, than the code is broken because I shouldn’t be able to get that value in that context.

    If I care about the difference between None and empty, then I’ll make that explicit:

    if bar is None:
        ...
    if not bar:
        ...
    

    In any case, I just do not like if len(bar) == 0 because that looks like a mistake (i.e. did the dev know it could throw an error if it’s not a list? Was that intentional?).



  • It’s only vague if coming from a language where it’s invalid or vague semantically. For example:

    • Javascript - [] is truthy for whatever reason
    • C - int x[] = {}; evaluates to true because it’s a pointer; C only evaluates to false if something is 0
    • Rust - invalid because you cannot convert a vec -> bool directly, and there’s no concept of null (same w/ Go, but Go has nil, but requires explicit checks)
    • Lua - empty tables, zero, and empty strings are truthy; basically, it’s truthy unless it’s nil or false

    The only surprising one here is Javascript. I argue Lua and Python make sense for the same reason, Lua just decided to evaluate truthiness based on whether the variable is set, whereas Python decided to evaluate it based on the contents of the variable. I prefer the Python approach here, but I prefer Lua as a language generally (love the simplicity).


  • The notion of truthiness is defined by the language.

    Here are most of the built-in objects considered false:

    • constants defined to be false: None and False
    • zero of any numeric type: 0, 0.0, 0j, Decimal(0), Fraction(0, 1)
    • empty sequences and collections: ‘’, (), [], {}, set(), range(0)

    It’s not something that happens to work, it’s literally defined that way.

    if not x is the common way to tell if you have data or not, and in most cases, the difference between None and empty data ([], {}, etc) isn’t important.

    len(x) == 0 will raise an exception if you give it None, and that’s usually not what you want. So I guess the verbose way to do that is if x is None or len(x) == 0:, but that’s exactly equivalent to if not x, with the small caveat that it doesn’t check if the value has __len__ implemented. If you’re getting wonky types thrown around (i.e. getting a class instance when expecting a list), you have bigger problems.

    I use type hinting on pretty much all “public” methods and functions, and many of my “private” methods and functions as well. As such, sending the wrong types is incredibly unlikely, so not x is more than sufficient and clearly indicates intent (do I have data?).




  • “Many” isn’t the same as “most,” though I don’t think there’s any way to know what “most” is.

    But here’s my reason for agreeing w/ the OP:

    • not x checks both None and emptiness, which is usually desired
    • len(x) == 0 will raise an exception if x is null
    • with type hinting, those should be the only two reasonable options

    It’s nice that it’s slightly faster, though performance has absolutely nothing to do w/ my preference, though it does have something to do with my preference for avoiding exceptions for non-exceptional cases.


  • len(mylist) tells me it’s definitely a list and not null or whatever

    Do you know what else can tell you it’s a list? Type hinting.

    If I care about the distinction between None and an empty list, I’ll make separate checks. len(None) raises an exception, and most of the time that’s not what I want when checking whether there’s something in the list, so not x is generally preferred, especially when type hinting is used consistently enough to be reasonably sure I’m actually getting a list or None. If I get something else, that means things got really broken and they’ll likely get an exception alter (i.e. when doing anything list-like).

    For sequence type objects, such as lists, their truth value is False if they are empty.

    That’s generally exactly what I want. len(x) == 0 doesn’t tell you it’s a list, it just tells you it has __len__. So that could be a dict, list, or a number of other types that have a length, but aren’t lists.

    Don’t rely on checks like that to tell you what type a thing is.






  • Anyone have a good solution for projects with multiple sub-projects? My structure is like this:

    • root - no venv
      • project_a
        • .venv
        • app/
      • project_b
        • .venv
        • app/

    To get completions to work, I need to manually switch venvs since each uses imports like app.a.b.c. But I frequently work on multiple projects at the same time, so I’d like it to switch venvs based on where the file lives.

    Anyone know if that’s possible? I’m probably missing something obvious since this seems like a fairly common thing.