1

I met a weird bug and I don't think it's my code's problem.

Here are the objects I have got:

  • Boundary: represents domain and range, such that [1, 10], it has a low attribute and a high attribute, in this case low = 1 and high=10
  • Lim: represents union set, such that [1, 10]U[20, 30], stored as self.boundaries = [Boundary([1, 10]), Boundary([20, 30])]

Here's what I am trying to do,

  1. I defined __len__ in boundary, such that len(Boundary([1, 10])) #=> 9
class Boundary:
    def __len__(self):
            return abs(self.high - self.low)
  1. in Lim object, I have self.boundaries, which is a list of Boundary objects. with __len__ in Boundary defined, I coded Lim's __len__ in following:
class Lim:
    def __len__(self):
            return sum([len(bd) for bd in self.boundaries])

Here's how the issue occurred, with following composition:

class Boundary:
    def __len__(self):
        return abs(self.high - self.low)

class Lim:
    def __len__(self):
        return sum([len(bd) for bd in self.boundaries])

print(len(Lim([1, 10], [20, 30])))

# Traceback (most recent call last):
#   File "boundary.py" in <module>
#     print(len(Lim([1, 10], [20, 30])))
#   File "boundary.py", in __len__
#     return sum([len(bd) for bd in self.boundaries])
#   File "boundary.py", in <listcomp>
#     return sum([len(bd) for bd in self.boundaries])
# TypeError: 'float' object cannot be interpreted as an integer

But with this composition:

class Boundary:
    def __len__(self):
        return abs(self.high - self.low)

class Lim:
    def __len__(self):
        return sum([bd.__len__() for bd in self.boundaries])

print(len(Lim([1, 10], [20, 30])))

# Traceback (most recent call last):
#   File "boundary.py",in <module>
#   print(len(Lim([1, 10], [20, 30])))
# TypeError: 'float' object cannot be interpreted as an integer

However, the code finally executes with this composition:

class Boundary:
    def __len__(self):
        return abs(self.high - self.low)

class Lim:
    def __len__(self):
        return sum([bd.__len__() for bd in self.boundaries])

print(Lim([1, 10], [20, 30]).__len__())
# 19

Why changing len() to __len__() will dismiss the error? I will be so glad if you offer some help.

Weilory
  • 2,621
  • 19
  • 35
  • This questions has been answered here already: https://stackoverflow.com/a/2481433/2402281 – tafaust Feb 06 '21 at 07:40
  • @tahesse Sorry, I read that with some relevant posts before, but did not solve my problem – Weilory Feb 06 '21 at 07:43
  • Fair enough. Do you mind sharing your `Lim` and `Boundary` constructor with us? I'll draft a demo for you. – tafaust Feb 06 '21 at 07:52
  • The code as posted would not give the output posted. Even filling in reasonable `__init__` methods, it would not give the output posted. tahesse is most likely right about the cause of the underlying bug, but the code in the question does not reproduce the bug. – user2357112 Feb 06 '21 at 07:55
  • @tahesse I just went out, I will update a constructor as soon as I go back. By the way, the constructor is way more complicated in support of different boundaries such as (), [], (], [), positive and negative infinities. Therefore, instead of pasting 2000 lines here, I will define the Boundary constructor which assign **low** and **high** to self. And Lim constructor which takes a list of boundaries as param and directly assign to **self.boundaries** – Weilory Feb 06 '21 at 07:56
  • 3
    `__len__` is intended to retrieve the size of sequences and other collections - things with discrete, integer sizes, usually bounded by memory limits. Despite the name, it's not appropriate for the lengths of line segments and curves, or the sizes of intervals, or other use cases where the return value needs to be a non-integer. – user2357112 Feb 06 '21 at 08:00
  • Okay, after your comment on the complexity of the boundaries it makes perfect sensor. Exactly as @user2357112supportsMonica pointed out, `len` produces `int` and not `float`. Your error message indicates that. `len` is not what you want in this case because it counts elements of collections. You want to compute euclidean distances for boundaries that must not be `int`. And the `Lim` class should have some converge functionality I guess that sums the distances. Is that what you want to achieve? – tafaust Feb 06 '21 at 08:13
  • @user2357112 supports Monica, thanks very much, I appreciate it. if you would not mind posting it as an answer, I reckon a lot of people just like me are not aware that **len()** returns int only otherwise it reports an error. – Weilory Feb 06 '21 at 08:15
  • Although it’s instinctive, I do hope this feature to be deprecated in the future since it lowers flexibility. – Weilory Feb 06 '21 at 08:19
  • 1
    It will definitely not be deprecated any time soon. See the docs to understand why: https://docs.python.org/3/library/functions.html#len. It does what it is supposed to do. – tafaust Feb 06 '21 at 08:26

1 Answers1

1

As pointed out in the comments, len is not compatible with your use case. I do not know how flexible your solution needs to be but I came up with a minimum working example:

from typing import List

def dist(boundary: object, m='euclidean') -> float:
    """Computes a given distance for object-typed boundaries.
    """
    return boundary.__dist__(m)

def converge(lim: object) -> float:
    """_Integrates_ the distances over boundaries."""
    return lim.__converge__()


class Boundary(object):
    low = 0
    high = 0
    
    def __init__(self, lo: float, hi: float):
        self.low = lo
        self.high = hi
        
    def __dist__(self, m: str) -> float:
        if m == 'euclidean':
            return abs(self.high - self.low)
        else:
            raise Error(f'Unknown distance {m}')

class Lim(object):
    boundaries = []
    def __init__(self, boundaries: List[Boundary]):
        self.boundaries = boundaries

    def __converge__(self) -> float:
        return sum([dist(bd) for bd in self.boundaries])

print(converge(Lim([Boundary(1.0, 10.0), Boundary(20.0, 30.0)])))
# expect abs(10-1) + abs(30-20) = 9+10 = 19

that does what you want (to the best of my knowledge). Further, you can introduce different Boundary classes that base off of the Boundary class. For the base Boundary class, you are able to introduce different distance measures and for the Lim class (being a base class), you can create different convergence schemes. While this answer does not make a single point about your initial problem (it is all in the comments), does that somehow outline a way forward for you?

tafaust
  • 1,457
  • 16
  • 32