19

I have a dictionary that has values sometimes as strings, and sometimes as a functions. For the values that are functions is there a way to execute the function without explicitly typing () when the key is accessed?

Example:

d = {1: "A", 2: "B", 3: fn_1}
d[3]() # To run function

I want:

d = {1: "A", 2: "B", 3: magic(fn_1)}
d[3] # To run function
MSeifert
  • 145,886
  • 38
  • 333
  • 352
tyleax
  • 1,556
  • 2
  • 17
  • 45
  • 9
    Why without `()`? Don't knit a sweater around a button... – Alexander Sep 08 '17 at 00:20
  • 5
    Don't do this. `Explicit is better than implicit`. The meaning in your code is to look up a function and call it; so the code *should look like* you are doing exactly that. As an aside, mixing types like that is a bad idea; you need to check whether you have a string first (since you can't call the string), and that makes your life harder. – Karl Knechtel Sep 08 '17 at 06:24
  • 2
    ... But on reflection, it sounds like you really have a *design* question, and should be asking a question that more closely reflects what you really want to do. I.e., why do you have this dictionary in the first place? – Karl Knechtel Sep 08 '17 at 06:25
  • 1
    It sounds like instead of writing `d[3]`, you should be calling some function `some_func(3)`, and the function should index the dict and call the value if the value is a callable. – user2357112 Sep 11 '17 at 23:30

5 Answers5

16

Another possible solution, is to create a custom dictionary object that implements this behavior:

>>> class CallableDict(dict):
...     def __getitem__(self, key):
...         val = super().__getitem__(key)
...         if callable(val):
...             return val()
...         return val
...
>>>
>>> d = CallableDict({1: "A", 2: "B", 3: lambda: print('run')})
>>> d[1]
'A'
>>> d[3]
run

A perhaps more idiomatic solution would be to use try/except:

def __getitem__(self, key):
    val = super().__getitem__(key)
    try:
        return val()
    except TypeError:
        return val

Note however the method above is really for completness. I would not reccomend using it. As pointed out in the comments, it would mask TypeError's raised by the function. You could test the exact content of TypeError, but at that point, you'd be better of using the LBYL style.

Christian Dean
  • 22,138
  • 7
  • 54
  • 87
  • EAFP - `try: return val(); except TypeError: return val`? – AChampion Sep 08 '17 at 00:42
  • @AChampion Eh, I could use EAFP instead. In some cases, I really don't think it matters to much :) I really wasn't thinking about it when I posted my answer. – Christian Dean Sep 08 '17 at 00:45
  • 1
    EAFP hides TypeErrors that occur inside the called function in this case. I wouldn't use it. – Pekka Klärck Sep 10 '17 at 09:04
  • @PekkaKlärck Yeah, your right. I see that clearly now. I guess I was distracted when adding it to my answer, and wasn't thinking about the potential consequences of using it. I'll edit. – Christian Dean Sep 10 '17 at 23:32
  • In this example the return value of `d[3]` would be `None`, right? The printing of `"run"` is a side effect. – Håken Lid Sep 11 '17 at 10:15
  • @HåkenLid Sure. But the `lambda` function I used was just for exemplary purposes. – Christian Dean Sep 11 '17 at 13:52
  • Essentially this just moves the call to the function "with parentheses" inside the callable: how is this better than just calling the function itself? – gented Jan 01 '19 at 04:16
  • Primarily, it could be considered more compact and convenient than having to write parenthesis after each key index to a function. @gented This answer is more for the OP though. Personally, I would probably just stick with calling the function in the dictionary explicitly. – Christian Dean Jan 01 '19 at 21:54
7

I don't think that's (easily) possible with the standard library but you could use lazy_object_proxy.Proxy from the module lazy_object_proxy (it's third party so you need to install it):

>>> import lazy_object_proxy
>>> def fn_1():
...     print('calculation')
...     return 1000
...
>>> d = {1: "A", 2: "B", 3: lazy_object_proxy.Proxy(fn_1)}
>>> print(d[3])
calculation
1000
MSeifert
  • 145,886
  • 38
  • 333
  • 352
1

You can try this:

  1. declare the dictionary with its keys and the name of each

  2. function without the()

    functions = {'1': function1, '2':fuction2, '3':fuction3 ,...}

  3. pass the function/value by using the method get, which returns None

  4. if the key doesn't exists
    action = functions.get(key)

  5. call the function ,which is stored in the var action, + () action()
  6. your function will be executed.
Vega
  • 27,856
  • 27
  • 95
  • 103
OG_
  • 49
  • 6
0

Use callable() to check if variable is, well, callable:

d = {1: "A", 2: "B", 3: fn_1}
if callable(d[3]):
    d[3]()
else:
    d[3]
Alex Undefined
  • 620
  • 1
  • 5
  • 8
-3

Another solution: you can also pass some class method decorated using @property:

class Test:
    @property
    def method(self):
        return 'X'

d = {'a': 1, 'b': Test().method}
print(d)
print(d['a'])
print(d['b'])
msztolcman
  • 397
  • 3
  • 10
  • 2
    This isn't a function that is called when the key is accessed. It just sets the dictionary value when defined: `{'a': 1, 'b': 'X'}` – Mark Tolonen Sep 09 '17 at 22:03