rspec/rules/S2638/python/rule.adoc
Fred Tingaud 16f6c0aecf
Inline adoc when include has no additional value (#1940)
Inline adoc files when they are included exactly once.

Also fix language tags because this inlining gives us better information
on what language the code is written in.
2023-05-25 14:18:12 +02:00

140 lines
5.4 KiB
Plaintext

== Why is this an issue?
Because a subclass instance may be used as an instance of the superclass, overriding methods should uphold the aspects of the superclass contract that relate to the Liskov Substitution Principle. Specifically, an overriding method should be callable with the same parameters as the overriden one.
The following modifications are ok:
* Adding an optional parameter, i.e. with a default value, as long as they don't change the order of positional parameters.
* Renaming a positional-only parameter.
* Reordering keyword-only parameters.
* Adding a default value to an existing parameter.
* Changing the default value of an existing parameter.
* Extend the ways a parameter can be provided, i.e. change a keyword-only or positional-only parameter to a keyword-or-positional parameter. This is only true if the order of positional parameters doesn't change. New positional parameters should be placed at the end.
* Adding a vararg parameter (``++*args++``).
* Adding a keywords parameter (``++**kwargs++``).
The following modifications are not ok:
* Removing parameters, even when they have default values.
* Adding mandatory parameters, i.e. without a default value.
* Removing the default value of a parameter.
* Reordering parameters, except when they are keyword-only parameters.
* Removing some ways of providing a parameter. If a parameter could be passed as keyword it should still be possible to pass it as keyword, and the same is true for positional parameters.
* Removing a vararg parameter (``++*args++``).
* Removing a keywords parameter (``++**kwargs++``).
This rule raises an issue when the signature of an overriding method does not accept the same parameters as the overriden one. Only instance methods are considered, class methods and static methods are ignored.
=== Noncompliant code example
[source,python]
----
class ParentClass(object):
def mymethod(self, param1):
pass
class ChildClassMore(ParentClass):
def mymethod(self, param1, param2, param3): # Noncompliant * 2.
# Remove parameter "param2" or provide a default value.
# Remove parameter "param3" or provide a default value.
pass
class ChildClassLess(ParentClass):
def mymethod(self): # Noncompliant. Add missing parameter "param1".
pass
class ChildClassReordered(ParentClass):
def mymethod(self, inserted, param1): # Noncompliant
# Remove parameters "inserted" or provide a default value.
pass
----
=== Compliant solution
[source,python]
----
class ParentClass(object):
def mymethod(self, param1):
pass
class ChildClassMore(ParentClass):
def mymethod(self, param1, param2=None, param3=None):
pass
class ChildClassLess(ParentClass):
def mymethod(self, param1=None):
pass
class ChildClassReordered(ParentClass):
def mymethod(self, param1, inserted=None):
pass
----
=== Exceptions
In theory renaming parameters also breaks Liskov Substitution Principle. Arguments can't be passed via keyword arguments anymore. However, https://www.python.org/dev/peps/pep-0570/#consistency-in-subclasses[as PEP-570 says], it is common to rename parameters when it improves code readability and when arguments are always passed by position.
"Positional-Only Parameters" were introduced in python 3.8 to solve this problem. As most programs will need to support older versions of python, this rule won't raise an issue on renamed parameters.
[source,python]
----
class ParentClass(object):
def mymethod(self, param1):
pass
class ChildClassRenamed(ParentClass):
def mymethod(self, renamed): # No issue but this is suspicious. Rename this parameter as "param1" or use positional only arguments if possible.
pass
----
== Resources
* https://en.wikipedia.org/wiki/Liskov_substitution_principle[Wikipedia - Liskov substitution principle]
* Python Enhancement Proposal (PEP) 3102 - https://www.python.org/dev/peps/pep-3102/[Keyword-Only Arguments]
* Python Enhancement Proposal (PEP) 570 - https://www.python.org/dev/peps/pep-0570/[Python Positional-Only Parameters]
ifdef::env-github,rspecator-view[]
'''
== Implementation Specification
(visible only on this page)
=== Message
* When some parameters are missing
** add missing parameters XXX, YYY, ...
* When there are more mandatory positional parameters:
** Remove parameter XXX or provide default value.
* When the default value of a parameter is removed
** Add a default value to parameter XXX.
* When a positional parameter is not at the right place in the list.
** Move parameter XXX to position 1.
* When a parameter was keyword-or-positional and becomes keyword-only or positional-only
** Make parameter XXX keyword-or-positional
* When a parameter was keyword-only and becomes positional-only
** Make parameter XXX keyword-only or keyword-or-positional
* When a parameter was positional-only and becomes keyword-only
** Make parameter XXX positional-only or keyword-or-positional
* When the overriden method had a vararg (*args) but the overriding method doesn't
** Add the missing vararg (*args)
* When the overriden method had a 'keywords' argument (**kwargs) but the overriding method doesn't
** Add the missing "keywords" argument (**kwrags)
=== Highlighting
* Primary:
** the function definition or parameter, depending on the issue
* Secondary
** the overriden method
** *message: "Overriden method's definition"*
'''
== Comments And Links
(visible only on this page)
include::../comments-and-links.adoc[]
endif::env-github,rspecator-view[]