Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature proposal: Golang style interfaces #497

Closed
thisfred opened this issue Nov 9, 2014 · 7 comments
Closed

Feature proposal: Golang style interfaces #497

thisfred opened this issue Nov 9, 2014 · 7 comments

Comments

@thisfred
Copy link

thisfred commented Nov 9, 2014

I love mypy and think it's a great addition to every Python developers toolkit. One thing I think could be a nice addition is lightweight interfaces such as exist in Go:

https://golang.org/ref/spec#Interface_types

For those unfamiliar with that language, the salient part (IMO) is that in Go you can declare an interface, and use it in place of a type declaration, and any object that implements all methods (with the right type signature) that make up the interface will be a valid value for arguments with that type declaration, so as an example:

from typing import Interface

class StringJoiner(Interface):

    def join_two_strings(self, string1: str, string2: str) -> str:
        """Join two strings together."""
        pass


class ImplementsStringJoiner(object):
    """Implicitly implements the interface."""

    def join_two_strings(self, string1: str, string2: str) -> str:
        """Join two strings together."""
        return string1 + string2


class DoesNotImplementStringJoiner(object):
    """Does not have the required method."""

    pass


def some_function(joiner: StringJoiner, string1: str, string2: str) -> str:
    return joiner.join_two_strings(string1, string2)


#  this should be fine
some_function(ImplementsStringJoiner(), 'foo', 'bar')
# this should not
some_function(DoesNotImplementStringJoiner(), 'foo', 'bar')

What I like about these is that they really embody a static version of Python's ducktyping, where it matters what the object does, rather than what type it is, or what it inherits from.

Here is a very naive first stab, definitely not ready for a PR, (really just a proof of concept, no tests, likely some broken edge cases, and less than pretty code):

https://github.com/thisfred/mypy/compare/golang-style-interfaces?expand=1

If you think these are a good idea at all, I would love to hear feedback on what these interfaces could look like (a base class works, but they could also be class decorators,) and where in mypy the best place to implement them would be.

@JukkaL
Copy link
Collaborator

JukkaL commented Nov 9, 2014

This is definitely a good idea and I've been thinking about something like this for a long time. There's already typing.Protocol that could be used for this purpose (similar to your Interface) but the type checker doesn't know about it.

So your example could be written like this:

from typing import Protocol

class StringJoiner(Protocol):

    def join_two_strings(self, string1: str, string2: str) -> str:
        """Join two strings together."""
        pass

...  # The rest would be identical

This could be implemented in a few phases. In the first phase, we wouldn't have generic protocols (such as MyProtocol[T]). This would already be useful but somewhat limited. The second phase would add support for generic protocols -- these would be somewhat trickier to implement.

@JukkaL
Copy link
Collaborator

JukkaL commented Nov 9, 2014

Some ideas about a basic implementation:

  • We need a new subclass of Type such as ProtocolType (so that it won't clash with typing.Protocol). It may make sense to have an ABC such as ObjectType as a common base class of Instance and ProtocolType, since they are fairly similar in many ways but subtyping and some type operations are different.
  • The semantic analyzer must detect protocol types and create ProtocolType types rather than Instance types.
  • is_subtype must be modified to support protocol types.
  • Various type operations may need to be updated to know about protocol types. At least joins and meets and is_same_type are important. We can start with very simple implementations, though.
  • Many places that deal with Instance types right now should probably use the abstract ObjectType class instead. This includes things such as attribute lookup type checking.

@thisfred
Copy link
Author

thisfred commented Nov 9, 2014

Awesome, I will start digging into Protocols, and will probably have a lot
more questions soon. Thanks for your quick response, and I'm glad to hear
that a feature like this is welcome/already partially exists in mypy!

On Sat, Nov 8, 2014 at 5:22 PM, Jukka Lehtosalo notifications@github.com
wrote:

Some ideas about a basic implementation:

  • We need a new subclass of Type such as ProtocolType (so that it
    won't clash with typing.Protocol). It may make sense to have an ABC
    such as ObjectType as a common base class of Instance and ProtocolType,
    since they are fairly similar in many ways but subtyping and some type
    operations are different.
  • The semantic analyzer must detect protocol types and create
    ProtocolType types rather than Instance types.
  • is_subtype must be modified to support protocol types.
  • Various type operations may need to be updated to know about
    protocol types. At least joins and meets and is_same_type are
    important. We can start with very simple implementations, though.
  • Many places that deal with Instance types right now should probably
    use the abstract ObjectType class instead. This includes things such
    as attribute lookup type checking.


Reply to this email directly or view it on GitHub
#497 (comment).

  • eric casteleijn

@gotgenes
Copy link

I recently did a project in Go and fell in love with its implicit interfaces. They're the greatest idea in OOP in recent history. @thisfred really nailed it when he said,

it matters what the object does, rather than what type it is, or what it inherits from.

This is where Python's abc library got it wrong. ABCMeta seems to provide an idea of abstract interfaces, but its most reliable way of declaring a provider of the interface has two issues:

  1. it does not provide interfaces based on behavior, but rather, through typing and inheritance;
  2. it requires explicit declaration, rather than implicit fulfillment.

To work properly, it requires subclassing:

>>> import abc
>>> class Foo(metaclass=abc.ABCMeta):
...     @abc.abstractmethod
...     def do_something():
...         return NotImplemented
...
>>> class Bar(Foo):
...     pass
...
>>> class Baz(Foo):
...     def do_something():
...         return True
...
>>> bar = Bar()
Traceback (most recent call last):
  File "<ipython-input-24-482989d4cfb5>", line 1, in <module>
    bar = Bar()
TypeError: Can't instantiate abstract class Bar with abstract methods do_something

>>> baz = Baz()
>>>

Alternatively, one can use the register() method, but this also has two downsides:

  1. it performs no validation that the implementer satisfies the interface;
  2. it also requires explicit declaration.

Riffing on the example above:

>>> import abc
>>> class Foo(metaclass=abc.ABCMeta):
...     @abc.abstractmethod
...     def do_something():
...         return NotImplemented
...
>>> class Bar:
...     pass
...
>>> Foo.register(Bar)
<class '__main__.Bar'>
>>> bar = Bar()
>>> bar.do_something()
Traceback (most recent call last):
  File "<ipython-input-29-2015a90099fc>", line 1, in <module>
    bar.do_something()
AttributeError: 'Bar' object has no attribute 'do_something'

I'm curious if there's been any more discussion in this project on the idea of implicit interfaces. Issue #552 indicates maybe it's become even more difficult.

@gvanrossum
Copy link
Member

This is a duplicate of python/typing#11.

@gotgenes
Copy link

gotgenes commented Nov 29, 2016

@gvanrossum Apologies, thanks for pointing me to the full discussion. This issue was a top hit for me for Go interfaces and Python.

@JukkaL
Copy link
Collaborator

JukkaL commented Dec 8, 2017

Just to be explicit, mypy currently supports structural subtyping through typing_extensions.Protocol (details in the documentation).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants