Overwrite * and ** unpacking with datamodel methods?

292 Views Asked by At

I'm trying to figure out how to give an object the ability to unpack values.

The use case I came up with is the following:

  • Let's have an Interval class, which we'll use to evaluate real valued functions.
  • We would like to ask for
    • Membership, hence __contains__.
    • Iterate over it, by calling with an specific step, or just using a default step, hence the __call__, and __iter__.
  • In the future I'll support union, and intersection.

One of the features I'm looking for, is to be able to call an Interval object and unpack it, such that if I = Interval(1, 2) and a, b = *I then a == 1 and b == 2.

This is mainly to work in functions that will have interval limits in their arguments.

For example a function def Integrate(f, a, b): ... and then we evaluate, integral = Integrate(f, *I) where $f$ real valued function and I is the interval we were discussing.

The problem is that I'm not entirely sure which data model method should I use.

So far the example class I came up with is the following.

class Interval:
    def __init__(self, a, b):
        if a >= b:
            raise Exception("Invalid values for an interval")
        self.upper = b
        self.lower = a

    def __contains__(self, value):
        return self.lower <= value <= self.upper

    def __call__(self, h=_h):
        yield from dense_range(self.lower, self.upper, h)

    def __iter__(self):
        return self()

    def __repr__(self):
        return f"<Interval [{self.lower}, {self.upper}]>"

Any ideas?

1

There are 1 best solutions below

10
ddejohn On

I don't have a lot of experience implementing iterators, but I believe all you need is to yield the relevant items from __iter__:

class Interval:
    def __init__(self, a, b):
        if a >= b:
            raise Exception("Invalid values for an interval")
        self.upper = b
        self.lower = a

    def __contains__(self, value):
        return self.lower <= value <= self.upper

    def __call__(self, h=_h):
        yield from dense_range(self.lower, self.upper, h)

    def __iter__(self):
        yield self.lower
        yield self.upper

    def __repr__(self):
        return f"<Interval [{self.lower}, {self.upper}]>"

Some uses:

In [5]: interval = Interval(1, 7)

In [6]: a, b = interval

In [7]: a
Out[7]: 1

In [8]: b
Out[8]: 7

In [9]: [*interval]
Out[9]: [1, 7]

In [10]: [value for value in interval]
Out[10]: [1, 7]

In [11]: dict(zip("ab", interval))
Out[11]: {'a': 1, 'b': 7}

In [12]: a, b, *c = interval

In [13]: a
Out[13]: 1

In [14]: b
Out[14]: 7

In [15]: c
Out[15]: []

In [16]: a, *b, c = interval

In [17]: a
Out[17]: 1

In [18]: b
Out[18]: []

In [19]: c
Out[19]: 7