Dynamic Class Generation in Python
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 | 4 | |
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/21171 (DOI) | |
Publisher | ||
Release Date | ||
Language |
Content Metadata
Subject Area | ||
Genre | ||
Abstract |
|
00:00
Symbolic dynamicsSoftware developerTopological algebraDynamical systemUniverse (mathematics)Video gameRight angleSystem administratorAreaLecture/Conference
00:44
Symbolic dynamicsRun time (program lifecycle phase)ArmData typeObject (grammar)Standard ModelCodeSource codeInstance (computer science)Computer programmingParameter (computer programming)Topological algebraMaxima and minimaSymbolic dynamicsMultiplication sign2 (number)LogicCategory of beingFunctional (mathematics)Topological algebraType theoryResultantRun time (program lifecycle phase)Inheritance (object-oriented programming)Variable (mathematics)TupleLatent heatCASE <Informatik>System callCartesian coordinate systemPattern languageEndliche ModelltheorieMenu (computing)Letterpress printingFrustrationRow (database)WorkloadPhysicalismXML
03:30
Level (video gaming)Electric generatorComputer-assisted translationSource codeInsertion lossLevel (video gaming)Product (business)Pattern languageClient (computing)CodeFunctional (mathematics)Symbolic dynamicsLibrary (computing)Software maintenanceCartesian coordinate systemResultantRun time (program lifecycle phase)CASE <Informatik>Service (economics)Standard ModelDemo (music)Term (mathematics)BitCuboidConcentricWordEndliche ModelltheorieMultiplication signTopological algebraIntegrated development environmentDifferent (Kate Ryan album)Lecture/ConferenceComputer animation
05:26
Demo (music)Client (computing)Data typeInformationInteractive televisionOnline helpPhysical systemObject (grammar)Ext functorDefault (computer science)Lambda calculusInterior (topology)Service (economics)Scaling (geometry)Point cloudSynchronizationData storage deviceGateway (telecommunications)TrailEvent horizonBlogLibrary catalogElasticity (physics)Identity managementCodeElectric generatorComputer-assisted translationCognitionSet (mathematics)Ordinary differential equationComputer fileSymbolic dynamicsStandard ModelConfiguration spaceContent (media)Computer fileService (economics)Client (computing)Run time (program lifecycle phase)Line (geometry)Standard ModelSystem callOpen setFiber (mathematics)DialectSubsetAreaOperator (mathematics)Order (biology)outputSingle-precision floating-point formatCodeFunctional (mathematics)Source codeComputer animation
07:23
Dynamical systemDatabaseCanonical ensembleSource codePoint (geometry)Cartesian coordinate systemFunctional (mathematics)Multiplication signOrder (biology)Classical physicsStandard ModelWeb 2.0DatabaseClient (computing)Library (computing)CASE <Informatik>CodeServer (computing)Computer animationLecture/ConferenceXML
08:06
BuildingWeightTopological algebraSymbolic dynamicsCartesian coordinate systemStandard ModeloutputCodeClient (computing)InjektivitätLatent heatDependent and independent variablesInterface (computing)Communications protocolEndliche ModelltheorieInferenceHypermediaComputer animationXML
08:41
Client (computing)Real numberCartesian coordinate systemValidity (statistics)Interface (computing)INTEGRALDependent and independent variablesLatent heatExtension (kinesiology)ParsingEndliche ModelltheorieoutputCASE <Informatik>Menu (computing)Point (geometry)Computer animation
09:07
Client (computing)String (computer science)Dependent and independent variablesComputer wormParameter (computer programming)ResultantDirection (geometry)System calloutputValidity (statistics)Type theoryClient (computing)AdditionElement (mathematics)Revision controlQuery languageCodeSampling (statistics)Multiplication signMappingPhysical systemXML
10:53
Standard ModelClient (computing)Endliche ModelltheorieClient (computing)Electronic mailing listSystem callCASE <Informatik>Function (mathematics)Structural loadComputer clusterValidity (statistics)Position operatorParameter (computer programming)Exception handlingRun time (program lifecycle phase)Standard ModelElement (mathematics)ResultantParsingDependent and independent variablesString (computer science)IntegerOrder (biology)ParsingSampling (statistics)Uniform resource locatoroutputOperator (mathematics)Computer fileLatent heatTerm (mathematics)View (database)InferenceType theoryTouch typingNumbering schemeMedical imagingWeb 2.0Right anglePresentation of a groupFormal grammarInternet service providerSpacetimeProxy serverMereologyComputer animationXML
14:04
Latent heatFactory (trading post)Address spaceLatent heatClient (computing)Element (mathematics)Figurate numberLine (geometry)Order (biology)System callString (computer science)Operator (mathematics)Functional (mathematics)Online helpType theoryFinite differenceStandard ModelVariable (mathematics)CASE <Informatik>SpacetimeParameter (computer programming)Data dictionaryCategory of beingInheritance (object-oriented programming)Proxy serverGoodness of fitCodeDifferent (Kate Ryan album)Factory (trading post)Point (geometry)Office suiteInstance (computer science)Military baseSoftware testingWordExtension (kinesiology)Right angleWorkstation <Musikinstrument>Computer animationXML
17:18
Client (computing)Factory (trading post)Functional (mathematics)Client (computing)Extension (kinesiology)Military baseMultiplication signOrder (biology)ResultantSemiconductor memoryString (computer science)Cache (computing)Key (cryptography)Standard ModelType theoryParameter (computer programming)Operator (mathematics)Default (computer science)TupleSampling (statistics)Cartesian coordinate systemSystem callData storage deviceSet (mathematics)Exception handlingHookingComputer configurationElectronic signatureLine (geometry)Server (computing)RootLevel (video gaming)Basis <Mathematik>LogicSoftware testingPoint (geometry)Physical systemCASE <Informatik>Well-formed formulaWordInheritance (object-oriented programming)SpacetimeState of matterLetterpress printingLecture/ConferenceXML
19:49
Electronic program guideStandard ModelOrder (biology)IntegerMedical imagingEndliche ModelltheorieDifferent (Kate Ryan album)Server (computing)FreewarePerimeterElectronic mailing listResultantCodeSummierbarkeitDifferenz <Mathematik>Computer animationXML
20:35
Demo (music)Content (media)Heat transferInformationData typeInteractive televisionOnline helpPhysical systemExt functorObject (grammar)Default (computer science)Client (computing)Symbolic dynamicsMultiplicationError messageAttribute grammarDifferenz <Mathematik>Mathematical singularityStandard ModelFunctional (mathematics)Computer fileEndliche ModelltheorieFreewareSymbolic dynamicsComputer configurationCartesian coordinate systemCodeDemo (music)Topological algebraClient (computing)Computer programmingComputer animation
22:23
Cartesian coordinate systemNumberSampling (statistics)Topological algebraWeb 2.0Order (biology)Source codeSymbolic dynamicsFunctional (mathematics)IterationStandard ModelDifferent (Kate Ryan album)CodeDynamical systemLecture/Conference
23:07
Bus (computing)Machine codeResultantLogicCodeSoftware bugCommunications protocolClient (computing)Query languageComputer animationLecture/ConferenceXML
23:36
CodeCommunications protocolSoftware bugFunctional (mathematics)Term (mathematics)Different (Kate Ryan album)Topological algebraSymbolic dynamicsLecture/Conference
23:59
Repository (publishing)Proxy serverTopological algebraClient (computing)Product (business)CodeWordProjective planePresentation of a groupSymbolic dynamicsComputer animation
24:54
Type theoryOrder (biology)Attribute grammarCuboidDifferent (Kate Ryan album)Functional (mathematics)Self-organizationRight angleLecture/Conference
Transcript: English(auto-generated)
00:00
Okay, so we're moving on to the last speaker before lunch. So I'm guessing everybody's hungry, but please refrain from just storming the doors two minutes before lunch. Please welcome Kyle Knapp, who's going to talk about dynamic class generation in Python.
00:22
Hello, my name is Kyle Knapp, and I'm a software developer at AWS, where I primarily focus on developing the AWS SDK for Python, also known as Boto3, and the AWS CLI, which is a Python-based command line tool to manage your AWS resources. And I'm going to talk to you today about dynamic class generation in Python.
00:43
So let me first go over what you should expect from this talk. First I'm going to talk to you about the basics of dynamic class generation, mainly what it is and how you'd go about doing it. Then I'll talk about the benefits of doing it and when it is appropriate to use it. And finally I'm going to build an application from the ground up that uses dynamic class
01:02
generation and really shows you all the benefits you gain from it. So let me start by answering the question, what is dynamic class generation? What it is is the pattern is to generate classes at runtime. So all functionality of a specific class or the definition of a specific class doesn't
01:23
exist in the source code, and as a result, it gets generated as the program runs. And because the class doesn't exist in source code, the functionality needs to be generated from somewhere else. In most cases it's some other data source, such as a schema or API model.
01:41
So let me show you a simple example of how you can dynamically generate a class. Here I have a hello world function that takes an object and prints hello world from that object. I also have a class that I call base class that takes an instance name as a parameter and sets it as a property. Now I call type.
02:00
Usually if you're familiar with using type, it usually takes only one argument and tells you the type of object, but here type can also accept three different arguments, where the first argument is the name of my class, and the second is a tuple representing the inheritance I want this new class to have, and the third parameter is a dictionary representing the additional properties I want to add to this class.
02:23
And once I call type, it will actually create my new class such that I can instantiate it as so. Now if I was to introspect some of these variables here, if I look at the other name of my class, it will appropriately show my class. Next if I try to access one of the properties like instance name, it will appropriately
02:41
show up as my instance when I access that property. And also I can call hello world from this instance to get that additional functionality of printing out hello world from this specific object. So you're probably thinking, okay, that's cool, but why should I care about dynamic class generation? First of all, it can improve your workflow.
03:00
Improve your workflow in the sense that you will have to write a minimal amount of Python code to actually add new functionality. Maybe not, you may not even have to write any Python code at all, which is cool. Second it can improve the reliability of your code. A lot of times how dynamic class generation works out is you have a generalized code path and most of your logic flows through that generalized code path.
03:22
As a result it becomes more heavily well used and tested. Then third it can reduce the physical size of your code because specific classes doesn't actually exist in the source code and therefore doesn't take up against the size. And finally this is a production level pattern. I'll go into an application that uses it a little bit later in the talk.
03:42
So now let me talk about some of the downsides of it. First of all, tracebacks are a little bit difficult to follow mainly because a lot of times you're using a generalized source path or source code and as a result you have to look through the data source that you're getting this functionality from to really identify where it's going wrong.
04:03
Secondly, IDE support doesn't come right out of the box mainly because IDEs need the source code to be able to auto suggest and it simply isn't there. So now let me go into a production level application in terms of Boto3. Boto3 is the AWS SDK for Python so it allows you to interact with all the different
04:22
AWS APIs available and Boto3 is dynamic and it's data driven. It's dynamic in the sense that all of its clients and all of its resources are generated at runtime and data driven in the sense that the functionality for these dynamically generated classes are pulled from JSON models representing an AWS API.
04:44
And what those two qualities allow for me as a maintainer of the SDK is an efficient workflow because with AWS it's constantly innovating, constantly adding new features and services so being able to stay up to date and provide all these new features
05:01
through the SDK is very important but it's very difficult when there's a lot of APIs to work with. With Boto3 and the fact that it's dynamic and data driven, all it takes for me is to simply update a new JSON model that's packaged in the library and I don't have to write any Python code at all and I'll immediately be able to take up that functionality and I can spend my time doing some other work that can't be really handled with dynamic
05:24
class generation. So now what I'm going to do is show you a demo of what this would look like. So now what I'm going to do is open up my Python here.
05:41
And now let's say AWS came out of the new service called my service. So if I try to create a client for my service, I will probably get a runtime error saying this is not available. So let's actually go fix this. So what I'm going to do now is let's say we got a new API model for this service
06:02
and I'm going to open up this model to show you what it kind of looks like. And if you look at it right here, it's just Amazon EC2's API model. It's nothing new here. And now what I can use now is the AWS CLI which shares the same data path as Boto3.
06:22
In order to add this model of the same data path that they share. So if I provide for the service model, the file that I just opened, and then I provide a new say I want to rename the service name to my service, what that will do is
06:41
just copy the file into the correct data path for Boto3 to search such that if I open up my Python again, I can then use it. So if I input Boto3 and try to create the new client, it appropriately creates and now
07:02
I can actually just call one of EC2's operations. I'm just going to describe region's call. And it appropriately was able to make the call as well. So this was really cool because I did not have to write a single line of Python code and able to add new functionality. So that being said, you're probably now wondering when should I actually consider
07:24
dynamically generating these classes? The one big point you probably want to look for is if there exists some canonical source of data where you can actually pull functionality from. And better yet, if there's more than one application possibly using this source of data. Classic example is web APIs.
07:41
A lot of times what happens is you have to update a model anyways in order to generate some of the server code. So being able to use that for the client code, you get to pick that up for free, which is awesome. Then you can get in the case of databases where you have a defined schema and libraries kind of like Sandman are able to use that schema to create dynamic APIs to interact
08:02
with those databases. So now let's actually build an application from the ground up that uses dynamic class generation. The application we're going to build is a lightweight Boto3. It involves having a client interface where its methods are one-to-one mappings to
08:21
the various APIs. And this application is going to be all model driven. There's not going to be any code that's specific to a specific API method. It's going to be able to validate inputs based off models. It's going to be able to parse responses based off models. And for now it's only going to support JSON RPC protocol.
08:42
So let me go over the steps real quick as well of what we're going to do. First I'm going to show you how you can write a simple JSON RPC client. Then I'm going to integrate API models to pick up input validation and response parsing. Then I'm going to add API specific methods to this application or the client interface.
09:01
Fourth step, I'm going to make sure it's extensible so I can inject my new classes. And finally I'm going to actually add more APIs. So for those of you not familiar with JSON RPC, let me briefly go over it. So JSON RPC relies on using post against a single URI where there's not any additional paths or query strings you have to worry about.
09:21
And most of the data is in JSON bodies through the request and response. So like in the request you will specify like a method or parameters you want to provide that method. In a response it will return you a result from that method call. So now let me go over some sample code of how this JSON RPC client worked.
09:41
Don't worry about looking through it too deeply right now. I'll walk through it with you. So now if I was to try to instantiate this client with an endpoint URL, it will save the URL for later when I try to make an API call. For the make API call all I have to do is specify a method and parameters such that the method gets mapped to the method element and the JSON payload I will send and the
10:03
parameters will be mapped to the params element. Then once I have that payload set I can just use requests to just send a post to that URL with the JSON document. And what it will return to me is a JSON document as well where you can see here it will the result of multiplying one by two by three together which as you would expect would be
10:24
six. So let's talk about what needs to be expanded on this. This feels very general. Honestly you don't even need this client class right now. You could probably just call a request directly. You don't have any input validation. You have no idea what methods you can use, what parameters you can provide or even the
10:41
types. And also it returns an entire response. So if you notice in the response there is elements talking about what the ID of the request and response was in the version of JSON RPC being used. So now let's talk about step two which is integrating the API models into this client. So currently where we're at we can create a client with an endpoint URL and make a
11:02
generalized API call. By the end of the step what we will be able to do is take a JSON model, load it and have the new client class consume this model such that I can make a model API call which what I will do is be able to know if it's positional or keyword arguments except and appropriately print out just the result I wanted.
11:23
And then similarly if I pass an incorrect parameter type, say like the string foo, I can't multiply one by foo, that will throw me a runtime error saying this is of type string. I expected type integer. So in order to get this working, let's talk about an API model schema. Here is a sample API schema that we are going to use.
11:42
This is a lot simpler than some of the other schemas you might see out there like in 3 uses or something like swagger but for the sake of this presentation, try to keep it simple. So here what I do in the schema is you can identify what the endpoint URL here is for your API and also you can provide the operations that you may want to provide.
12:02
In our case we only have multiply for now and for each operation you can specify an overarching documentation for that method. You can specify what the input is supposed to look like and also what the output is supposed to look like as well. In terms of the inputs, you can say what the type is. In our case it's going to be a list and therefore we want to have it be position
12:22
arguments. And you can also specify how you want to describe this input as well. And you can also model what each element in this list will be. In this case it's integer. And you can do the same for the output in which you get documentation and also specify it's an integer as well.
12:41
So now what we're going to do is actually take this model and then try to run it through the client. Don't worry about trying to read through this right now. I'll go over it as well. So now what we do is if we import the API.json file into a model, we can now have this model client class consume the model. And in initializer what I'll do is I'll create a param validator and a response
13:04
parser which will use the model to both validate and parse responses based off the provided parameters and the API response given back. So now what I'll do is continue on and instantiate the inherited client initializer.
13:20
And then I'll be able to use the make model API call. In which case what I'll first do is try to validate the parameters provided using the validator and the input model. Once we know these parameters are validated, we'll then use the make API call which is inherited from the client class in order to actually make a request against the API.
13:43
Then we'll be able to parse the response based off the model and the response given back to us. As so. Now let's talk about when he's expanded on this step. It still feels too general. Mainly because the API is still completely undocumented. We have no idea what methods we can provide.
14:01
We have no idea what parameters and what the output is going to be. So let's go fix this by actually adding API specific methods by dynamically creating them. So currently what we have is we can open up a model, instantiate a new client and make a model API call. But by the end of the step what we'll be able to do is now use a factory function
14:22
to create a new client that will actually have these new methods that we want on the client. And similarly if we call help on the client, we can also get documentation on what it will actually look like. So let's go over some code on how this would look.
14:40
So once we load the JSON model and create the client, we'll then start initializing some of the variables needed for the type called later down the road. In which case we just call the class name my client and set some class spaces we wanted to inherit from model client in this case. And also we will create an empty dictionary for class properties that we want to dangle
15:00
off that class. So now what we'll do is actually open up the model and look for all the different operations available to us and call this helper function get client method. With the good client method what we'll do is actually define a new function called underscore API call which will be used by the instantiated client.
15:21
So once we return that defined function, we attach it to the class properties that will provide to type which will actually create this new client class and then with this new client class we'll instantiate an instance of it. So if I call multiply of 1, 2, 3, it's actually proxying out to this newly defined API call
15:41
here where self is now referring to the client that the method got attached to and the make model API call which is what's called underneath of this function is inherited from the model client class. But there's one big issue. The doctrineings are still not specific.
16:01
If I try to call help on this client right now you'll just see exactly what I described which is multiply is just a proxy out to this underscore API call. So let's figure out how to do that. It's actually not too difficult. You just add these two lines here which we're setting the dunder name and the dunder doc for the method that we add to the class properties.
16:21
Such that when we call help now we will actually pick up the new documentation and the correct name for the method. So by setting the dunder name right here to whatever the string operation name, it allows you to override the fact that it looks like a proxy when you call help. And by setting the dunder doc you're able to set the documentation for that method.
16:42
The get doc string function is pretty much what it does. It takes the operation model and looks through all the different documentation elements if you remember from before the API schema in order to concatenate together a string that has the operations and the parameter types and all the return types for you.
17:03
So let's talk about what needs to be expanded off this model. First of all, it's not extensible. It's not extensible in the sense that we need to be able to support something like custom class names or custom inheritance in the sense that we don't want to actually only have to rely on these model API methods. We want to be able to add new functionality on top of it.
17:22
So let's talk about step four in which we make the client extensible. Let's start with this sample application where I create a cache client in a sense that we know that given a set of a method and a set of parameters we're going to get the same result every single time. So it doesn't actually make sense to hit the server every single time to do it.
17:41
Why not just store the result in memory and return that when needed? So with this new client class I'll create a new dictionary representing the cache and then I override the make model API calls such that I create a new cache key consisting of the method, the arguments and the keyword arguments that I provide and check to see
18:00
if this key is in the operation cache. And if it is, I'll just print out where I'm retrieving it from and also return the result from the cache. Then if it's not in the cache, what I'll do is I'll actually make a call to the server and get the result and store it for us. So currently where we're at, we have this logic, but there's not really a great way
18:21
to actually hook in our cache client class. There's no option for us right now. So by the end of the step what we're going to be able to do is take this new model and then actually create a client such that we can override what the name is of the class we want to use and its inheritance such that now if I call client.multiply you can see where it's retrieving the result from and then if I call it again it will actually save that result and say it's retrieving it from history.
18:44
And then I can also look at what the name is as well and see that it will return my super smart client as I defined before. So how to do this. It's actually pretty straightforward. Other than updating the signature for this function, I just added these two lines which sets the default if no class bases were provided.
19:02
In order to do that, or if now if I walk through this and I create a new client with the model and the customized string name and the class bases, you can see my super smart client gets mapped to the class name when type is called and the cache client tuple gets mapped to class bases when type is called.
19:23
So now when I call multiply you can see it's appropriately using the cache client and when I call the dunder name it will appropriately use my super smart client for the string name. So let's talk about what needs to be expanded here. To be honest we're actually at a good base.
19:40
There's not really any glaring holes except for the big elephant in the room there's only one API method so let's actually go fix that. So in this final step we're going to add more APIs such that our engineers working on the server end were hard at work and they added two new APIs. Add and subtract.
20:02
So in order to actually update this API they updated the models as well in order to generate some of the server code and as a result we got these new API models for free. And to look at some of these API models you can see that for add it's very similar to multiply in a sense it's a list of integers and what it will return to you is the sum of the integers
20:24
as a single integer. Subtract is also pretty much the same where it's a list of integers and it will return to you the difference of those integers as well as an integer. So now let's actually do a demo of what this will look like. So if I open up IPython right here what I'm going to do is I'm going to import a couple
20:47
helper functions. First I'm going to import one that lets me get my models pretty quickly without having to do the opening up the files and loading it. And I'll import the create client function. So now if I open if I call get model what I'm going to do first is I'm going to open
21:07
up the old model the one that only has the multiply in it. Such that when I create the client now you can see that it only has the three methods.
21:21
It has the generalize make API call it has a model one and also has a multiply. So if I call multiply now it will print out the correct answer of six. But now if I try to call add or something like that I won't be able to do it because the API model is not up to date here. So now let me go fix that and use the new updated API models that I got for free.
21:45
Such that if I create this client again and look at all the different options that I have or methods available to me now you can see that I have add. So if I add one against each other it's going to get me two. And if I subtract on this instead it appropriately shows zero.
22:05
So this is really cool because I didn't have to write any more Python code once our application was built from the ground up and adding new features in the future is not very difficult at all. So now let me get to the kind of conclusion of this talk where dynamic class generation
22:27
I realized my sample application was super simple. It was just adding subtracting multiplying numbers together. There's not really even needs to be a web API for this. But what I want to get at is the fact that we started with an application and kind of built it to the ground up such that in order to add new functionality all it required was
22:44
updating a new model or getting a new model. Hopefully you weren't even the one to have to update this new data source in order to pick up the new functionality. And that's really powerful especially when you have a bunch of different other applications possibly using this similar model such that one update to the model can
23:01
update and different applications that may be consuming it. Dynamic class generation produces really robust code in the end because it's a generalized code path and all your logic is flowing down that. As a result, you get very well tested and heavily used code.
23:21
And one thing I really didn't talk about was features and bug fixes have a much wider spread impact in the sense that right now for the client application I wrote, it only supports JSON RPC protocol. If I was to add something like a rest JSON or rest XML or query protocol, what that opens it up for me is to be able to support a bunch of different APIs that may be only
23:42
running on that protocol. And in terms of bug fixes, if I fix a bug in a certain code path, chances are even though I was targeting for one functionality, if another function is using that same code path, I probably fixed a bug in there too. So I really hope you guys got a lot of ideas about how dynamic class generation
24:05
works, how to use it, where would you use it, and possibly have some ideas on using it in your next big project or even current project today. I would like to really thank you all for coming. If you want to look at some of the presentation code, here is the GitHub repository
24:21
for that. Here is the Boto3 repository if you want to look at the more nitty gritty stuff on resources in dynamic client generation. And also BotoCore, that's where most of the dynamic client generation happens. Boto3 just kind of proxies out to that. So if you want to actually see how clients get created there, I would recommend checking that out.
24:41
But otherwise, I'll be here all week. So if you guys have any questions about Boto3, BotoCore, CLI, or about AWS, come find me. I'll be happy to talk about it. But that's about it. Is there any questions?
25:15
Thank you very much. Could you contrast this technique that you showed us with two other ways of doing
25:24
a similar thing that is using the getat attribute. Okay. And using metaclasses. Yeah, so metaclasses is definitely something you can actually try to use it with. Because in reality, type is just a metaclass, right?
25:41
So you could define your own metaclasses if you want some specific functionality out of it as well. So there's a bunch of different other approaches in order to dynamically generate classes. It's just type is one of them that is kind of built for you right out of the box. Is that good?
26:00
Anyone else? More questions? Okay. And that's lunch. Let's thank Kyle. Thank you.