17

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.

10
  • @timgeb See also a very similar one I could've used to close but didn't - stackoverflow.com/questions/9437726/… Commented Nov 27, 2017 at 7: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. Commented Nov 27, 2017 at 7: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) Commented Nov 27, 2017 at 7:06
  • 1
    @cᴏʟᴅsᴘᴇᴇᴅ I'm hesistant to use stackoverflow.com/questions/9437726/… 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. Commented Nov 27, 2017 at 7:11
  • 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. Commented Nov 27, 2017 at 8:30

5 Answers 5

35

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']
Sign up to request clarification or add additional context in comments.

6 Comments

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)
@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.
@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 mapping 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.
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 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.
|
12

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']

8 Comments

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 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?
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.
PS. It's good/interesting to know about locals(); it's just a terrible way to approach this task.
For completeness: globals() does the same thing for the global scope.
|
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'))

Comments

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.

8 Comments

I guess someone grumpy downvoted because of your wrongly indented return. Just fix that. +1 because this is the right idea.
Actually, I don't like this approach as each call to get_ext an new dictionary is created which is redundant. It's better to seperate the creation of the dict and the getter.
wats cache man?
@omri_saadon well, are you sure?
@omri_saadon your comment is valid. I edited my answer.
|
1

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}))

1 Comment

That is a lot of typing compared to replacing the function with a dictionary that is accessed directly by the caller.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.