2
4

概要

背景として、後述のDjangoのModel定義でリスコフの置換原則満たさない書き方をみんなしているのではないかという疑問が出てきたので、軽くまとめました。

関数の場合

以下の継承は、reverse の型が変わっているので、リスコフの置換原則を満たしません。

class Parent:
    def reverse(self, x: int) -> int:
        return -x
class Child(Parent):
    def reverse(self, x: str) -> str:
        return x[::-1]

同じく、一見大丈夫そうに見えますが、@propartyを用いた場合もリスコフの置換原則を満たしません。
Parentではnameメソッドですが、Childではnameフィールドになっています。

class Parent:
    @property
    def name(self) -> str:
        return "parent"

class Child(Parent):
    name = "child"

以下のように修正しなければなりません。

class Parent:
    @property
    def name(self) -> str:
        return "parent"

class Child(Parent):
    @property
    def name(self) -> str:
        return "child"

インナークラスの場合

以下はインナークラスを使用したリスコフの置換原則を満たさない例です。

class Car:
    class Engine:
        gas = True


class EVCar(Car):
    class Engine:
        battery = True


car = Car()
engine = car.Engine()
print(engine.gas)  # True

ev = EVCar()
engine = ev.Engine()
print(engine.gas)  # AttributeError
print(engine.battery)  # True

上記は継承しているのにも関わらず、Engineをオーバーライドしてしまっています。EVCarのgas属性がないので、EVCar is Carの関係が成り立ちません。これはリスコフの置換原則を満たしません。

以下のようにすると、EVCarでCarのEngineも継承できております。

class Car:
    class Engine:
        gas = True


class EVCar(Car):
    class Engine(Car.Engine):
        battery = True


car = Car()
engine = car.Engine()
print(engine.gas)  # True

ev = EVCar()
engine = ev.Engine()
print(engine.gas)  # True
print(engine.battery)  # True

しかし、上記だと、EVCar の engineの gas がTrueになってしまいます。そこで、EVCar のEngineで明示的に gas を False にします。

class Car:
    class Engine:
        gas = True


class EVCar(Car):
    class Engine(Car.Engine):
        battery = True
        gas = False


car = Car()
engine = car.Engine()
print(engine.gas)  # True

ev = EVCar()
engine = ev.Engine()
print(engine.gas)  # False
print(engine.battery)  # True

DjangoのAbstractBaseModel

Djangoでは抽象モデルとして、共通フィールドを AbstractBaseClassで定義することがあります。
例えば以下のようにcreate_atを共通化します。AbstractModelのメタデータにabstract = Trueがあるので、AbstractModelはデータベースにマッピングされません。

from django.db import models

class AbstractModel(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        abstract = True


class Foo(ModelTemplate):

    class Meta:
        ordering = ["id"]

よく見かけるこの書き方、継承しているのにも関わらず、オーバライドしています。
そこで以下のように修正します。

from django.db import models

class AbstractModel(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        abstract = True


class Foo(ModelTemplate):
    class Meta(ModelTemplate.Meta):
        ordering = ["id"]

実行してみます。

from public_service.models.a import ModelTemplate,Foo
a = Foo()
a.Meta().abstract # False

abstract=Trueが継承されているので、a.Meta().abstractTrue になると思ったのですが、 False になりました。models.Model の実装を見ると、_metaでabstractがなければ自動的に falseが設定されるようになっています。(実装の説明は割愛)

最後に

Pylanceでは reportAssignmentType として、継承時にオーバーライドしてしまっているものを検知することができます。

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4