2

This question is more coding style related which is why this question is asked here.

I'm new to Python, coming from PHP. I've been looking through Python style guides & code examples, I've noticed the None data type is used where I wouldn't use it.

I always avoid using the None/Null data type. I always like to keep everything (variable types, param types, property types, return types) in the one data type that they're supposed to be. And so instead of using None, I prefer to have an empty data type (That equates to falsy), so:

For strings: ''

For lists: []

For dicts: {}

For bools: True/False

For ints: 0 if only positive ints expected. -1 if 0 or positive ints are expected. None if any ints (positive, negative, 0) are expected.

For floats: 0.0 if only positive floats expected, otherwise None.

With ints, if I use -1 for default params, then -1 is viewed as the empty int (instead of 0) and will be checked explitly using:

if my_var == -1:
    #... my_var is empty

Here is an example using None as the defaults:

def my_function(my_string=None, my_list=None, my_number=None, my_bool=None, my_dict=None):
    """Return [1, 2, 3] if all args are truthy, otherwise return None.

    - Defaults parameters are all None.
    - Could return list or None.
    """
    if my_list and my_number and my_string and my_bool and my_dict:
        return [1, 2, 3]
    else:
        return None

This is how I would prefer to write the above by using empty data types:

def my_function(my_string: str = '', my_list: list = [], my_number: int = 0, my_bool: bool = False, my_dict: dict = {}) -> list:
    """Return [1, 2, 3] if all args are truthy, otherwise return empty list.

    - Default parameters are empty data types.
    - Will always return the one expected data type.
    - Because of this we can always use data type hints everything.
    """
    if my_list and my_number and my_string and my_bool and my_dict:
        return [1, 2, 3]
    else:
        return []

I hardly ever use the None/Null data type. I would like to hear views about these two coding styles.

  • 1
    "For object instances: None (there's no other option)" This is nonsensical/redundant; in Python, absolutely everything is an object instance. – Delioth Aug 9 at 16:53
  • 1
    One thing: in python, you should not be using mutable objects as the default value, it is literally same object for all function invocations, so any update would spoil it for future calls. – max630 Aug 9 at 17:03
  • 3
    To the contrary, I would argue that the Null value should be used consciously wherever there's a possible difference between an empty value and "no value given". I always considered the decision to treat empty strings and Null values identically in Oracle a major design blunder, and it's not standard SQL either if I remember correctly. Of course, if a variable only can meaningfully have string values, by all means initialize it with a sensible default ('' most of the time). – Hans-Martin Mosner Aug 9 at 17:20
  • 1
    Empty collections have desirable behaviour when you only care about the "empty/none" case insofar as you don't want to do anything in response. E.g. a for loop over an empty collection will do nothing. A for loop over None will crash. When doing nothing is preferred, use empty collections. However, I would avoid things like empty strings, 0/-1, false as defaults at all costs. None throws an error when used. -1 does not. Errors will silently propagate through your system, that could have been detected early if None was used. – Alexander - Reinstate Monica Aug 9 at 18:05
4

There's a conceptual difference between

  • an empty value
  • a default value
  • the absence of a value

Python's None object is generally used to indicate the absence of a value, and is similar to a null pointer in other languages. This isn't 100% perfect, but it's a good fit most of the time.

You can then check whether you have a value if something is not None: .... It is a common error to check a possibly-empty value for truthiness as in if something: ..., because a present value could also be falsey. Consider:

                   value = True    value = False   value = None
bool(value)        True            False           False
value is not None  True            True            False
                                   ^^^^^^^^^^^^^

So a simple truthiness check would not find present values such as False, 0, ''. Furthermore, many empty values are true-ish, especially most user-defined objects. User-defined objects can override truthiness checks via the __bool__ dunder-method, adding to the possible confusion. The something is None/something is not None check suffers from no such problems because it checks for object identity.

Python function arguments can take a default value. However, this default is evaluated at function definition time, which makes these defaults unsuitable for expensive objects or mutable objects. Then, setting the default to None and supplying the default within the function can be better:

def append_items(items=None):
  """Append some items to the list, defaulting to a new list."""
  if items is None:
    items = []

  items.append(1)
  items.append(2)
  return items

As an added benefit, callers can now explicitly request the default value, without having to know exactly what it is.

In some scenarios this can be problematic: when None is an allowed value! Then, you can create your own singleton that stands in for the default value. I sometimes write code like this:

_default = []  # some private object that has an identity

def my_function(argument=_default):
  if argument is _default:
    argument = "the default was chosen"
  return str(argument)

assert my_function() == "the default was chosen", "Argument is optional"
assert my_function(123) == "123", "Can take values"
assert my_function(None) == "None", "None is not the default value"
  • I'm not sure I buy that checking explicitly for None is necessarily better. Building logic around object identity seems like an error to me. If you instead build around truthiness, you can use None or something more sophisticated. If you hard-code None in your checks, it's a lot less flexible. – JimmyJames Aug 9 at 19:24
  • @JimmyJames None is supposed to be a singleton, and checking for identity is the only reasonable approach. As explained in my answer, truthiness can be misleading. Personally, I'm tired of bugs where you cannot set a parameter to zero or the empty string. – amon Aug 9 at 19:29
  • If it's crucial to the algorithm that the reference is None then you should be checking for None. I think something != None is more clear way of doing that. But I disagree that building logic around truthiness is necessarily wrong. Overriding __bool__ is a useful feature. – JimmyJames Aug 9 at 19:38
  • I guess if you want to be sure the reference is to None you need to use is but I still think it should be the exception that you are checking for identity, not the rule. – JimmyJames Aug 9 at 20:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.