5

When I define a class, how can I include arguments in its methods' signatures which have to be of the same class? I am building a graph structure which should work like this, but here is a simplified example:

class Dummy:
    def __init__(self, value: int, previous: Dummy=None):
        self._value = value
        self._previous = previous

    @property
    def value(self):
        return self._value

    def plus_previous(self):
        return self.value + self._previous.value

d1 = Dummy(7)
d2 = Dummy(3, d1)
d2.plus_previous()

This results in the following error:

NameError: name 'Dummy' is not defined

I mean, I can do it the Python 2 way, but I was hoping there is a more python-3-ic solution than this:

class Dummy:
    def __init__(self, value: int, previous=None):
        assert type(previous) is Dummy or previous is None
        ...
Elias Mi
  • 611
  • 6
  • 14
  • PS: obviously, I can't use: type(self) as self is not defined yet in the signature. I could only do this: assert type(previous) is type(self) which at least is a small improvement – Elias Mi Jun 20 '17 at 10:23
  • As an aside, you realize that type hints will not work like the `assert` statement, right? They aren't enforced. They are *hints*. – juanpa.arrivillaga Jun 20 '17 at 10:30

1 Answers1

4

Although I agree, it is a rather ugly hack, you can use strings as type hints as well:

class Dummy:
    def __init__(self, value: int, previous: 'Dummy'=None):
        self._value = value
        self._previous = previous

    @property
    def value(self):
        return self._value

    def plus_previous(self):
        return self.value + self._previous.value

as is described in PEP-484 on type hints:

When a type hint contains names that have not been defined yet, that definition may be expressed as a string literal, to be resolved later.

A situation where this occurs commonly is the definition of a container class, where the class being defined occurs in the signature of some of the methods. For example, the following code (the start of a simple binary tree implementation) does not work:

class Tree:
    def __init__(self, left: Tree, right: Tree):
        self.left = left
        self.right = right

To address this, we write:

class Tree:
    def __init__(self, left: 'Tree', right: 'Tree'):
        self.left = left
        self.right = right

The string literal should contain a valid Python expression (i.e., compile(lit, '', 'eval') should be a valid code object) and it should evaluate without errors once the module has been fully loaded. The local and global namespace in which it is evaluated should be the same namespaces in which default arguments to the same function would be evaluated.

A problem with this hack however is that if you do a rename in the IDE, it is definitely possible that the IDE will not take these string literals into account and thus fail to rename these.

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555