Python. len() ≤ 0

Python. len() <= 0

Photo by Dominik Van Opdenbosch on Unsplash

Is it possible to receive a negative value from the built-in len() function in Python?

The built-in len() function returns the length (the number of items) of an object. Intuitively, the number of items in a collection can’t be negative. It has to be either equal to 0 or greater.

However, I gave it a couple of additional thoughts when I encountered the following production Python code.

if len(files_names) <= 0:
# code

The conditional is getting skipped if the collection is not empty. If it’s empty, we go into the if block. At first glance, it may look like a minor spelling mistake out of inattention. But still, let’s explore the cases when len() could return a negative value.

The built-in len()

First, let’s have a look at the docs:

Return the length (the number of items) of an object. The argument may be a sequence (such as a string, bytes, tuple, list, or range) or a collection (such as a dictionary, set, or frozen set).

Here are some examples of using len() with sequences and mappings.

>>> names = ['Hanna', 'Georg', 'Richard']
>>> len(names)
3
>>> name = 'Theodor'
>>> len(name)
>>>
7
>>> manager = (35, 'Thea')
>>> len(manager)
2
>>> plants_ph = {'fiddle_leaf': 6, 'bird_of_paradise': 5.5 }
>>> len(plants_ph)
2
>>> unique_plants = {'hazel', 'azalea', 'daisy', 'fern'}
>>> len(unique_plants)
4

There is also implementation for someone desiring to dive deeper into the inners.

The special method __len__

The built-in len() function works as a facade to the dunder __len__() method of the object (docs). You can explicitly invoke it for built-in types, such as sequences or collections:

>>> names = ['Hanna', 'Georg', 'Richard']
>>> names.__len__()
3
>>> plants_ph = {'fiddle_leaf': 6, 'bird_of_paradise': 5.5 }
>>> plants_ph.__len__()
2
>>> unique_plants = {'hazel', 'azalea', 'daisy', 'fern'}
>>> unique_plants.__len__()
4

But, if you try to use it with a custom class without defining __len__, it won’t work out, and you will receive an exception:

>>> class Plant:
>>> pass
>>>
>>> len(Plant())
TypeError: object of type 'Plant' has no len()

TypeError exception has been raised as the special method __len__ wasn’t defined, and len() can’t be used with an instance of this class.

Returning a negative value

So, to test the original hypothesis — len() can return a negative value, we need to create a custom type and define the special method __len__ returning it.

First, we’ll emulate a custom container type and define __len__() returning a small negative integer:

>>> class MyList:
>>> def __len__(self):
>>> return -1
>>>
>>> len(MyList())
ValueError: __len__() should return >= 0
>>>
>>> MyList().__len__()
-1

As a result, it goes for ValueError, which corresponds to the documented behavior, even though nothing prevents us from successfully explicitly invoking __len__.

What about returning a large negative number?

>>> import sys
>>> class MyList:
>>> def __len__(self):
>>> return -sys.maxsize - 2**10
>>>
>>> len(MyList())
ValueError: __len__() should return >= 0

We receive the same ValueError no matter how negative the value is.

However, there used to be an OverflowError, according to the bug tracker and the pull request. You can check it on previous python versions (for example, try it out on Python 3.6.15). The previous implementation returning an OverflowError had restrictions for length in CPython implementation, that still works for large positive numbers at the moment of Python 3.11.2:

CPython implementation detail: In CPython, the length is required to be at most sys.maxsize. If the length is larger than sys.maxsize some features (such as len()) may raise OverflowError.

>>> import sys
>>> class MyList:
>>> def __len__(self):
>>> return sys.maxsize + 2 ** 10
>>>
>>> len(MyList())
OverflowError: cannot fit 'int' into an index-sized integer

Summing up, we can’t make len() return a negative value because the output of __len__ is thoroughly validated. That’s why it’s impossible to go into the conditional:

if len(obj) < 0:
print('negative length')

This code isn’t detected as unreachable by either mypy or PyCharm. Possibly, it makes sense to mark it as unreachable to draw attention to it.

The method __len__ lookup

Let’s examine the method lookup as an advanced example and refer to the corresponding paragraph about the special methods:

For custom classes, implicit invocations of special methods are only guaranteed to work correctly if defined on an object’s type, not in the object’s instance dictionary.

The explanation is not easy to grasp at once. It says about 2 cases for defining __len__:

  • defining in the object’s instance dictionary
  • defining on an object’s type (works correctly)

We will look into both cases with examples for clarification. First, create a custom type without __len__ and assign it as an attribute to the object’s instance:

