15

I want to return a pre-determined list from my function, based on the string input.

def get_ext(file_type):
    text = ['txt', 'doc']
    audio = ['mp3', 'wav']
    video = ['mp4', 'mkv']
    return # what do I return here?

get_ext('audio')  #should return the list ['mp3', 'wav']

What is the easiest way to do it?


For the related problem of trying to use strings to assign or create variables, see How do I create variable variables?. This question is about looking them up.

For lookup on an existing object (rather than in the current local variables), see How to access object attribute given string corresponding to name of that attribute.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
Ludoloco
  • 177
  • 1
  • 5
  • @timgeb See also a very similar one I could've used to close but didn't - https://stackoverflow.com/questions/9437726/how-to-get-the-value-of-a-variable-given-its-name-in-a-string – cs95 Nov 27 '17 at 07:05
  • Which is not the same as the previous one but was also closed the same way. If an answer in the target addresses this question, I think it's O.K. – cs95 Nov 27 '17 at 07:06
  • @cᴏʟᴅsᴘᴇᴇᴅ that would be a better dupe target I suppose, which has been duped again to the target you initially proposed... can't argue with that. (*Although I still think there's a difference between creating a dynamic number of variables and variable lookup by string*) – timgeb Nov 27 '17 at 07:06
  • @timgeb Yup, that was why I closed it as a dupe of the parent initially :) But now that I've closed the question once, I cannot close it again. Help? – cs95 Nov 27 '17 at 07:07
  • 1
    @cᴏʟᴅsᴘᴇᴇᴅ I'm hesistant to use https://stackoverflow.com/questions/9437726/how-to-get-the-value-of-a-variable-given-its-name-in-a-string because do we really want to teach people to use `globals`? I would rather let the question stay open, but that could totally be my bias since I made the accepted answer. – timgeb Nov 27 '17 at 07:11
  • @timgeb Yes, that's the other reason I closed as a dupe of the parent... since I stood by that solution more than the `globals` one. – cs95 Nov 27 '17 at 07:12
  • 2
    @timgeb: that duplicate was correct, I've re-closed it. I've added another post in the mix however. No, in this case `globals()` would not be helpful, but the advice to build a dictionary to hold a namespace *is*. – Martijn Pieters Nov 27 '17 at 08:30
  • The other duplicate was *clearly* a duplicate of the canonical; the goal was to use existing strings to create variable names procedurally. The goal here is to use a *single* string to *select an already existing* variable. The approach is wrong in the same way in both cases, and the same underlying advice applies, but this is clearly a different question and I've reopened it. https://stackoverflow.com/questions/9437726 is frighteningly bad for something so popular, but it's much more like this question than the variable-variables canonical. If anything, *it* is a duplicate of *this*. – Karl Knechtel Jul 04 '22 at 22:32
  • Note for future closers: https://stackoverflow.com/questions/1373164/ is still the appropriate target in cases where OP *has already*, e.g., created multiple related variable names algorithmically rather than using a list and then wants to access them. However, sometimes the generation is done by e.g. a GUI form designer and is out of OP's control. In these cases, try to find something more specific. – Karl Knechtel Jul 04 '22 at 22:56
  • Possibly related: https://stackoverflow.com/questions/60208, https://stackoverflow.com/questions/3061, https://stackoverflow.com/questions/2612610 – Karl Knechtel Jul 05 '22 at 00:06

5 Answers5

32

In most cases like this, an ordinary dictionary will do the job just fine.

>>> get_ext = {'text': ['txt', 'doc'],
...            'audio': ['mp3', 'wav'],
...            'video': ['mp4', 'mkv']
... }
>>> 
>>> get_ext['video']
['mp4', 'mkv']

If you really want or need a function (for which there can be valid reasons) you have a couple of options. One of the easiest is to assign to the get method of the dictionary. You can even re-assign the name get_ext if you don't have use for the dictionary behind the curtain.

>>> get_ext = get_ext.get
>>> get_ext('video')
['mp4', 'mkv']

This function will return None per default if you enter an unknown key:

>>> x = get_ext('binary')
>>> x is None
True

If you want a KeyError instead for unknown keys, assign to get_ext.__getitem__ instead of get_ext.get.

If you want a custom default-value one approach is to wrap the dictionary inside a function. This example uses an empty list as the default value.

def get_ext(file_type):
    types = {'text': ['txt', 'doc'],
             'audio': ['mp3', 'wav'],
             'video': ['mp4', 'mkv']
    }

    return types.get(file_type, [])

However, @omri_saadon gave the valid remark that the types = ... assignment is performed every function call. Here's what you can do to get around that if this bothers you.

class get_ext(object):
    def __init__(self):
        self.types = {'text': ['txt', 'doc'],
                      'audio': ['mp3', 'wav'],
                      'video': ['mp4', 'mkv']
        }

    def __call__(self, file_type):
        return self.types.get(file_type, [])

get_ext = get_ext()

You can use get_ext like a regular function from here on, because in the end callables are callables. :)

Note that this approach - besides the fact that self.types is only created once - has the considerable advantage that you can still easily change the file types your function recognizes.

