Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Locked learning resources

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Passing Arguments to defaultdict

00:00 Welcome back. In previous lessons, I’ve been telling you how .default_factory must be set to a callable object that takes no arguments and returns a value.

00:09 I know it feels like I’ve been saying that a lot, but it really is one of the key points here. However, that’s a little bit inflexible, right? You might sometimes want to supply different arguments to defaultdict.

00:21 There are two ways of doing this. One is using lambda functions, and the other one is using functools.partial(). Let’s start with lambda functions. Here we are back in the terminal, and the very first thing I’m going to do is import defaultdict from collections so it’s available to me here. Okay.

00:43 So, imagine a scenario where you want to create a function which generates a default value in a defaultdict. The function does some very basic processing and then it returns a value. You could have a function which is something like this.

00:57 I’m going to call this factory(). We’ll pass it an argument here.

01:03 For now, I’ll just set a result to the uppercase value of the argument that’s passed, but you can imagine different kinds of processing taking place here.

01:14 And then I just return this, so return result. All right, it’s not a very impressive function, but it works. The way I can use this is like this.

01:25 I am creating a defaultdict here. It’s called def_dict, I’m calling defaultdict(), and as a first argument—and remember, the first argument is what gets passed to defaultdict()—I’m passing a lambda function. And what that contains is this factory() function which I just created, and I’m giving it a 'default value' here.

01:46 Okay, so I’ve created my defaultdict. We can have a quick look at it.

01:52 There it is, and you can see that this has a lambda function here. Remember, in other examples, it said list or str, as the case may be.

02:02 So let’s see what happens here when I try to access a missing value. My dictionary is completely empty, I can access anything I want. I’ll go with a string 'a' and this returns 'DEFAULT VALUE' in uppercase.

02:15 And remember, in the previous lesson I told you that you can always update the .default_factory. .default_factory is just an attribute, so I could update it with something like this.

02:26 The notation is <the name of my defaultdict>.default_factory and then I set a new value here. I’m reusing my factory() function, which I had defined up here, but this time I’m passing a different input parameter.

02:41 So first I’ll run this. Now my def_dict has been updated. If we have a look at it, it still has just one entry, 'DEFAULT VALUE'.

02:50 But watch what happens when I try to access a different missing key. I already tried 'a', let’s try 'b' this time. And that gives me 'ANOTHER DEFAULT VALUE'. If I look at my def_dict now it has two entries and they both have different default values, which were both generated using .default_factory. In both cases, .default_factory() triggered this lambda function, but in the two cases I had passed different arguments to that function.

03:18 So that’s when you want different string values, but what about when you want different integer values? Remember, if you pass int as the callable to .default_factory, it’s always generating a 0, but we can get around this by doing something like in the following example.

03:33 Imagine we’ve got this list of numbers. I’m going to create a new defaultdict here, and again, I’m setting the .default_factory to lambda, but this time the lambda is just 1.

03:45 So, that ran, let’s just confirm that I have

03:50 a new defaultdict, and you can see it’s empty. I cleared all of what I had been adding before with the keys 'a' and 'b'.

03:58 Now what I’m going to do is I’m going to iterate through these numbers in my list. And each time I don’t already have that number in my dictionary, I’m going to add that number by multiplying it with 1.

04:11 So let’s try accessing def_dict again. And you can see, I have four entries and the entry values are 1, 8, 27, and 64.

04:21 So the first time each of these numbers has been encountered, an entry was created with just 1 mapped to that number, but then each additional time that number has been multiplied by the value which was there. So that’s how lambda can be useful, but what about functools.partial()?

04:39 What functools.partial() allows us to do is it allows me to return a partial object. This is an object with positional and keyword arguments.

04:48 It behaves similar to when you call a function and pass it arguments and keywords, and you can take advantage of this behavior to pass arguments to .default_factory. This is easier to grasp if I showed you an example.

