0

Before you mark this a duplicate, I have read this question and this question and they don't answer my question (and even seem to confuse me more, not to mention that they're both in Python 2 and I want to know about Python 3). From this question, understand why the following code,

from typing import Hashable


class UnorderedPair(frozenset):
    def __init__(self, left: Hashable, right: Hashable):
        self.left = left
        self.right = right
        super().__init__((left, right))
        if len(self) != 2:
            raise TypeError("There must be 2 distinct elements. Possible duplicate in {{{}, {}}}.".format(left, right))

    def __str__(self):
        return "{{left: {}, right: {}}}".format(self.left, self.right)


if __name__ == '__main__':
    p = UnorderedPair(1, 2)
    print(p)

produces the following error.

TypeError: UnorderedPair expected at most 1 arguments, got 2

It's because I need to override __new__() as well as __init__(). However, this is where it gets confusing for me. Following the answers to the aforementioned question, it looks like I should override __new__() as follows.

from typing import Hashable


class UnorderedPair(frozenset):
    def __new__(cls, *args, **kwargs):
        return super().__new__(cls, *args, **kwargs)

    def __init__(self, left: Hashable, right: Hashable):
        self.left = left
        self.right = right
        super().__init__((left, right))
        if len(self) != 2:
            raise TypeError("There must be 2 distinct elements. Possible duplicate in {{{}, {}}}.".format(left, right))

    def __str__(self):
        return "{{left: {}, right: {}}}".format(self.left, self.right)


if __name__ == '__main__':
    p = UnorderedPair(1, 2)
    print(p)

But this gives me the same error. So maybe I should override it as follows (some code omitted because it's the same as before).

def __new__(cls, left: Hashable, right: Hashable):
    return super().__new__(cls, (left, right))

But this gives the following very cryptic error.

    super().__init__((left, right))
TypeError: object.__init__() takes no parameters

What does it have to do with object.__init__()!?!?!? Anyway, perhaps then I should override like the following.

def __new__(cls, left: Hashable, right: Hashable):
    return super().__new__(cls)

But this still gives the same error.

    super().__init__((left, right))
TypeError: object.__init__() takes no parameters

So, how do I do this properly and make it work? (I think my intentions are clear from the non-working definition of the class.)

Ray
  • 7,833
  • 13
  • 57
  • 91
  • 1
    `frozenset` apparently does not define an `__init__()` (immutable objects generally don't), so your `super` call is being passed all the way up to the root `object` class - which takes no parameters. Either avoid calling `super().__init__()`, or get rid of your `__init__` and do everything in `__new__`. – jasonharper Aug 16 '17 at 18:54
  • Thank you @jasonharper. Some questions: 1. How do you know that `frozenset` doesn't define `__init__()`? How would I find out things like this myself? (Teach a man to fish.) 2. Why do immutable types use `__new__()` to allow subclasses? What can it do that `__init__()` can't??? – Ray Aug 17 '17 at 09:49
  • A truly immutable type HAS to use `__new__()`, because there's no mechanism for setting the initial value at any later time - if `__init__()` was capable of setting the value, anybody could reset the value later, thus violating the immutability. `__new__()` also gives the possibility of returning an existing, cached value instead of creating a new one. – jasonharper Aug 17 '17 at 12:09

0 Answers0