A proper way to bring real interfaces to Python

Hi, all! I believe, almost everybody is familiar with the `abc` package. The problem is that the ABC class is not a separate thing from `object`. So, using ABCs often results in complicated inheritance designs and even in the infamous metaclass conflict. Another problem is that ABC performs checks at the moment an object is being instantiated which isn't exactly the way one expects an interface to work. The obvious way would be to enforce the implementation at the moment `type` for that class is created, i.e. on module execution time. I'm aware there was `PEP 245 -- Python Interface Syntax` which was rejected. However, I agree with the idea that the syntax proposed in this PEP wasn't particularly "pythonish". So, I would like to propose adding a third main object called `interface` in addition to `object` and `type` and to use it to define interface objects. Such interfaces could then be used in the class definition in the following way. ``` class MyInterfaceA(interface): def foo(): pass class MyInterfaceB(interface): def bar(): pass class Implementation(implements=[MyInterfaceA, MyInterfaceB]): def foo(): pass def bar(): pass ``` As a proof of concept, I've implemented a library which implements the idea and allows to use this approach right out of the box — https://pypi.org/project/strict-interfaces/. I would like to get any feedback for the idea and the library. Thanks to all!

On Sun, May 05, 2019 at 04:23:58AM +0300, Serge Matveenko wrote:
How will that solve the problem? Your `interface` object will still inherit from both object and type since everything inherits from object and all types inherit from type.
Isn't it? That's how I expect it to work in Python, and I haven't had any problems with it so far. That's not to say there aren't problems, but you should explain what they are rather than assume that others have experienced the same issues you have. Why is the late check a problem? Are you worried about performance? It isn't that I *oppose* moving the checks to class-creation time instead of instantiation time, but I'd like to hear more about why it is a problem.
Module execution time is not necessarily when the class is created, if it matters. -- Steven

On Sun, May 5, 2019 at 6:42 AM Steven D'Aprano <steve@pearwood.info> wrote:
In my realization, `interface` stays aside from the inheritance logic. It has its own tree of inheritance and doesn't participate in MRO of its implementations. This is the key feature and the goal.
Well, you haven't had any problems doesn't mean there are no:)
One of the problems is that ABCs doesn't require a class to implement anything. IF you have only class-methods and never instantiate the class it will be never checked. Another one is that ABCs aren't interfaces at all as they all to have actual implementations being encapsulated in them. This causes misunderstandings of the code and strange behaviors like in `collection.abc` module for instance where there is a lot of implementation and some methods are just mentioned by name. The code could be much more clear if there were only interfaces and partial implementation mixins. Overall, there is no interface implementation in Python at the moment. There is abc module indeed which is just a bunch of legacy hacks from the times when there was no possibility to do it in a better way. Now we have tools in the language to do it better and I think that it's time to rethink this now.
The main problem that this is just checks on an instance without any hard connection to method signatures as they defined and without any check on type annotations or ever treating a class definition as a proper type.

On Sun, May 5, 2019 at 6:42 AM Steven D'Aprano <steve@pearwood.info> wrote:
This doesn't look like moving checks from one place to another for me. It is basically how I would design interfaces in Python at the current state of the language ability to express this concept. I think it is worth mentioning the following here from the POC implementation README. ``` Design Goals * Be as strict as possible * Fail on import time * Do not mess with object and/or type inheritance * Possibility to integrate in CPython Core * Ability to use "out of the box" regardless support in an interpreter ```

Worth maybe noting that this could play interestingly with PEP 484? -- Ryan (ライアン) Yoko Shimomura, ryo (supercell/EGOIST), Hiroyuki Sawano >> everyone else https://refi64.com/ On May 4, 2019, 8:24 PM -0500, Serge Matveenko <s@matveenko.ru>, wrote:

On Sun, May 5, 2019 at 6:40 AM Ryan Gonzalez <rymg19@gmail.com> wrote:
Worth maybe noting that this could play interestingly with PEP 484?
You're right. It already plays along with type hints in different ways. At the moment it requires an exact match of implementation method signatures with the interface including type annotations. There are some examples in test modules https://github.com/lig/python-interfaces/blob/master/tests/interfaces_test.p...

On Sun, May 5, 2019 at 8:23 PM Stephen J. Turnbull <turnbull.stephen.fw@u.tsukuba.ac.jp> wrote:
`zope.interface` has a lot of functionality that is true. However, it is just another hacky way to bring interfaces functionality to a project. My proposal is to finally bring interfaces to Python. I've provided a POC for the idea. I agree that while my POC could be usable in real life projects it lacks some functionality of `zope.interfaces`. However, bringing the basic but strict interface support to Python could greatly benefit to projects like `zope.interface` as it would allow developers to write more reusable code using core Python functionality when it suits their need and rarely depend on external libraries when they need some extra features. Also, `strict-interfaces` provides typing annotations support and could be easily be adopted in conjunction with PEP 544.

On Mon, 6 May 2019 at 03:23, Serge Matveenko <s@matveenko.ru> wrote:
I am not sure why one would need another special base class to enforce checking implementations statically. Currently mypy can already enforce implementations of Protocols/ABCs at instantiation time. I can imagine one can just add a flag (like --early-implementations) without other changes. -- Ivan

On Mon, May 6, 2019 at 5:33 PM Ivan Levkivskyi <levkivskyi@gmail.com> wrote:
This another special base class is not for static checks. This is about checks at module execution time and generally as Steven has pointed out at class creation time. This allows to build interfaces using dynamic factories for instance and then enforce the implementation. I see how mypy, type annotations and protocols are other useful links in the same chain along with interfaces but I cannot see how mypy is the replacement for the later.

Serge Matveenko writes:
Yes, I understand that, but it isn't responsive to my question. zope.interface *does* "bring interfaces to Python", and it has a lot of functionality already. You haven't explained why zope.interfaces is "hacky", nor why it would be so beneficial to have interface support in the stdlib.
"Could" isn't enough to support "should". Real-world examples would help. Eg, point to a stdlib module (which by definition only uses core Python functionality) that could be easier to understand and/or maintain with built-in 'interface' support. (Feel free to assume functionality that hasn't been implemented in 'strict-interface' yet, and wave your hands a lot.) The thing is, projects in one or a few files with few non-stdlib imports just don't need interface support in my experience. They're small enough to keep in my head, and the overhead of defining interfaces and implementing them separately is too great to be worthwhile. Type annotations are helpful in ensuring proper use of classes and functions. In larger projects, the overhead of importing zope.interface is proportionately tiny, and well-worth the effort to create and maintain interfaces that are heavier-weight than function signatures.

On Sun, May 05, 2019 at 04:23:58AM +0300, Serge Matveenko wrote:
Having read this thread, I think the proposal is complex enough that you will need to write a PEP explaining the nature of interfaces, the problem with the status quo, and your suggested solution. I don't think a proof-of-concept class is sufficient. https://www.python.org/dev/peps/ -- Steven

On Wed, May 8, 2019 at 7:30 PM Serge Matveenko <s@matveenko.ru> wrote:
When you come to write it up, I hope you can go into some detail about what you mean by "a third main object". Currently, the hierarchy is reentrant at the apex ("object" is an instance of "type", and "type" is a subclass of "object"); where would "interface" fit in? Would it be a type? Would it be an object? Will both type and interface be subclasses of a BaseType, same as we have with exceptions? ChrisA

On Sun, May 05, 2019 at 04:23:58AM +0300, Serge Matveenko wrote:
How will that solve the problem? Your `interface` object will still inherit from both object and type since everything inherits from object and all types inherit from type.
Isn't it? That's how I expect it to work in Python, and I haven't had any problems with it so far. That's not to say there aren't problems, but you should explain what they are rather than assume that others have experienced the same issues you have. Why is the late check a problem? Are you worried about performance? It isn't that I *oppose* moving the checks to class-creation time instead of instantiation time, but I'd like to hear more about why it is a problem.
Module execution time is not necessarily when the class is created, if it matters. -- Steven

On Sun, May 5, 2019 at 6:42 AM Steven D'Aprano <steve@pearwood.info> wrote:
In my realization, `interface` stays aside from the inheritance logic. It has its own tree of inheritance and doesn't participate in MRO of its implementations. This is the key feature and the goal.
Well, you haven't had any problems doesn't mean there are no:)
One of the problems is that ABCs doesn't require a class to implement anything. IF you have only class-methods and never instantiate the class it will be never checked. Another one is that ABCs aren't interfaces at all as they all to have actual implementations being encapsulated in them. This causes misunderstandings of the code and strange behaviors like in `collection.abc` module for instance where there is a lot of implementation and some methods are just mentioned by name. The code could be much more clear if there were only interfaces and partial implementation mixins. Overall, there is no interface implementation in Python at the moment. There is abc module indeed which is just a bunch of legacy hacks from the times when there was no possibility to do it in a better way. Now we have tools in the language to do it better and I think that it's time to rethink this now.
The main problem that this is just checks on an instance without any hard connection to method signatures as they defined and without any check on type annotations or ever treating a class definition as a proper type.

