-
-
Notifications
You must be signed in to change notification settings - Fork 6
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
Inverted attribute order #39
Comments
Sure! This relates to the Liskov substitution principle Take the following example: class A:
def method(self, message: str, count: int) -> None:
print("Message:" + message, count + 1)
class B(A):
def method(self, count: int, message: str) -> None:
print("Subclass message:" + message, count + 1)
def do_something(a: A) -> None:
if isinstance(a, A):
a.method("High", 5)
# Passes type-checking and isinstance check
do_something(A())
# Passes type-checking and isinstance check, but raises TypeError
do_something(B()) It's even worst if the parameter types are all compatible, because the error might spread further in. In tempora's case, one might unknowingly flip class DelayedCommand:
@classmethod
def at_time(cls, at, target): ...
class PeriodicCommandFixedDelay(DelayedCommand):
@classmethod
def at_time(cls, at, delay, target): ...
def do_something(command: DelayedCommand) -> None:
if isinstance(command, DelayedCommand):
command.at_time(now(), lambda x:x)
# Passes type-checking and isinstance check
do_something(DelayedCommand())
# Passes type-checking and isinstance check, but raises:
# TypeError: at_time() missing 1 required positional argument: 'target'
do_something(PeriodicCommandFixedDelay()) To further show how unexpected this error can be: Which can be solved either by adding the unused parameter to the subclass; or making the additional parameter optional; or saying screw the LSP, users of this code should explicitly make sure they're not getting this subclass (maybe its usage is rare and specialized). Even if you decide to violate the LSP by design, keeping the parameter order is nicer and more consistent when used: at = now()
target = lambda x:x
delay = 5
# This
def do_something(command: DelayedCommand) -> None:
if isinstance(command, PeriodicCommandFixedDelay):
command.at_time(at, delay, target)
else:
command.at_time(at, target)
# instead of
def do_something(command: DelayedCommand) -> None:
if isinstance(command, PeriodicCommandFixedDelay):
command.at_time(at, target, delay)
else:
command.at_time(at, target) |
Thanks for the thorough explanation. Looking at these functions, I mean for them to be alternate constructors. I don't intend for them to be used as instance methods or for Liskov substitution to apply. I merely wish for The reason I put Perhaps there's a better way, perhaps to have a
i.e.
That still doesn't satisfy LSP, however, as In short, because these are constructors and not meant to be used as instance methods, LSP doesn't apply. |
In that case, a suppression comment linking back here should be fine :) |
In #36, we learned that there's an inverted attribute order in:
tempora/tempora/schedule.py
Line 121 in 6579e3c
@Avasam can you elaborate on the concern?
The text was updated successfully, but these errors were encountered: