Peeking into Python’s C API
This is a modal window.
The media could not be loaded, either because the server or network failed or because the format is not supported.
Formal Metadata
Title |
| |
Title of Series | ||
Part Number | 155 | |
Number of Parts | 169 | |
Author | ||
License | CC Attribution - NonCommercial - ShareAlike 3.0 Unported: You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this | |
Identifiers | 10.5446/21224 (DOI) | |
Publisher | ||
Release Date | ||
Language |
Content Metadata
Subject Area | ||
Genre | ||
Abstract |
|
EuroPython 2016155 / 169
1
5
6
7
10
11
12
13
18
20
24
26
29
30
31
32
33
36
39
41
44
48
51
52
53
59
60
62
68
69
71
79
82
83
84
85
90
91
98
99
101
102
106
110
113
114
115
118
122
123
124
125
132
133
135
136
137
140
143
144
145
147
148
149
151
153
154
155
156
158
162
163
166
167
169
00:00
Extension (kinesiology)Table (information)SummierbarkeitMachine codeLink (knot theory)Slide ruleData structureKey (cryptography)Hash functionFunction (mathematics)CollisionElectronic mailing listMaxima and minimaStructural loadImplementationInterior (topology)String (computer science)IntegerMultiplication signProgrammer (hardware)Radical (chemistry)Projective planeWebsitePower (physics)NumberKey (cryptography)MappingTrailLengthProblemorientierte ProgrammierspracheTraffic reportingStreaming mediaThresholding (image processing)Hash functionData structureSoftware developerBit rateData storage deviceLink (knot theory)ResultantSubject indexingElectronic mailing listOrder (biology)Logical constantStructural loadString (computer science)Semiconductor memoryLibrary (computing)CodeComputer programmingIntegerBest, worst and average caseDampingDifferent (Kate Ryan album)Electronic signatureParameter (computer programming)Natural numberPresentation of a groupData dictionaryTable (information)AverageComplex (psychology)ImplementationInequality (mathematics)Variable (mathematics)Category of beingAreaMaxima and minimaOperator (mathematics)Functional programmingCollisionProgramming languageoutputSet (mathematics)Modulo (jargon)System callSlide ruleMachine codeSoftwareRecursionComputer animationLecture/Conference
06:35
Function (mathematics)Hash functionString (computer science)Hash functionFunctional programmingIntegerLengthSymbol tableRevision controlData storage deviceMultiplication signInternetworkingRoundness (object)
06:58
WindowFunctional programmingHash functionString (computer science)WritingTable (information)Computer animationLecture/Conference
07:27
Computer programmingCodeString (computer science)Programming languageSensitivity analysisLine (geometry)Computer animation
07:49
Mathematical singularityObject-oriented programmingHash functionTable (information)Computer fileCodePointer (computer programming)Object-oriented programmingCategory of beingString (computer science)Representation (politics)Hash functionCountingMultilaterationTable (information)Functional programmingLibrary (computing)Point (geometry)Disk read-and-write headMacro (computer science)EmailMaxima and minimaSemiconductor memoryType theorySocial classData structureCASE <Informatik>Multiplication signStructural loadComputer animation
09:21
Module (mathematics)Functional programmingComputer programmingLine (geometry)Module (mathematics)Type theoryData dictionarySocial classObject-oriented programmingPoint (geometry)Different (Kate Ryan album)Content (media)Hash functionComputer fileCodeTable (information)Order (biology)Data miningCompilation albumLatent heatEndliche ModelltheorieSet (mathematics)Multiplication signMereology
10:28
Computer fileComputer programmingWindowHash functionExtension (kinesiology)Table (information)Interpreter (computing)Directory serviceModule (mathematics)Type theoryObject-oriented programmingBuildingSource code
11:08
EstimationElectronic meeting systemCountingComputer programmingTrailNumberType theoryEndliche ModelltheorieSemiconductor memoryObject-oriented programmingSpeicherbereinigungCountingFunctional programmingMemory managementModule (mathematics)Sheaf (mathematics)Electronic mailing listProgrammer (hardware)High-level programming languageBitComputer animation
12:27
Interior (topology)Functional programmingObject-oriented programmingParameter (computer programming)
12:51
Demo (music)Object-oriented programmingFunctional programmingModule (mathematics)Physical systemEndliche ModelltheorieNamespaceParameter (computer programming)Projective planeMultiplication signSpeicheradresseFrame problemSystem callCountingFunction (mathematics)Computer fileRight angleLocal ringComputer animationLecture/ConferenceSource code
14:52
Arithmetic meanExtension (kinesiology)Semiconductor memoryMereologyMacro (computer science)Computer programmingSystem callCountingType theoryObject-oriented programmingFunctional programmingCrash (computing)Point (geometry)Graph (mathematics)Right angleSource codeComputer animationLecture/Conference
16:13
Functional programmingType theoryObject-oriented programmingData structureGraph (mathematics)CountingHash functionTable (information)Semiconductor memoryPointer (computer programming)Projective planeStaff (military)FreewareComputer programmingTerm (mathematics)Rule of inferenceSubsetComputer configurationParameter (computer programming)SurfaceSet (mathematics)Exception handlingLecture/ConferenceComputer animation
17:39
Computer programmingEndliche ModelltheorieFunction (mathematics)WindowMultiplication signElectronic mailing listWrapper (data mining)Gradient descentCountingString (computer science)Object-oriented programmingPoisson-KlammerHash functionTable (information)Square numberMappingFunctional programmingKey (cryptography)Module (mathematics)Scripting languageCompilerRight angleSource code
19:18
Demo (music)Multiplication signSquare numberTheoryString (computer science)BuildingRevision controlEndliche ModelltheorieGoodness of fit
20:07
Hash functionTable (information)Functional programmingDemo (music)Computer programmingType theoryHash functionGraph (mathematics)Object-oriented programmingCountingPointer (computer programming)Cellular automatonVideo gameTable (information)Data structureAttribute grammarDefault (computer science)Computer animation
21:41
Demo (music)Hash functionTable (information)Multiplication signCountingMathematicsGame controllerObject-oriented programmingFunctional programmingComputer programmingSource codeComputer animation
22:26
Table (information)Functional programmingObject-oriented programmingCountingEncryptionBulletin board systemMultiplication signHash functionComputer animation
22:49
ResultantFunctional programmingObject-oriented programmingMultiplication signCountingHash functionTable (information)System callSource codeComputer animation
23:49
Semiconductor memorySemiconductor memoryType theoryError messageObject-oriented programmingCountingFunctional programmingHash functionComputer programmingVideo gameComputer animation
24:26
Hash functionError messageLine (geometry)Endliche ModelltheorieHash functionMultiplication signSet (mathematics)Computer programmingDataflowLetterpress printingFunctional programmingStatement (computer science)Module (mathematics)Wrapper (data mining)Library (computing)CodeIntegerStack (abstract data type)String (computer science)Computer animation
25:08
Computer programmingLibrary (computing)Hash functionSquare numberContent (media)Electronic mailing listSound effectModule (mathematics)Multiplication signKey (cryptography)Power (physics)Functional programmingDemosceneEndliche ModelltheorieTable (information)Line (geometry)CodePoint (geometry)Object-oriented programmingString (computer science)Data structureLevel (video gaming)Letterpress printingMengenfunktionStatement (computer science)Wrapper (data mining)SpacetimeResultantMoving averageFreewareSource codeComputer animation
27:29
Multiplication signFunctional programmingShared memoryPoint (geometry)Initial value problemDifferent (Kate Ryan album)Type theoryCodeError messagePointer (computer programming)Poisson-KlammerSlide ruleComputer configurationQuicksortRule of inferenceForm (programming)Graph (mathematics)Object-oriented programmingAreaData structureComputer animationLecture/Conference
Transcript: English(auto-generated)
00:04
Welcome to another talk. Sophia Davis is going to be talking about picking into Python's C API. So we're all here because we like Python, the programming language.
00:24
Today I'm going to talk a little about Python, the C program underlying that programming language, by walking through how I learned the basics of making a C library callable from Python code, and vice versa. So here's a screenshot of the first time I segfaulted the Python REPL.
00:41
I'm Sophia, I'm an American software developer, and I'm right now in Amsterdam. In the summer of 2014, I attended the Recurse Center, which is kind of like a writer's workshop for programmers in New York City. And this talk comes out of a very down the rabbit hole kind of project that I worked on while I was there.
01:01
Code and soon a link to the slides will be on my GitHub. So let's get started. This is the story of how I shaved a yak, because probably if you find yourself breaking out the Python C API docs, you started out with a different problem, one that you thought you could solve using tools provided by an existing C codebase.
01:22
So for me, this codebase was a hash table implementation. So this is probably review for most of you, but just in brief, a hash table is a very powerful data structure for storing key value pairs, for associating keys with values such that every key maps to one value. For Python people, we tend to call them dictionaries.
01:43
They're so powerful because they're very efficient. No matter how many key value pairs you have in a hash table, the average time complexity of adding a key value pair, looking up the value associated with the key, or removing a key value pair is constant, so they go one. How does it achieve this amazing performance?
02:02
Well, under the hood, a hash table is just an array, and we're going to call each index in this array a bucket. Every key value pair gets put into one of these buckets. And how do we know which pair goes in which bucket? Well, that's where the hash of hash table comes in. A hash function is just a mapping of any arbitrary input to a fixed set of values,
02:24
like the set of all positive integers. When we want to put a key and a value in our hash table, we pass the key through a hash function to convert it to an integer, and then we use that number modulo the size of the array to determine which bucket the key value pair should go in.
02:41
We worked similarly for lookup and remove. We calculate the hash of the key, go to the bucket associated with that hash, and either look up the value or remove the key value pair. So here's a picture, thanks Wikipedia, of a phone book stored as a hash table. So we calculate the hash value of each person's name, and use that number to determine the bucket in the array to put the phone number entry.
03:05
But what happens if the hash values of two keys result in them being put in the same bucket? Well, that's called a collision. And there are a couple ways of dealing with it, but one way is just to store a linked list at every bucket in the array.
03:21
So every item that gets assigned to that bucket just gets tacked onto the linked list. Again, at the Wikipedia example here, we're using a hash function that results in John Smith and Sandra Dee both being assigned to the same index, 152. So we've just started a list containing both entries.
03:41
But if lots of items end up in the same buckets, then our hash table starts to look like just a bunch of linked lists, and the performance of linked lists is not nearly as good as those of a hash table, especially when looking up or removing items. A lookup or removal on a linked list, in the average case, involves traversing the list, which is a big O of N operation.
04:02
And as we add more and more items to our hash table, it's inevitable that more and more entries will end up in the same bins, the same buckets. Consider a hash table with an underlying array of length one. No matter what hash function you use, all items are going to be stored in that one and only bucket, and that's rapidly going to be a long linked list.
04:21
So in order to keep our average performance constant, we'll occasionally increase the size of the underlying array and redistribute our keys. Then, provided that we're using a decent hash function, the number of collisions should decrease because we're spreading out the same number of keys as before among more buckets.
04:40
And how do we know when to resize? Well, if we keep track of the number of items currently in the hash table compared to the length of the underlying array, then we should resize when the proportion of items to length reaches a certain threshold. We'll call this the maximum load proportion. So we've talked about three variable properties of hash tables.
05:02
We've got the size of the underlying array, the hash function, and the maximum load proportion. And all three can affect the performance of your hash table. For example, initial size helps determine how often you might need to resize, and that's going to be a costly operation. The hash function impacts how many collisions you might have,
05:21
and more complicated hash functions will take longer to evaluate. The maximum load proportion will play a role in how long those linked lists might get before you resize, etc. So in order to explore how these affect performance, I wrote my own hash table implementation, and it enabled the user to choose the maximum load proportion
05:42
and the initial size of the underlying array. My library provided functions to initialize a table with the given properties, to add, lookup, and remove key-value pairs of integer, float, and string type, and finally to free the memory malloc to store the data structure, so the array, the linked lists, all your strings, data, whatever.
06:04
And I also wanted to explore how different hash functions would affect performance. So this is the signature of the add function in my C implementation. It accepts a hash argument, because my idea was that the user should do their own hashing of the keys, and then they'd just pass that hash value in
06:21
when adding, looking up, or removing an entry. And my library would find the appropriate bucket for that key-value pair based on the passed-in hash. If the user chose not to pass in a hash with their key, then my library just used this hand-rolled hand function. Hash functions keep me. So if it's an integer, use the integer.
06:42
If it's a float, round it down and use that integer. If it's a string, just use the length of the string. That was inspired by the hash function that, no joke, an early version of PHP used to store function names in the symbol table. Good times. So it's basically an awful hash function.
07:01
And next, I set off to do some hardcore bit-shifting and string manipulation in C to experiment with writing my own hash functions. Just joking. If I were going to experiment, I'd rather do it in Python. Wouldn't it be nice if I could write cool hash functions in Python and then just call them from my C hash table code?
07:21
After all, Python is so nice and easy to write, and I'm a lot faster at writing Python than I am at writing C. But you know, under the hood, Python is actually just a really big, complicated C program that processes those strings of whitespace-sensitive code that we write. And thankfully, there's a well-documented API
07:41
for bridging the gap between Python the programming language and Python the C program. It's as easy to use this API as including a simple line in your C file. And then the magic begins. So my goal is to call a hash function that's written in pure Python from inside my C hash table library.
08:03
Little disclaimer, the C API did change substantially-ish between Python 2 and Python 3, and all the code in my talk is Python 2. So I started by wrapping everything I needed to use a hash table inside my hash table library inside of a struct.
08:20
So I've got a pointer to the actual data structure, and some of those other properties associated with hash tables like the current load, the initial size. I also have this py object pointer to hash func. So at this point I had the data that I wanted in my Python type, but I needed to implement that API telling Python how to manage objects of this type.
08:43
It starts with this py object head thing, which is a macro imported with the Python header. It expands to the bare minimum that you need to create a Python object, which is a reference count. I chose to ignore that at the time. And a pointer to this pytype object,
09:01
which is just a struct of function pointers defining how Python should manage objects of the hash table type. So that's things like the class name, how to print and make a string representation of your object, how to initialize, delete, and free the memory allocated to hold objects. I'll come back to these a little later.
09:21
And there are a lot more that I left out. So at this point I had my basic type defined, but I needed some way to use this type from within Python code. So I created a module to contain the hash table type. In order to initialize a module, you need to write a pymod init func function
09:41
that has the name init your module name. So mine's init hash table. When a Python program imports a module for the first time, this is the function that's run. Again, I've left some stuff out, but of note are this line, which initializes the type, and it fills in more of that pytype object
10:00
with, I think, some compiler-specific functions. Here we initialize the module, and this line adds my new type to the module dictionary so we can actually instantiate new objects via the class name. Packaging. There are a couple different ways to package Python modules, but one simple way is just to write a setup.py file,
10:23
telling Python the name of your module and what C files your module needs. So this is the entire contents of my setup.py file. When you run python setup.py build, it creates a build subdirectory and puts a compiled file containing your extension
10:44
that can be dynamically loaded into a Python program. So on Unix, this is a shared object file. I work on a Mac, so my module is named hashtable.so. On Windows, this would be a dll with a .pyd extension. So if you start up a Python interpreter
11:00
or run a Python program in the same directory as that .so file, then you can type import hashtable and do hashtable stuff from Python. So that was cool, except my program kept segfaulting, and I was forced to look at a section of the API docs that I had kind of been ignoring, which was the section on reference counting.
11:23
One reason why Python is so nice is that it's a pretty high-level language, and it handles a lot of things for the programmer, for example, memory management. When you use data in a Python program, Python takes care of dealing with the OS to ensure that that data is stored in memory.
11:41
However, if Python only added to your program's memory, eventually the program would run out of memory, so it needs to know when it can remove data once that data isn't being used anymore. Python the C program uses a method called reference counting to know when it can safely free objects. So that means it keeps track of the number of other things
12:01
referring to a given object, and when that reference count drops to zero, Python cleans up the unneeded object by calling the deallocation function that's defined for its type. So here are two tools that can help us understand reference counts a little bit. From the sys module, we have the getRefCount function, and from the GC garbage collection module,
12:23
there's getRefers, which returns a list of all things that currently own references to an object. So here I've written a function showRefCounts, and all it does is finds the objects that own references to the argument named an object,
12:41
it prints out how many there are, and optionally, it can call itself a lot of times, and it can optionally print out extra details about exactly which objects own references to an object. So let's look at how this works. So in a Python shell,
13:01
we're going to start by importing the tools that we need, which are the sys and GC modules, and also that function I just showed you, which I saved in a file called refCounts. So we'll start by instantiating a new object, and we'll see what the reference count is on that object.
13:20
Two, all right. And if we assign another variable name to that object, then we'll look at the reference count again, and it's three. Cool. So what exactly is referring to this object? Well, we'll use that getRefers function. So we've got this dict here with the two variable names that we just made,
13:40
and they're both referring to object, object at memory location thing. So what exactly is that? Well, it's the local namespace. So there it is again. Cool. So what happens to the reference count on an object if we pass it as an argument to a function?
14:03
Well, we'll use that function that I just showed you. So first we'll call it, we're going to pass in our object, and we'll have it not, we'll just call it once, and we will show the details about the referrers this time.
14:22
So we've still got that local namespace, but there's something new, this frame object in there that now also owns a reference to our object, and if we call it again, this time we'll have it call itself recursively a bunch of times, and we'll turn off the overwhelming debug output.
14:40
So we see that each time we call the function, the reference count increases by one, and if we were to look at the details of the referrers, we'd see another frame object being added to the referrers with each call. Cool. So if you're going to write a C extension and work with Python objects,
15:03
then first you need to signal to Python when your program starts working with a certain object by triggering Python to increase the reference count on that object by one, thereby keeping it in memory while you, represented by that one, need it. Otherwise, if the reference count drops to zero, then Python will free the object,
15:22
and when your program tries to access that object, which is now in a piece of memory that has been released back to the OS, I guess, your program will crash, hopefully, or just weird shit will happen. You also need to take care of telling Python when you're done working with a certain object,
15:41
by decrementing its reference count by one. If you don't do your part in decrementing that reference count, its reference count can never decrease to zero, and it will never be cleaned from memory, so that's a memory leak. The Python C API provides two macros to communicate when you're starting to work with an object, and when you're finished working with an object.
16:02
Calling pyaincraft on an object increases the reference count by one, and calling pydecref decreases a reference count by one. Also, if the reference count has reached zero, it triggers a call to the deallocation function for the type. So, what happens when you forget to pyaincraft an object that you need to work on?
16:22
Remember that pytype object struct of function pointers that defined the Python API for my type? Well, this was what I had defined as the deallocation function for Python to call when the reference count of a hash table object reaches zero. It does two important things. The first one is some printf debugging, so we can see when it's being called,
16:43
and it also calls the free function that I defined for my object via the pytype object struct. And so that free function also has some nice printf's, and then calls the free table function provided by my initial C program to free the memory malloc for that hash table.
17:03
Initially, the set method on hash table objects returned the hash table object itself. See, here we've got turnself. Now, there are a lot of rules and exceptions about in which situations the caller versus the callee is responsible for pyinkrefing arguments, and I barely scratched the surface.
17:22
However, I think I caused a problem here because if a C function returns a reference to an object, like self, then that reference must be owned by the function, i.e. the object must have been pyinkrefed inside the function. But I had left that out,
17:42
so let's see how this affects my program. First, we'll use that setup.py script to build our module. Okay, compiler output, great. I've got another window open here to the build subdirectory, so there's our .so file.
18:01
And if we start up the Python REPL, then we can import hash table, my module, and we can instantiate a new hash table object, and we'll start setting some values. So, not very creative, but I rounded down.
18:22
So, remember that the set method right now, it returns the hash table object itself. So this is the string representation, the repper, or whatever, being returned to the Python REPL. Each bracket square represents one of our buckets, and each star represents an item in the linked list at that bucket.
18:41
So let's set some more values. Two maps to four. All right, so, uh-oh, those are the printf's that I put inside my cleanup functions. We didn't tell Python to increase the reference count on that object, on that hash table object, but all the other referrers must have released their references to that object,
19:04
and its reference count has dropped to zero, and the cleanup functions have been called. So, if we try to do anything else with our hash table object, like set another key value pair, then Python's like rods.
19:22
So, let's add that PyIncraft back in there, and look at the demo again. I just ran the build step, and so we'll import the new version of my module, and instantiate a new object, and start setting some values.
19:41
So, we'll start with pi again, and there, a star representing pi, great. Two stars, looking good. No segfaults so far. We'll keep going with this squares thing. It resized, that's great. We've still got three items, four items. We'll try a string,
20:02
because strings are evil. And it resized again, so looking good, no segfaults. I think that solved that problem, at least. But, the other type of mistake you can make is forgetting to call PyDecGraph when you're done with an object, and I also ran into that issue. So, remember that my Python hash table type struct here,
20:23
it contains a pointer to another Python object, namely the hash function used to hash keys. So, here's a snippet from the initialization function of hash tables. We do some other stuff, but I set the object hash func attribute either to the hash function passed in by the user
20:42
when the object was initialized, or to Python's built-in hash function, just as a default. I also increased the reference count on this hash function object, because I need to tell Python, like, hey, I'm going to be working with this function object for a while, please don't clean it up. So, we say that each hash table object
21:01
owns a reference to the hash function object. Conversely, in the deallocation function for my hash table type, I tell Python to decrement the reference count on that hash function object. So, here's a simple demo. We're going to look at the reference count on Python's built-in hash function. So, I have this function here, hash table stuff,
21:22
and all it does is initialize a hash table using the built-in hash function as the hash function. So, our hash table object will own a reference to the built-in hash function object, hash. It prints out the reference count on that function object, and then this program will just call
21:41
do hash table stuff a couple of times. So, let's look at how that reference count on the built-in hash function changes. I've run the build step, and we're going to run the program. So, initially, we start out with just three references
22:00
to the built-in hash function object. Each time we enter do hash table stuff and instantiate a new hash table that owns a reference to the built-in hash function, the reference count on that function object increases by one to four. Each time do hash table stuff completes, the hash table that was initialized inside of it
22:21
goes out of scope, so the reference count on the hash table drops to zero, which triggers the deallocation function for hash tables. This function, which triggers a PyDeckRef on the built-in hash function object. So, after calling do hash table stuff a couple of times, we still just have a reference count of three
22:43
on that built-in hash function object. But let's just say that we had forgotten to decrease that reference count. And we'll run that demo again. I just ran the build step. So, this is without that PyDeckRef.
23:03
Initially, the reference count is still three up there. And each time we enter do hash table stuff, we instantiate a new hash table which owns a reference to the built-in hash function object. The reference count on that function object increases by one to four to five to six. Because each time do hash table stuff completes,
23:23
its hash table goes out of scope, reference count on hash table drops to zero, and its deallocation function is called. But nowhere did we release that reference that we own to the built-in hash function. So, after calling do hash table stuff a couple of times, the reference count on the built-in hash function object
23:41
has increased from three to six, even though the objects that owned those last three references have themselves been freed. So, this is a memory leak. Those three extra references were owned by objects that Python has now cleaned up.
24:00
They no longer exist. We've lost our opportunity to signal to Python that those references aren't needed anymore. The reference count can never drop to zero, so Python will never remove that function object from memory. Now, we're talking about the built-in hash function here, so it's not like we really want it removed from memory, but imagine a more memory-intensive object
24:20
and a longer-running program that created tons of these objects that could never be cleaned up. Eventually, this type of error will cause a problem. So, I went back and added that pydec ref line, and I rebuilt my program, and after all that, I finally had a module that worked well enough. So, I wrote my very own Python hash function.
24:41
If the item to hash is an integer or a float, then we'll do this one thing I found on Stack Overflow, and otherwise, we're clearly hashing a string, so I did this other thing I found on Stack Overflow, and so I've also included a print statement, so we can see when this function is called, it's prefixed by the word Python because this is Python code,
25:00
and I also went back and added more print statements to my C wrapper module and the original C library, and those are also prefixed by their origin. So, now let's look at how the program works. So, I've started an ipython REPL here with that awesome hash function loaded,
25:21
and we can import the module, and so we can see some print statements that I put inside that init hash table function. So, I can instantiate a new hash table object, and as a hash function, we're going to use that awesome Python hash function
25:40
that I just wrote. So, via the wrapper module, we see that it's the underlying C library that's actually doing the heavy hitting of malleking space for my data structure and getting everything set up. So, we can look at that new object. It's empty. Great. So, we'll set some values.
26:02
I was on a roll with squares here. So, we see the C module, the set function there, is executing the Python code that I wrote in the REPL. It's calling Python. That's so cool. And then it's also further, you know, dealing with the underlying C library,
26:20
which actually does the hard work of adding that item into the right linked list and things. So, we'll set some more values. Cool. Still works. Strings always break everything. So, hey, look. So, the underlying C library knew that it needed to resize. So, that was done.
26:41
And it's bigger. Great. Also, bonus, it works as a hash table. We can look up values. We can remove the value, the key value pair associated with pi. And now, if we try to look up the value again, then the library, the underlying library looks for the key value pair,
27:00
can't find it, and the module takes care of returning none to the REPL. When I finally quit the REPL, then, again, we see that the deallocation function is called from the C module, but it's the underlying library that's actually doing the heavy hitting of walking through all my data structures and free new stuff. So, I've got the Python REPL calling the functions
27:22
that I wrote in my C module, and that C code executing the hash function that I wrote in Python in the REPL, and it's just all working together, and I thought it was pretty cool. But, if there are any questions.
27:46
Are there any questions? Well, first, thanks for sharing. It looks great. Could you clarify, is this a typo? I don't think so. At some point you had some pi-decref macros,
28:05
and in other code I think I saw pi underscore x-decref. Is that a typo, or are those different types of references? There are two different types. The pi-x-decref one doesn't throw an error
28:21
if the pointer is none, whereas the other one, if you can't, if you actually have a pointer there, then it won't work.
28:43
Would it be hard to make syntax like in native Python dictionary, you can initiate values with brackets and so on? I'm not sure, but I know that all the double under methods
29:01
are related to C functions, so probably that would work. You'd just add it to that pi-typed object, struct of function pointers, but I'm not sure. Anybody else?
29:27
Thanks for the talk. Very well structured. I enjoyed that. The answer to this question is probably no, but did you get a chance to experiment with any other ways to do this? Let me go back to my yak slide. No, I just shaved into it. I thought we'd know because I've been exploring this.
29:43
There's five or six different ways to do it, and this is one of the ways, that's why I was interested in the talk, but this is a very good example, so I do like this as well. Maybe we have time for one more. Thank you very much.