Question

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.

Was it helpful?

Solution

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"
Licensed under: CC-BY-SA with attribution
scroll top