On Sun, May 5, 2019 at 6:42 AM Steven D'Aprano <steve@pearwood.info> wrote:
This doesn't look like moving checks from one place to another for me. It is basically how I would design interfaces in Python at the current state of the language ability to express this concept. I think it is worth mentioning the following here from the POC implementation README. ``` Design Goals * Be as strict as possible * Fail on import time * Do not mess with object and/or type inheritance * Possibility to integrate in CPython Core * Ability to use "out of the box" regardless support in an interpreter ```

Worth maybe noting that this could play interestingly with PEP 484? -- Ryan (ライアン) Yoko Shimomura, ryo (supercell/EGOIST), Hiroyuki Sawano >> everyone else https://refi64.com/ On May 4, 2019, 8:24 PM -0500, Serge Matveenko <s@matveenko.ru>, wrote:

On Sun, May 5, 2019 at 6:40 AM Ryan Gonzalez <rymg19@gmail.com> wrote:
Worth maybe noting that this could play interestingly with PEP 484?
You're right. It already plays along with type hints in different ways. At the moment it requires an exact match of implementation method signatures with the interface including type annotations. There are some examples in test modules https://github.com/lig/python-interfaces/blob/master/tests/interfaces_test.p...

On Sun, May 5, 2019 at 8:23 PM Stephen J. Turnbull <turnbull.stephen.fw@u.tsukuba.ac.jp> wrote:
`zope.interface` has a lot of functionality that is true. However, it is just another hacky way to bring interfaces functionality to a project. My proposal is to finally bring interfaces to Python. I've provided a POC for the idea. I agree that while my POC could be usable in real life projects it lacks some functionality of `zope.interfaces`. However, bringing the basic but strict interface support to Python could greatly benefit to projects like `zope.interface` as it would allow developers to write more reusable code using core Python functionality when it suits their need and rarely depend on external libraries when they need some extra features. Also, `strict-interfaces` provides typing annotations support and could be easily be adopted in conjunction with PEP 544.

On Mon, 6 May 2019 at 03:23, Serge Matveenko <s@matveenko.ru> wrote:
I am not sure why one would need another special base class to enforce checking implementations statically. Currently mypy can already enforce implementations of Protocols/ABCs at instantiation time. I can imagine one can just add a flag (like --early-implementations) without other changes. -- Ivan

On Mon, May 6, 2019 at 5:33 PM Ivan Levkivskyi <levkivskyi@gmail.com> wrote:
This another special base class is not for static checks. This is about checks at module execution time and generally as Steven has pointed out at class creation time. This allows to build interfaces using dynamic factories for instance and then enforce the implementation. I see how mypy, type annotations and protocols are other useful links in the same chain along with interfaces but I cannot see how mypy is the replacement for the later.

Serge Matveenko writes:
Yes, I understand that, but it isn't responsive to my question. zope.interface *does* "bring interfaces to Python", and it has a lot of functionality already. You haven't explained why zope.interfaces is "hacky", nor why it would be so beneficial to have interface support in the stdlib.
"Could" isn't enough to support "should". Real-world examples would help. Eg, point to a stdlib module (which by definition only uses core Python functionality) that could be easier to understand and/or maintain with built-in 'interface' support. (Feel free to assume functionality that hasn't been implemented in 'strict-interface' yet, and wave your hands a lot.) The thing is, projects in one or a few files with few non-stdlib imports just don't need interface support in my experience. They're small enough to keep in my head, and the overhead of defining interfaces and implementing them separately is too great to be worthwhile. Type annotations are helpful in ensuring proper use of classes and functions. In larger projects, the overhead of importing zope.interface is proportionately tiny, and well-worth the effort to create and maintain interfaces that are heavier-weight than function signatures.

On Sun, May 05, 2019 at 04:23:58AM +0300, Serge Matveenko wrote:
Having read this thread, I think the proposal is complex enough that you will need to write a PEP explaining the nature of interfaces, the problem with the status quo, and your suggested solution. I don't think a proof-of-concept class is sufficient. https://www.python.org/dev/peps/ -- Steven

On Wed, May 8, 2019 at 7:30 PM Serge Matveenko <s@matveenko.ru> wrote:
When you come to write it up, I hope you can go into some detail about what you mean by "a third main object". Currently, the hierarchy is reentrant at the apex ("object" is an instance of "type", and "type" is a subclass of "object"); where would "interface" fit in? Would it be a type? Would it be an object? Will both type and interface be subclasses of a BaseType, same as we have with exceptions? ChrisA
participants (6)
-
Chris Angelico
-
Ivan Levkivskyi
-
Ryan Gonzalez
-
Serge Matveenko
-
Stephen J. Turnbull
-
Steven D'Aprano