Skip to content
This repository was archived by the owner on Sep 17, 2025. It is now read-only.
This repository was archived by the owner on Sep 17, 2025. It is now read-only.

stats: invalid usage of for enumerate and else clause unconditionally logs "Negative bucket boundaries" and trims off 0 boundary values #520

@odeke-em

Description

@odeke-em

Currently in this code there is code

for ii, bb in enumerate(boundaries):
if bb > 0:
break
else:
ii += 1
if ii:
logger.warning("Dropping {} negative bucket boundaries, the "
"values must be strictly > 0"
.format(ii))
boundaries = boundaries[ii:]

that uses for/else but we mistakenly assume that the else will ONLY be executed if we encounter a break. However as per https://docs.python.org/3/tutorial/controlflow.html
screen shot 2019-02-25 at 3 07 51 am

I believe tries to ensure that the bucket boundaries are > 0. However, there are two subtle bugs:
a) if a bucket boundary starts at 0 e.g. [0, 5, 10, 200], it will always falsely trim the list to [5, 10, 200] but 0 isn't a negative value
b) for and then else will ALWAYS run the else clause whenever the for loop terminates so still we falsely report that 1 value is getting dropped

Illustration of the 2 bugs

To illustrate please take a look at this code below:

#!/usr/bin/env python

def trim(boundaries):
 for ii, bb in enumerate(boundaries):
     if bb > 0:
         break
 else:
     ii += 1

 if ii <= 0:
     return None, boundaries
     
 return boundaries[:ii], boundaries[ii:]

def main():
    samples = [
            [0, 10, 5],
            [1, 2, 5],
            [-1, -2, -5],
    ]

    for sample in samples:
        with_negatives, without_negatives = trim(sample)
        print("original: %s\nwith_negatives:    %s\nwithout_negatives: %s\n\n"%(
            sample, with_negatives, without_negatives))

if __name__ == '__main__':
    main()

which produces

original: [0, 10, 5]
with_negatives:    [0]
without_negatives: [10, 5]


original: [1, 2, 5]
with_negatives:    None
without_negatives: [1, 2, 5]


original: [-1, -2, -5]
with_negatives:    [-1, -2, -5]
without_negatives: []

I think we can definitely fix this code with clarity and less else magic

first_non_negative_elem_index = -1
for i, boundary in enumerate(boundaries): 
     if boundary >= 0: 
         first_non_negative_elem_index = i
         break

if first_non_negative_elem_index > 0:
     logger.warning("Dropping {} negative bucket boundaries, the " 
                    "values must be strictly > 0" 
                    .format(first_non_negative_elem_index)) 
     boundaries = boundaries[first_non_negative_elem_index:] 

Full illustration

#!/usr/bin/env python

def proper_trim(boundaries):
 first_non_negative_index = -1
 for ii, bb in enumerate(boundaries):
     if bb >= 0:
         first_non_negative_index = ii
         break

 if first_non_negative_index < 0:
     return boundaries, None
     
 ii = first_non_negative_index
 return boundaries[:ii], boundaries[ii:]

def trim(boundaries):
 for ii, bb in enumerate(boundaries):
     if bb > 0:
         break
 else:
     ii += 1

 if ii <= 0:
     return None, boundaries
     
 return boundaries[:ii], boundaries[ii:]

def main():
    samples = [
            [0, 10, 5],
            [1, 2, 5],
            [-1, -2, -5],
    ]

    fns = [('buggy_trim', trim), ('fixed_trim', proper_trim)]
    for sample in samples:
        for name, fn in fns:
            with_negatives, without_negatives = fn(sample)
            print("%s        (%s)\nwith_negatives:    %s\nwithout_negatives: %s\n"%(
                name, sample, with_negatives, without_negatives))

        print("\n")
if __name__ == '__main__':
    main()

which gives

$ python foo.py 
buggy_trim        ([0, 10, 5])
with_negatives:    [0]
without_negatives: [10, 5]

fixed_trim        ([0, 10, 5])
with_negatives:    []
without_negatives: [0, 10, 5]



buggy_trim        ([1, 2, 5])
with_negatives:    None
without_negatives: [1, 2, 5]

fixed_trim        ([1, 2, 5])
with_negatives:    []
without_negatives: [1, 2, 5]



buggy_trim        ([-1, -2, -5])
with_negatives:    [-1, -2, -5]
without_negatives: []

fixed_trim        ([-1, -2, -5])
with_negatives:    [-1, -2, -5]
without_negatives: None

I no longer seem to have access to this repo so I'll just /cc @c24t @mayurkale22 @songy23

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions