Search code examples
pythonpython-3.xoopmethodsmethod-signature

Method signature arguments of type(self)


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
        ...

Solution

  • 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.