Repository: keras-team/keras

Issue:
Change keras.Model inheritance for improved static analysis support
Hi all,

The `keras.Model` class may inherit from one of many different trainer classes. This is chosen at runtime by a conditional statement, which makes static analysis difficult. 

Knowing Python's Method Resolution Order (MRO) and the fact that all trainer classes have a common base class, I propose that `keras.Model` should inherit from the base trainer class. The change is minimal and would look like this:

```python
class Model(Trainer, Layer, base_trainer.Trainer):
```

This change fixes the syntax highlighting in my IDE, so I would really appreciate it.

Also, I believe it would also make the direct assignment of docstrings unnecessary, as they would be inherited from the base trainer class. The current state of the code is as follows:

```python
Model.fit.__doc__ = base_trainer.Trainer.fit.__doc__
Model.predict.__doc__ = base_trainer.Trainer.predict.__doc__
...
```

Thank you for your time and consideration.


Discussion:

Resolving patch:
diff --git a/keras/models/functional.py b/keras/models/functional.py
--- a/keras/models/functional.py
+++ b/keras/models/functional.py
@@ -1,5 +1,6 @@
 import copy
 import inspect
+import typing
 import warnings
 
 from keras import backend
@@ -94,6 +95,9 @@ class Functional(Function, Model):
             trainable.
     """
 
+    def __new__(cls, *args, **kwargs):
+        return typing.cast(Functional, super().__new__(cls))
+
     @tracking.no_automatic_dependency_tracking
     def __init__(self, inputs, outputs, name=None, **kwargs):
         if isinstance(inputs, dict):
diff --git a/keras/models/model.py b/keras/models/model.py
--- a/keras/models/model.py
+++ b/keras/models/model.py
@@ -1,5 +1,6 @@
 import inspect
 import json
+import typing
 import warnings
 
 from keras import backend
@@ -27,7 +28,7 @@
 
 
 @keras_export(["keras.Model", "keras.models.Model"])
-class Model(Trainer, Layer):
+class Model(Trainer, base_trainer.Trainer, Layer):
     """A model grouping layers into an object with training/inference features.
 
     There are three ways to instantiate a `Model`:
@@ -138,7 +139,7 @@ def __new__(cls, *args, **kwargs):
             from keras.models import functional
 
             return functional.Functional(*args, **kwargs)
-        return super().__new__(cls)
+        return typing.cast(Model, super().__new__(cls))
 
     def __init__(self, *args, **kwargs):
         Trainer.__init__(self)
@@ -599,11 +600,3 @@ def inject_functional_model_class(cls):
     cls.__new__(cls)
 
     return cls
-
-
-Model.fit.__doc__ = base_trainer.Trainer.fit.__doc__
-Model.predict.__doc__ = base_trainer.Trainer.predict.__doc__
-Model.evaluate.__doc__ = base_trainer.Trainer.evaluate.__doc__
-Model.train_on_batch.__doc__ = base_trainer.Trainer.train_on_batch.__doc__
-Model.test_on_batch.__doc__ = base_trainer.Trainer.test_on_batch.__doc__
-Model.predict_on_batch.__doc__ = base_trainer.Trainer.predict_on_batch.__doc__
diff --git a/keras/models/sequential.py b/keras/models/sequential.py
--- a/keras/models/sequential.py
+++ b/keras/models/sequential.py
@@ -1,5 +1,6 @@
 import copy
 import inspect
+import typing
 
 from keras.api_export import keras_export
 from keras.backend.common import global_state
@@ -60,6 +61,9 @@ class Sequential(Model):
     ```
     """
 
+    def __new__(cls, *args, **kwargs):
+        return typing.cast(Sequential, super().__new__(cls))
+
     def __init__(self, layers=None, trainable=True, name=None):
         super().__init__(trainable=trainable, name=name)
         self._functional = None


Test patch: