Repository: omni-us/jsonargparse

Issue:
Cannot override Callable init_args without passing the class_path
## 🐛 Bug report

Unlike the class argument, I cannot override `Callable` argument without passing the class_path even it is given previously.

### To reproduce

```python
from jsonargparse import CLI
from typing import Callable

def call():
    print("Hi")

class Call:
    def __init__(self, name: str):
        self.name = name

    def __call__(self):
        print(f"Hi, {self.name}")

def fn(
    callable: Callable = call,
):
    callable()

if __name__ == "__main__":
    CLI(fn)
```

```yaml
callable:
  class_path: __main__.Call
  init_args:
    name: Alice
```

```console
python test.py --config test.yaml
Hi, Alice
python test.py --config test.yaml --callable.name Bob
usage: test.py [-h] [--config CONFIG] [--print_config [={comments,skip_null,skip_default}+]] [--callable CALLABLE]
test.py: error: Unrecognized arguments: --callable.name Bob
```

### Expected behavior

```console
python test.py --config test.yaml
Hi, Alice
python test.py --config test.yaml --callable.name Bob
Hi, Bob
```

### Environment

- jsonargparse version: 4.15.1
- Python version: 3.9
- How jsonargparse was installed: `pip install jsonargparse`
- OS (e.g., Linux): macOS & Linux


Comments:

Patch:
diff --git a/jsonargparse/typehints.py b/jsonargparse/typehints.py
index b163825..e4f1d2d 100644
--- a/jsonargparse/typehints.py
+++ b/jsonargparse/typehints.py
@@ -235,8 +235,12 @@ class ActionTypeHint(Action):
 
 
     @staticmethod
-    def is_callable_typehint(typehint):
+    def is_callable_typehint(typehint, all_subtypes=True):
         typehint_origin = get_typehint_origin(typehint)
+        if typehint_origin == Union:
+            subtypes = [a for a in typehint.__args__ if a != NoneType]
+            test = all if all_subtypes else any
+            return test(ActionTypeHint.is_callable_typehint(s) for s in subtypes)
         return typehint_origin in callable_origin_types or typehint in callable_origin_types
 
 
@@ -267,7 +271,7 @@ class ActionTypeHint(Action):
         typehint = typehint_from_action(action)
         if typehint and (
             ActionTypeHint.is_subclass_typehint(typehint, all_subtypes=False) or
-            ActionTypeHint.is_callable_typehint(typehint) or
+            ActionTypeHint.is_callable_typehint(typehint, all_subtypes=False) or
             ActionTypeHint.is_mapping_typehint(typehint)
         ):
             return action, arg_base, explicit_arg


Test patch:
diff --git a/jsonargparse_tests/test_typehints.py b/jsonargparse_tests/test_typehints.py
index af135f0..7d31c88 100755
--- a/jsonargparse_tests/test_typehints.py
+++ b/jsonargparse_tests/test_typehints.py
@@ -497,6 +497,24 @@ class TypeHintsTests(unittest.TestCase):
             self.assertEqual(init.call(), 'Bob')
 
 
+    def test_union_callable_with_class_path_short_init_args(self):
+        class MyCallable:
+            def __init__(self, name: str):
+                self.name = name
+            def __call__(self):
+                return self.name
+
+        parser = ArgumentParser()
+        parser.add_argument('--call', type=Union[Callable, None])
+
+        with mock_module(MyCallable) as module:
+            cfg = parser.parse_args([f'--call={module}.MyCallable', '--call.name=Bob'])
+            self.assertEqual(cfg.call.class_path, f'{module}.MyCallable')
+            self.assertEqual(cfg.call.init_args, Namespace(name='Bob'))
+            init = parser.instantiate_classes(cfg)
+            self.assertEqual(init.call(), 'Bob')
+
+
     def test_typed_Callable_with_function_path(self):
         def my_func_1(p: int) -> str:
             return str(p)