Repository: aiogram/aiogram (python)

Issue:
Scenes are effectively not inheritable
### Checklist

- [X] I am sure the error is coming from aiogram code
- [X] I have searched in the issue tracker for similar bug reports, including closed ones

### Operating system

Windows 10, Ubuntu 22.04

### Python version

3.12

### aiogram version

3.10.0

### Expected behavior

`@on.message(F.text)` in the child class overrides `@on.message(F.text)` from the parent class, rather than being added after it, thus becoming uncallable.

### Current behavior

Due to the way `ObserverDecorator._wrap_filter` is currently implemented (handlers with same filters from child class appended after handlers from parent class), scenes cannot be correctly inherited from each other to extend their functionality.

### Steps to reproduce

1. Create a scene with handler registered as `@on.message(F.text)`
2. Create a subclass with handler also registered as `@on.message(F.text)`.

### Code example

```python3
class ParentScene(Scene):
    @on.message(F.text)
    async def handle_text(self, message: Message) -> None:
        await message.answer('Always called')
        
        
class ChildScene(ParentScene):
    @on.message(F.text)
    async def handle_text(self, message: Message) -> None:
        await message.answer('Never called')
```


### Logs

_No response_

### Additional information

_No response_


Patch:
diff --git a/CHANGES/1583.feature.rst b/CHANGES/1583.feature.rst
new file mode 100644
index 0000000000..e060762e4e
--- /dev/null
+++ b/CHANGES/1583.feature.rst
@@ -0,0 +1,5 @@
+Enhanced the inheritance of handlers and actions in :ref:`Scenes <Scenes>`.
+Refactored to eliminate the copying of previously connected handlers and actions from parent scenes.
+Now, handlers are dynamically rebuilt based on the current class, properly utilizing class inheritance and enabling handler overrides.
+
+That's mean that you can now override handlers and actions in the child scene, instead of copying and duplicating them.
diff --git a/aiogram/fsm/scene.py b/aiogram/fsm/scene.py
index fe4de5c434..991b34720b 100644
--- a/aiogram/fsm/scene.py
+++ b/aiogram/fsm/scene.py
@@ -316,10 +316,6 @@ def __init_subclass__(cls, **kwargs: Any) -> None:
             if not parent_scene_config:
                 continue
 
-            handlers.extend(parent_scene_config.handlers)
-            for action, action_handlers in parent_scene_config.actions.items():
-                actions[action].update(action_handlers)
-
             if reset_data_on_enter is None:
                 reset_data_on_enter = parent_scene_config.reset_data_on_enter
             if reset_history_on_enter is None:
@@ -327,9 +323,7 @@ def __init_subclass__(cls, **kwargs: Any) -> None:
             if callback_query_without_state is None:
                 callback_query_without_state = parent_scene_config.callback_query_without_state
 
-        for name in vars(cls):
-            value = getattr(cls, name)
-
+        for name, value in inspect.getmembers(cls):
             if scene_handlers := getattr(value, "__aiogram_handler__", None):
                 handlers.extend(scene_handlers)
             if isinstance(value, ObserverDecorator):


Test patch:
diff --git a/tests/test_fsm/test_scene.py b/tests/test_fsm/test_scene.py
index 2bb2cf0a75..9c388a117f 100644
--- a/tests/test_fsm/test_scene.py
+++ b/tests/test_fsm/test_scene.py
@@ -1,5 +1,4 @@
 import inspect
-import platform
 from datetime import datetime
 from unittest.mock import ANY, AsyncMock, patch
 
@@ -7,8 +6,9 @@
 
 from aiogram import Dispatcher, F, Router
 from aiogram.dispatcher.event.bases import NextMiddlewareType
+from aiogram.enums import UpdateType
 from aiogram.exceptions import SceneException
-from aiogram.filters import StateFilter
+from aiogram.filters import Command, StateFilter
 from aiogram.fsm.context import FSMContext
 from aiogram.fsm.scene import (
     ActionContainer,
@@ -1545,3 +1545,120 @@ async def test_scene_registry_middleware(self, bot: MockedBot):
         assert isinstance(data["scenes"], ScenesManager)
         handler.assert_called_once_with(event, data)
         assert result == handler.return_value
+
+
+class TestSceneInheritance:
+    def test_inherit_handlers(self):
+        class ParentScene(Scene):
+            @on.message(Command("exit"))
+            async def command_exit(self, message: Message) -> None:
+                pass
+
+        class ChildScene(ParentScene):
+            pass
+
+        assert len(ParentScene.__scene_config__.handlers) == 1
+        assert len(ChildScene.__scene_config__.handlers) == 1
+
+        parent_command_handler = ParentScene.__scene_config__.handlers[0]
+        child_command_handler = ChildScene.__scene_config__.handlers[0]
+
+        assert parent_command_handler.handler is ParentScene.command_exit
+        assert child_command_handler.handler is ParentScene.command_exit
+
+    def test_override_handlers(self):
+        class ParentScene(Scene):
+            @on.message(Command("exit"))
+            async def command_exit(self, message: Message) -> int:
+                return 1
+
+        class ChildScene(ParentScene):
+            @on.message(Command("exit"))
+            async def command_exit(self, message: Message) -> int:
+                return 2
+
+        assert len(ParentScene.__scene_config__.handlers) == 1
+        assert len(ChildScene.__scene_config__.handlers) == 1
+
+        parent_command_handler = ParentScene.__scene_config__.handlers[0]
+        child_command_handler = ChildScene.__scene_config__.handlers[0]
+
+        assert parent_command_handler.handler is ParentScene.command_exit
+        assert child_command_handler.handler is not ParentScene.command_exit
+        assert child_command_handler.handler is ChildScene.command_exit
+
+    def test_inherit_actions(self):
+        class ParentScene(Scene):
+            @on.message.enter()
+            async def on_enter(self, message: Message) -> None:
+                pass
+
+        class ChildScene(ParentScene):
+            pass
+
+        parent_enter_action = ParentScene.__scene_config__.actions[SceneAction.enter][
+            UpdateType.MESSAGE
+        ]
+        child_enter_action = ChildScene.__scene_config__.actions[SceneAction.enter][
+            UpdateType.MESSAGE
+        ]
+
+        assert parent_enter_action.callback is ParentScene.on_enter
+        assert child_enter_action.callback is ParentScene.on_enter
+        assert child_enter_action.callback is ChildScene.on_enter
+
+    def test_override_actions(self):
+        class ParentScene(Scene):
+            @on.message.enter()
+            async def on_enter(self, message: Message) -> int:
+                return 1
+
+        class ChildScene(ParentScene):
+            @on.message.enter()
+            async def on_enter(self, message: Message) -> int:
+                return 2
+
+        parent_enter_action = ParentScene.__scene_config__.actions[SceneAction.enter][
+            UpdateType.MESSAGE
+        ]
+        child_enter_action = ChildScene.__scene_config__.actions[SceneAction.enter][
+            UpdateType.MESSAGE
+        ]
+
+        assert parent_enter_action.callback is ParentScene.on_enter
+        assert child_enter_action.callback is not ParentScene.on_enter
+        assert child_enter_action.callback is ChildScene.on_enter
+
+    def test_override_non_function_handler_by_function(self):
+        class ParentScene(Scene):
+            do_exit = on.message(Command("exit"), after=After.exit)
+
+        class ChildScene1(ParentScene):
+            pass
+
+        class ChildScene2(ParentScene):
+            do_exit = on.message(Command("exit"), after=After.back)
+
+        class ChildScene3(ParentScene):
+            @on.message(Command("exit"), after=After.back)
+            async def do_exit(self, message: Message) -> None:
+                pass
+
+        assert len(ParentScene.__scene_config__.handlers) == 1
+        assert len(ChildScene1.__scene_config__.handlers) == 1
+        assert len(ChildScene2.__scene_config__.handlers) == 1
+        assert len(ChildScene3.__scene_config__.handlers) == 1
+
+        parent_handler = ParentScene.__scene_config__.handlers[0]
+        child_1_handler = ChildScene1.__scene_config__.handlers[0]
+        child_2_handler = ChildScene2.__scene_config__.handlers[0]
+        child_3_handler = ChildScene3.__scene_config__.handlers[0]
+
+        assert child_1_handler.handler is parent_handler.handler
+        assert child_1_handler.after == parent_handler.after
+        assert child_1_handler.handler is _empty_handler
+
+        assert child_2_handler.after != parent_handler.after
+        assert child_2_handler.handler is _empty_handler
+
+        assert child_3_handler.handler is not _empty_handler