12

I'm trying the return a given list with a function.

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

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

Then I'm stuck. This is a simple/short example of a big list of extensions. What is the easiest way to do it?

Tiskolin
  • 147
  • 17
Ludoloco
  • 147
  • 1
  • 5
  • I've closed this because the solution in both cases is to use a dictionary. – cs95 Nov 27 '17 at 07:03
  • @cᴏʟᴅsᴘᴇᴇᴅ I reopened because that fact does not make it the same question. – timgeb Nov 27 '17 at 07:05
  • @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

5 Answers5

30

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
  • 73,231
  • 20
  • 109
  • 138
  • 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
9

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

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']
Joe Iddon
  • 19,256
  • 7
  • 31
  • 50
  • 13
    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
  • @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
  • 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
  • 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
3

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
  • 73,231
  • 20
  • 109
  • 138
Jump3r
  • 1,000
  • 13
  • 28
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,231
  • 7
  • 33
  • 53
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}))
Alex Stasse
  • 181
  • 2
  • 1
    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