>>> class ImperfectList:
>>> pass
>>>
>>> imperfect_list = ImperfectList()
>>> imperfect_list.__dict__ # object's instance dictionary is empty
{}
>>>
>>> imperfect_list.__len__ = lambda: 100
>>> imperfect_list.__dict__
{'__len__': <function <lambda> at 0x7fc8d86ba040>}
>>>
>>> ImperfectList.__dict__.keys()
dict_keys(['__module__', '__dict__', '__weakref__', '__doc__'])
>>>
>>> len(imperfect_list)
TypeError: object of type 'ImperfectList' has no len()

It shows that implicit invocation of __len__ via len() raises an exception because __len__ is added as an instance attribute outside of the class. Yet, if we define the method __len__ inside the type’s definition, it works correctly:

>>> class PerfectList:
>>> def __len__(self):
>>> return 7
>>>
>>> perfect_list = PerfectList()
>>> perfect_list.__dict__
{}
>>>
>>> PerfectList.__dict__.keys()
dict_keys(['__module__', '__len__', '__dict__', '__weakref__', '__doc__'])
>>>
>>> len(perfect_list)
7

Implicit invocation of the special method __len__ does not use the one defined in the object’s instance dictionary. If you are about to define the methods, place them inside the scope of classes.

We have come to the conclusion we cannot get a negative value from the built-in len() because the value dunder __len__ returns is validated in such a case. If __len__ of a custom type returns a negative value, it will end up with an exception when implicitly invoking it. So, conditionals like this don’t make a difference:

if len(files_names) < 0:

However, don’t forget errors tend to accumulate together. When you come across a minor problem somewhere, there is a high probability you’ll find more major ones around the code. Double-check nearby code to make sure it works as expected.


Python. len() ≤ 0 was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.


This content originally appeared on Level Up Coding - Medium and was authored by Andrei Smykov

Python. len() <= 0

Photo by Dominik Van Opdenbosch on Unsplash

Is it possible to receive a negative value from the built-in len() function in Python?

The built-in len() function returns the length (the number of items) of an object. Intuitively, the number of items in a collection can’t be negative. It has to be either equal to 0 or greater.

However, I gave it a couple of additional thoughts when I encountered the following production Python code.

if len(files_names) <= 0:
# code

The conditional is getting skipped if the collection is not empty. If it’s empty, we go into the if block. At first glance, it may look like a minor spelling mistake out of inattention. But still, let’s explore the cases when len() could return a negative value.

The built-in len()

First, let’s have a look at the docs:

Return the length (the number of items) of an object. The argument may be a sequence (such as a string, bytes, tuple, list, or range) or a collection (such as a dictionary, set, or frozen set).

Here are some examples of using len() with sequences and mappings.

>>> names = ['Hanna', 'Georg', 'Richard']
>>> len(names)
3
>>> name = 'Theodor'
>>> len(name)
>>>
7
>>> manager = (35, 'Thea')
>>> len(manager)
2
>>> plants_ph = {'fiddle_leaf': 6, 'bird_of_paradise': 5.5 }
>>> len(plants_ph)
2
>>> unique_plants = {'hazel', 'azalea', 'daisy', 'fern'}
>>> len(unique_plants)
4

There is also implementation for someone desiring to dive deeper into the inners.

The special method __len__

The built-in len() function works as a facade to the dunder __len__() method of the object (docs). You can explicitly invoke it for built-in types, such as sequences or collections:

>>> names = ['Hanna', 'Georg', 'Richard']
>>> names.__len__()
3
>>> plants_ph = {'fiddle_leaf': 6, 'bird_of_paradise': 5.5 }
>>> plants_ph.__len__()
2
>>> unique_plants = {'hazel', 'azalea', 'daisy', 'fern'}
>>> unique_plants.__len__()
4

But, if you try to use it with a custom class without defining __len__, it won’t work out, and you will receive an exception:

>>> class Plant:
>>> pass
>>>
>>> len(Plant())
TypeError: object of type 'Plant' has no len()

TypeError exception has been raised as the special method __len__ wasn’t defined, and len() can’t be used with an instance of this class.

Returning a negative value

So, to test the original hypothesis — len() can return a negative value, we need to create a custom type and define the special method __len__ returning it.

First, we’ll emulate a custom container type and define __len__() returning a small negative integer:

>>> class MyList:
>>> def __len__(self):
>>> return -1
>>>
>>> len(MyList())
ValueError: __len__() should return >= 0
>>>
>>> MyList().__len__()
-1

As a result, it goes for ValueError, which corresponds to the documented behavior, even though nothing prevents us from successfully explicitly invoking __len__.

What about returning a large negative number?

>>> import sys
>>> class MyList:
>>> def __len__(self):
>>> return -sys.maxsize - 2**10
>>>
>>> len(MyList())
ValueError: __len__() should return >= 0

We receive the same ValueError no matter how negative the value is.

However, there used to be an OverflowError, according to the bug tracker and the pull request. You can check it on previous python versions (for example, try it out on Python 3.6.15). The previous implementation returning an OverflowError had restrictions for length in CPython implementation, that still works for large positive numbers at the moment of Python 3.11.2:

CPython implementation detail: In CPython, the length is required to be at most sys.maxsize. If the length is larger than sys.maxsize some features (such as len()) may raise OverflowError.
>>> import sys
>>> class MyList:
>>> def __len__(self):
>>> return sys.maxsize + 2 ** 10
>>>
>>> len(MyList())
OverflowError: cannot fit 'int' into an index-sized integer

Summing up, we can’t make len() return a negative value because the output of __len__ is thoroughly validated. That’s why it’s impossible to go into the conditional:

if len(obj) < 0:
print('negative length')

This code isn’t detected as unreachable by either mypy or PyCharm. Possibly, it makes sense to mark it as unreachable to draw attention to it.

The method __len__ lookup

Let’s examine the method lookup as an advanced example and refer to the corresponding paragraph about the special methods:

For custom classes, implicit invocations of special methods are only guaranteed to work correctly if defined on an object’s type, not in the object’s instance dictionary.

The explanation is not easy to grasp at once. It says about 2 cases for defining __len__:

  • defining in the object’s instance dictionary
  • defining on an object’s type (works correctly)

We will look into both cases with examples for clarification. First, create a custom type without __len__ and assign it as an attribute to the object’s instance:

>>> class ImperfectList:
>>> pass
>>>
>>> imperfect_list = ImperfectList()
>>> imperfect_list.__dict__ # object's instance dictionary is empty
{}
>>>
>>> imperfect_list.__len__ = lambda: 100
>>> imperfect_list.__dict__
{'__len__': <function <lambda> at 0x7fc8d86ba040>}
>>>
>>> ImperfectList.__dict__.keys()
dict_keys(['__module__', '__dict__', '__weakref__', '__doc__'])
>>>
>>> len(imperfect_list)
TypeError: object of type 'ImperfectList' has no len()

It shows that implicit invocation of __len__ via len() raises an exception because __len__ is added as an instance attribute outside of the class. Yet, if we define the method __len__ inside the type’s definition, it works correctly:

>>> class PerfectList:
>>> def __len__(self):
>>> return 7
>>>
>>> perfect_list = PerfectList()
>>> perfect_list.__dict__
{}
>>>
>>> PerfectList.__dict__.keys()
dict_keys(['__module__', '__len__', '__dict__', '__weakref__', '__doc__'])
>>>
>>> len(perfect_list)
7

Implicit invocation of the special method __len__ does not use the one defined in the object’s instance dictionary. If you are about to define the methods, place them inside the scope of classes.

We have come to the conclusion we cannot get a negative value from the built-in len() because the value dunder __len__ returns is validated in such a case. If __len__ of a custom type returns a negative value, it will end up with an exception when implicitly invoking it. So, conditionals like this don’t make a difference:

if len(files_names) < 0:

However, don’t forget errors tend to accumulate together. When you come across a minor problem somewhere, there is a high probability you’ll find more major ones around the code. Double-check nearby code to make sure it works as expected.


Python. len() ≤ 0 was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.


This content originally appeared on Level Up Coding - Medium and was authored by Andrei Smykov


Print Share Comment Cite Upload Translate Updates
APA

Andrei Smykov | Sciencx (2023-03-27T00:45:27+00:00) Python. len() ≤ 0. Retrieved from https://www.scien.cx/2023/03/27/python-len-%e2%89%a4-0/

MLA
" » Python. len() ≤ 0." Andrei Smykov | Sciencx - Monday March 27, 2023, https://www.scien.cx/2023/03/27/python-len-%e2%89%a4-0/
HARVARD
Andrei Smykov | Sciencx Monday March 27, 2023 » Python. len() ≤ 0., viewed ,<https://www.scien.cx/2023/03/27/python-len-%e2%89%a4-0/>
VANCOUVER
Andrei Smykov | Sciencx - » Python. len() ≤ 0. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2023/03/27/python-len-%e2%89%a4-0/
CHICAGO
" » Python. len() ≤ 0." Andrei Smykov | Sciencx - Accessed . https://www.scien.cx/2023/03/27/python-len-%e2%89%a4-0/
IEEE
" » Python. len() ≤ 0." Andrei Smykov | Sciencx [Online]. Available: https://www.scien.cx/2023/03/27/python-len-%e2%89%a4-0/. [Accessed: ]
rf:citation
» Python. len() ≤ 0 | Andrei Smykov | Sciencx | https://www.scien.cx/2023/03/27/python-len-%e2%89%a4-0/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.