>>> get_ext.types['binary'] = ['bin', 'exe']
>>> get_ext('binary')
['bin', 'exe']
timgeb
  • 76,762
  • 20
  • 123
  • 145
  • 4
    I'd suggest you go even further and *just* use a dictionary, Ludoloco. Why waste time and typing with a function at all? A dictionary provides the function you are seeking. (as this answer eloquently demonstrates) – The Nate Nov 27 '17 at 00:01
  • 2
    @TheNate You could make this an answer (I'm surprised none of the answers make this point). wrapping a dictionary in a function seems like an awful lot of work just to use round brackets rather than square brackets. – John Coleman Nov 27 '17 at 02:12
  • @JohnColeman I would say this depends on how the function/dict is used further down the road. E.g., if you intend to do a lot of `map`ping or general passing around of the callable and are not interested in membership tests (i.e. `key in mydict`) then the function feels a bit more natural to me. But other than that, I agree and would personally probably just use a dict. It becomes more interesting when the value retrieval is supposed to be coupled with more computation or side effects. Then you have the choice to subclass `dict` (better: `collections.UserDict`) or write your own callable. – timgeb Nov 27 '17 at 05:57
  • Another case where not using 'just' a dict is a bit more elegant is when you want the same default return value != `None` for every lookup. (An empty list, as suggested in this answer, for example.) – timgeb Nov 27 '17 at 06:04
  • @timgeb I don't really disagree, but OP comes across as a new Python programmer who isn't familiar with dictionaries. This might be an XY problem where the solution to their actual problem is just a basic dictionary used in a straightforward way. – John Coleman Nov 27 '17 at 12:33
  • @JohnColeman I agree. I have restructured my answer. – timgeb Nov 30 '17 at 15:19
11

If you don't want to define a dictionary as in @timgeb's answer, then you can call locals(), which gives you a dict of the variables available in the local scope.

def get_ext(file_type):
    text = ['txt', 'doc']
    audio = ['mp3', 'wav']
    video = ['mp4', 'mkv']
    return locals()[file_type]

and a test to show it works:

>>> get_ext("text")
['txt', 'doc']
Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
Joe Iddon
  • 20,101
  • 7
  • 33
  • 54
  • 15
    Nah, this works but it's a terrible approach to take. It takes advantage of the fact that `locals()` returns a dictionary-- use a real dictionary, that's what they're for! – alexis Nov 26 '17 at 20:08
  • 2
    @alexis I don't want to say that your comment is not valid, but I can't get behind the reasoning "It takes advantage of the fact that locals() returns a dictionary". When do we ever not "take advantage" of knowing what the return value of a call can do? – timgeb Nov 30 '17 at 15:30
  • 1
    My point is that this solution relies on the same data type that it pretends to avoid. It is a hack that obscures the distinction between variable names and data... and to what benefit? It doesn't even avoid the data type that _should_ have been used, in a perfectly straightforward way, from the start. – alexis Nov 30 '17 at 17:53
  • 1
    PS. It's good/interesting to know about `locals()`; it's just a terrible way to approach this task. – alexis Nov 30 '17 at 17:59
  • @alexis I agree that `explicit is better than implicit`, but the `dict` created by `locals()` is as "real" as any other. Just keep in mind that [modifying it may not work like you expect](https://stackoverflow.com/questions/4997184/why-is-it-bad-idea-to-modify-locals-in-python). – Karl Knechtel Jul 04 '22 at 23:17
  • For completeness: `globals()` does the same thing for the global scope. – Karl Knechtel Aug 09 '22 at 05:26
  • I work with a COTS product that allows us to script in Python. Our script will be passed, in a predictably named variable, the name of a variable that holds a value we may want to work with. @timgeb's "accepted" answer doesn't work at all in this case, as I have no control over how the COTS product passes data to my script. On the other hand, this `locals()[var_with_varname]` approach from @KarlKnechtel works great! – Preacher Jun 23 '23 at 14:43
4

You can easily use dict with tuple/list values like so:

def get_ext(file_type):
    d = {'text': ['txt', 'doc'],
         'audio': ['mp3', 'wav'],
         'video': ['mp4', 'mkv']}
    return d[file_type]


print(get_ext('audio'))
timgeb
  • 76,762
  • 20
  • 123
  • 145
Jump3r
  • 1,028
  • 13
  • 29
1

Use dictionary:

def get_ext(file_type):
    d = {'text' : ['txt', 'doc'],
         'audio' : ['mp3', 'wav'],
         'video' : ['mp4', 'mkv']}
    try:
        return d[file_type]
    except KeyError:
        return []

get_ext('audio') # ['mp3', 'wav']

returns empty list in case that key does not exists. how ever this is simplest answer that came in my mind , for better answer see @timgeb answer.

ᴀʀᴍᴀɴ
  • 4,443
  • 8
  • 37
  • 57
0

As per the answer by @timgeb I'd use a dictionary, but if you're accessing a lot, care about speed and don't want to define a class you can use caching.

from functools import lru_cache

def get_ext(file_type):
    d = {'text': ['txt', 'doc'],
         'audio': ['mp3', 'wav'],
         'video': ['mp4', 'mkv']}
    return d[file_type]

@lru_cache(maxsize=3, typed=False)
def get_ext_cached(file_type):
    d = {'text': ['txt', 'doc'],
         'audio': ['mp3', 'wav'],
         'video': ['mp4', 'mkv']}
    return d[file_type]

from timeit import timeit

# non cached
print(timeit(stmt='get_ext("text")', globals={'get_ext': get_ext}))
# 0.48447531609922706 on my machine

# cached
print(timeit(stmt='get_ext("text")', globals={'get_ext': get_ext_cached}))
# 0.11434909792297276

Though for this particular case it's likely overkill and you can just call get on the dictionary directly (cache just builds its own dictionary and does exactly that) you can use this in future for any pure functions which are effectively a computed lookup.

d = {'text': ['txt', 'doc'],
    'audio': ['mp3', 'wav'],
    'video': ['mp4', 'mkv']}

# 0.05016115184298542
print(timeit(stmt="d['text']",
             globals={'d':d,'c':c}))
Turksarama
  • 199
  • 2
  • 3
    That is a lot of typing compared to replacing the function with a dictionary that is accessed directly by the caller. – Arthur Tacca Nov 27 '17 at 08:41