05:01 Here we are back in the REPL and keep in mind that a bit above I had created this function. So I have this function factory(), which takes an argument and returns the same argument but in uppercase.

05:15 Before I can use partial() I need to import it. I import it from functools. Okay, so now it’s available to me. Now I’m going to create a defaultdict.

05:25 And as a default_factory, I’m passing partial(). And as arguments to partial(), I’m passing first my factory function—so, the function which I had up here, which returns uppercase arguments—and I’m passing 'default value' as an argument, which in turn will get passed as an argument to factory().

05:46 Let’s see what this gives us. My defaultdict is empty but if I try to access a missing key, such as the key 'a'—or the key 'b', which is closer, you see this gives me 'DEFAULT VALUE'. Remember with lambdas I showed you you can update what is being passed to .default_factory, and you do this by taking advantage of the fact that .default_factory is just an attribute of your defaultdict, so you can update it as we did here.

06:16 Well, I can do the same thing with partial(). I can do something like this, where I’m updating .default_factory. I’m again passing partial() to it, I am again passing the factory() function, but this time I’m passing a different argument.

06:32 Let’s have a look at what’s in my defaultdict right now. I have 'DEFAULT VALUE' from before, from when I looked up the key 'b'.

06:41 And if I now look for a different missing key—let’s go with 'c',

06:47 then I get 'ANOTHER DEFAULT VALUE'. So what I’ve done is I’ve updated the arguments which I’m passing to .default_factory. And if I now have a look at my defaultdict, you can see I have two entries—one for 'DEFAULT VALUE' and one for 'ANOTHER DEFAULT VALUE'.

07:04 Those were the two ways you can pass arguments to defaultdict. You can take advantage of lambda and you can take advantage of functools.partial(). In both cases, what you would be doing is you would be updating .default_factory as an attribute of your defaultdict. Okay. In the next lesson, I’m going to be going over a brief summary of everything we’ve looked at in this course. I’ll see you there!

Avatar image for Dan B

Dan B on Nov. 10, 2020

Not a great example.

I’d motivate this by saying, what if you just want to return an immutable value for a missing key? Then use lambda.

What if you later want to change the default (totally unrelated topic imho) then use the default_factory arg of the defaultdict.

Two separate topics that were confusingly mushed together in this episode… unless there is something I’m not following?

the whole arg.upper() part is also confusing. It’s not clear why you’re doing this (pattern) or under what circumstances such a pattern would be useful.

Sorry if I’m just not understanding something.

Avatar image for loopology

loopology on Nov. 28, 2020

def_dict = defaultdict(lambda: factory('default value'))

I’m not sure everyone’s aware that in lambda: factory('default value') the factory function is called once and that’s during the instantiation of def_dict. So in fact the above line is just an unnecessarily confusing way of saying:

def_dict = defaultdict(lambda: 'DEFAULT VALUE'))
Avatar image for Levi

Levi on Feb. 10, 2021

Thank you for the tutorial! I’m curious if there’s a way I can pass the key as an argument for the defaultdict value.

In this example below, say I wanted to call d['b']. If the key didn’t exist, I would want the value of 'b' assigned to 'bbb'. If I called 'a', it would be assigned to 'aaa', etc.

d = collections.defaultdict(lambda x: x**3)
Avatar image for Geir Arne Hjelle

Geir Arne Hjelle RP Team on Feb. 10, 2021

@Levi

I don’t think you get to use key in your default factory without doing some hacking.

In such a case, I’d suggest emulating a defaultdict using a UserDict and .__missing__() instead, similar to what is shown in realpython.com/python-defaultdict/#defaultdict__missing__

In your particular example, you can do something like the following:

>>> from collections import UserDict
>>> class TripleDict(UserDict):
...     def __missing__(self, key):
...         self[key] = key * 3
...         return self[key]
...
>>> d = TripleDict()
>>> d
{}
>>> d["b"]
'bbb'
>>> d
{'b': 'bbb'}

Become a Member to join the conversation.