-
Notifications
You must be signed in to change notification settings - Fork 12
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
Change from ABC to protocol. #58
Comments
@BSchilperoort I think this is good idea. I have a branch that does this and will create a pull request for you to have a look at (but probably not until next week). |
While I share a general grumble towards For example, there is nothing stopping someone today from writing a "thin" import typing
class BmiUpdater(typing.Protocol):
def update(self) -> None: ...
def update_bmi(bmi: BmiUpdater) -> None:
bmi.update()
update_bmi(SomeBmiSubclass()) # no type error Sure, there is more typing involved (no pun intended :)) but doesn't this accomplish the same goal? As for breaking changes, one drawback / feature (depends how you look at it) of
import abc
import inspect
class Base(abc.ABC):
@abc.abstractmethod
def foo(self) -> str: ...
@abc.abstractmethod
def bar(self) -> str: ...
class Subclass(Base):
def foo(self):
return "horray"
assert inspect.isabstract(Subclass) # passes
Subclass() # raises TypeError: Can't instantiate abstract class Subclass with abstract method bar
import typing
import inspect
class Base(typing.Protocol):
def foo(self) -> str: ...
def bar(self) -> str: ...
class Subclass(Base):
def foo(self):
return "horray"
assert inspect.isabstract(Subclass) # raises
Subclass() # typing error, but no runtime error This feels like if we could go back and re-write BMI from scratch, perhaps you would use a |
I think you missed one thing, @aaraney. When subclassing Protocol, you can still make use of abstractmethod: import typing
import inspect
from abc import abstractmethod
class Base(typing.Protocol):
@abstractmethod
def foo(self) -> str: ...
@abstractmethod
def bar(self) -> str: ...
class Subclass(Base):
def foo(self):
return "horray"
assert inspect.isabstract(Subclass) # passes
Subclass() # raises TypeError: Can't instantiate abstract class Subclass with abstract method bar I would still recommend most users to subclass the abstract BMI, especially more novice users, but by making it a protocol we can validate a BMI implementation without strictly needing to subclass it (using a type checker).
As far as I can see, the only way this breaks things is that |
Yeah, that's fair. I'd not considered decorating a protocol like this. Nice catch and interesting thought!
Great catch! Additionally import typing
from abc import abstractmethod
# need this
@typing.runtime_checkable
class Base(typing.Protocol):
@abstractmethod
def foo(self) -> str: ...
@abstractmethod
def bar(self) -> str: ...
class Subclass:
def foo(self):
return "hooray"
def bar(self) -> str:
return "bar"
assert issubclass(Subclass, Base) # without `@typing.runtime_checkable` this fails The docs point out there is a performance degradation when using
Aside, if I saw a If the goal is to remove the need for implementations to rely on I don't want to lose the plot, @BSchilperoort! I use BMI in a very constrained way and have my own biases.
|
I did not realize that this would break when using a protocol... That does make me doubt if this would be a good idea now.
It would indeed remove runtime dependence on bmipy, but I guess the workaround for this would be; from typing import TYPE_CHECKING
if TYPE_CHECKING:
from bmipy import Bmi
def func(bmi: "Bmi"):
bmi.initialize("config.toml")
In case BMI was a protocol, it would be easier to implement a BMI "partially", with less boilerplate required (#61). A BMI that exposes a rectilinear dataset for example, does not have to implement any setter functions or node/edge functionality (~10 methods). Protocols can cover part of the whole implementation, and implementations can be checked just by if they comply to the required protocol. |
Python nowadays supports "protocols". Protocols are mostly a typing thing, and allow you to specify that a class follows the protocol without explicitly being a subclass.
When using the BMI as an abstract base class, you currently need to use the following syntax:
If you don't subclass BMI, type checkers will get angry at this, as
BmiImplementation
is not a subclass ofBmiABC
:However, if you specify that
Bmi
is a subclass oftyping.protocol
, the following syntax passes type checkers:This is because type checkers will check if
BmiImplementation
follows theBmiProtocol
: does it have all methods that are in the protocol, and do all the methods have the correct typing.To implement this, all that we would need to change is;
Note that you can still subclass from Protocol like you could from an ABC.
The text was updated successfully, but these errors were encountered: