Python Memory Management and Tips Transcripts
Chapter: Recovering memory in Python
Lecture: Reference counting

Login or purchase this course to watch this video and the rest of the course contents.
0:00 Alright, we're gonna do some fun programming over here,
0:02 and it's fun because you've got to be a little bit clever to work and understand
0:07 reference counting in Python, because if you create a variable and you point it at a thing so
0:12 you can ask questions about it like,
0:13 "is it alive or not?", it's always gonna be alive cause you're giving at least
0:17 one to that reference count. So we're gonna use some cool little
0:22 utilities we're gonna write. So I'm gonna write
0:24 one first called "memutil" and I'm going to drop some code in here because
0:29 we don't need to do a whole lot here.
0:30 So we're gonna use this "ctypes" thing,
0:31 which actually allows us to get kind of a direct reference to PyObjects,
0:37 that's the same one we talked about, and then give us the field reference count.
0:40 Now I'm going to add that to the dictionary so it doesn't look broken.
0:44 It's going to come back as a long, like I said,
0:47 and then what we can do is if we get one of those id's,
0:50 remember, id of thing,
0:52 we can come down here and use this, say parse it from that address,
0:56 and then we're going to get the reference count.
1:00 Okay? We're gonna use that.
1:03 Then we're gonna have our "app_refcount"
1:05 That's the one we're gonna run.
1:07 I'll go ahead and set it to run
1:09 now and I'll just hit the hotkey,
1:11 we don't need it that big, do we?
1:13 There we go, and I'll do my fmain magic,
1:16 so it's ready to run. Now we're going to need something interesting to work with.
1:22 First of all, we can work with the garbage collector.
1:26 We're not really talking about the garbage collector,
1:28 and I only want to work with it to the extent where I say "let's
1:32 not have it do anything". Let's just disable it for now
1:34 so we don't think about it or worry about it because what's happening has nothing to
1:38 do with the garbage collector. Later we're gonna enable it and focus just on its
1:43 role in this whole world. We're gonna create some variable, call it "v1" and let's
1:47 just give it the number 7 and then we'll have,
1:50 I'll call it "oid"
1:52 for object id. It's gonna be the id of v1.
1:55 Now what we need to do is we need to grab this early
1:58 so we have the id.
1:59 This number knowing the memory address that the location given to us by id will not
2:06 keep this thing alive. Right?
2:07 So it could be potentially up for collection.
2:10 And we could still use this to ask from our memutil here.
2:14 So we'll say "import memutil" as well.
2:18 We're gonna need that, but in order to actually track this really clearly,
2:22 we need to do one more cool thing.
2:24 So I'm gonna create an object,
2:25 a class, and I'll call it "doomed".
2:28 Why is it doomed? Because its only purpose in life is to get created and
2:31 then get destroyed by the reference counting cleanup system.
2:36 So let's go in here and find a class "doomed".
2:39 Now, this is going to basically plug into the Python data model,
2:44 the "dunder methods" to capture different lifetime events of this object. So we'll have an
2:50 "__init__", and this is going to be when it gets created.
2:54 Let's go ahead say that it can have some friends.
2:55 I don't think we're gonna use this yet,
2:57 but this "*friends" means this will come in as a list and then we'll just
3:05 print. We'll come over here and say "at {id(self)}".
3:13 So we created this doomed thing wherever it happens to be now.
3:16 That's what happens when the thing comes to life.
3:19 And then we're gonna have another one run when the thing gets deleted.
3:28 Then we wanna have some string representation of it
3:30 so it's easy for us to just print it out and see what's going on
3:33 and we'll just do the id there,
3:35 but we want to have a string Representation so we'll have
3:37 a "__str__" and we'll return a "__repr__" as well
3:42 we'll just make them be the same.
3:46 We're just gonna paste some code
3:48 so you can see what's happening. All right,
3:49 so what we're gonna do is we're gonna say "there's a doomed object at this address
3:52 and it has however many friends if there are friends,
3:55 Otherwise it's not going to talk about his friends".
3:57 Doesn't want feel bad. So this is gonna be are doomed object.
4:02 We're going to create one of these over here in our code. Instead of creating 7,
4:05 I'm gonna create a "doomed"
4:07 and it's going to start with no friends. We'll come back, the friends is more important in the garbage collection side
4:11 of things. But let's just run this and see what happens.
4:16 Notice it created a doomed object at some address and then it deleted the doomed object
4:21 at that address. And that delete was when this function returned,
4:26 no more things pointed at it,
4:27 and it went away. But we can be more interesting than that.
4:31 Let's go see what else we can do.
4:35 Let's start by first printing out how many things refer to it.
4:39 So let's say "print", and then here
4:44 we're gonna use our "memutil.refs(oid)".
4:47 Now we run this.
4:50 You might think it's zero right,
4:53 cause we're kind of done with it,
4:54 but it doesn't get cleaned up explicitly.
4:56 Doesn't get cleaned up unless we explicitly do so until line 13.
5:01 So it should still say "1".
5:02 There we go. Step 1 ref count is 1.
5:06 Now, let's go to step 2.
5:10 Step 2 is we're gonna have another variable that is equal to v1.
5:15 So remember the way this works is Python sees this and says "there's a new variable
5:20 defined that is now pointing over there",
5:22 and so we now have v1 and v2 pointing at our doomed
5:26 object, and so now it's gonna have to increment that reference count.
5:30 Let's try that. Sure enough,
5:34 reference count is 2. Alright,
5:35 let's do some more things. Let's go over here and do this again.
5:39 Step 3 is we're gonna change where
5:43 variable 2 points. We're going to tell it
5:45 to point at none, but it could also be at 7.
5:47 It doesn't matter. If it's just not pointing at the doomed thing
5:51 anymore, that's going to decrement, take away one from the reference count.
5:55 Can you see it went "1, 2, 1",
5:57 okay? Pretty cool. And the final step,
6:02 step 4, let's go and tell v1
6:06 it also no longer points at doomed.
6:09 And at that point, nothing, not v1 not 2, nothing else should be pointing
6:14 that v1 and so it should clean itself up.
6:16 And let's do a "print('End of method')".
6:20 Right? So we should actually see this
6:22 go to zero. We should see it get cleaned up here,
6:25 and this should return zero, and you should see all that before it's getting cleaned
6:29 up naturally as part of the method return.
6:33 Are you ready? Let's see what we got.
6:34 Here goes. Beautiful. Okay, reference count is 1, then it's 2,
6:39 then it's 1, and this line, you can't really make it happen
6:44 all at once, but this line 19 is what is causing this right here.
6:48 When we say "the last thing no longer points here" immediately, like on line 19,
6:54 basically, this thing is getting destroyed,
6:57 the memory is getting reclaimed. And then later we can say,
7:00 Well, now how many things point at it?
7:02 Nothing. But that's already been the case on line 19 which did the cleanup.
7:06 And of course, that happened before the end of the method.
7:09 And if we don't do this one,
7:10 you'll see the cleanup doesn't come until after the end of the method.
7:15 So that's pretty cool. And I want to emphasize this is not non-deterministic.
7:21 Rather, I should say this is deterministic.
7:23 It will always, always be the case that on line 19 this will get cleaned up. Run it
7:29 again. You can see it definitely ran between step 3 and 4,
7:32 the destructor, if you will,
7:35 of our doomed object, right?
7:37 And it said "hey, I've been destroyed, I'm gone,
7:39 I've been deleted by memory management,
7:42 either reference counting or the
7:43 GC". This is really important.
7:45 This is incredibly lightweight. All you have to do, all Python, rather, has to do
7:52 to implement this is just to keep count of how many things point at it
7:55 and when some variable changes assignment just increment or decrement that number. If that number ever hits
8:01 zero, immediately take it out of the block and tell the block that spot is
8:07 now available again, right? You don't even actually have to clean up the memory.
8:10 So this is really, really efficient. In the deterministic part,
8:14 a lot of languages use garbage collectors as the primary way of cleaning up their objects,
8:20 you know .NET, Java,
8:22 those types of things. And because of that,
8:24 it's non-deterministic. When the garbage collector runs is based on the behavior the program
8:30 has had over time. You can't say on line
8:32 19 this thing's gonna get cleaned up.
8:34 You can say "well when the memory kind of gets full enough and the heuristic decides
8:38 that section of memory is worth looking at again,
8:41 then it'll get cleaned up", and that could be problematic for real-time
8:45 things, like stock trading, that has to have no latencies of,
8:50 like, 4 milliseconds or whatever it might turn out to be, right?
8:54 So this deterministic aspect of reference counting is really nice,
8:57 because it's going to behave the same way, memory wise, every single time.