We're sorry but this page doesn't work properly without JavaScript enabled. Please enable it to continue.
Feedback

Dynamic Class Generation in Python

00:00

Formal Metadata

Title
Dynamic Class Generation in Python
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
Publisher
Release Date
Language

Content Metadata

Subject Area
Genre
Abstract
Kyle Knapp - Dynamic Class Generation in Python This talk is about dynamic class generation in python: the practice of writing code that generates classes and their functionality at runtime. It will use boto3, the AWS SDK for Python, as a basis to dive into the basics, the benefits, and the drawbacks to dynamically generating classes. ----- This talk is about the concept of dynamic class generation in python. The whole idea is writing code that generates classes and their functionality at runtime. You now may be asking yourself, “That sounds like a neat trick. Why would I ever generate my classes at runtime?” Here are a few reasons why: - It can decrease the physical size of your code. - It can improve the workflow in adding new functionality. - It can improve reliability of your code. One example where the power of this concept has really been leveraged is in boto3, the AWS SDK for Python. Dynamic class generation has allowed boto3 to become heavily data driven such that most of its classes and methods are generated based off JSON models representing aspects of an AWS service’s API. For example, to add support for a new AWS service API in boto3, just plop in a JSON file into the library with no additional Python code required. Using lessons and techniques drawn from developing boto3, this talk will dive into the following topics related to dynamic class generation: - The basics of dynamic class generation such as how to effectively dynamically generate classes. - How to overcome some of the challenges of dynamic class generation. - The tradeoffs in dynamically generating classes and discussion on when it is appropriate.
Symbolic dynamicsSoftware developerTopological algebraDynamical systemUniverse (mathematics)Video gameRight angleSystem administratorAreaLecture/Conference
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
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
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
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
BuildingWeightTopological algebraSymbolic dynamicsCartesian coordinate systemStandard ModeloutputCodeClient (computing)InjektivitätLatent heatDependent and independent variablesInterface (computing)Communications protocolEndliche ModelltheorieInferenceHypermediaComputer animationXML
Client (computing)Real numberCartesian coordinate systemValidity (statistics)Interface (computing)INTEGRALDependent and independent variablesLatent heatExtension (kinesiology)ParsingEndliche ModelltheorieoutputCASE <Informatik>Menu (computing)Point (geometry)Computer animation
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
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
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
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
Electronic program guideStandard ModelOrder (biology)IntegerMedical imagingEndliche ModelltheorieDifferent (Kate Ryan album)Server (computing)FreewarePerimeterElectronic mailing listResultantCodeSummierbarkeitDifferenz <Mathematik>Computer animationXML
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
Cartesian coordinate systemNumberSampling (statistics)Topological algebraWeb 2.0Order (biology)Source codeSymbolic dynamicsFunctional (mathematics)IterationStandard ModelDifferent (Kate Ryan album)CodeDynamical systemLecture/Conference
Bus (computing)Machine codeResultantLogicCodeSoftware bugCommunications protocolClient (computing)Query languageComputer animationLecture/ConferenceXML
CodeCommunications protocolSoftware bugFunctional (mathematics)Term (mathematics)Different (Kate Ryan album)Topological algebraSymbolic dynamicsLecture/Conference
Repository (publishing)Proxy serverTopological algebraClient (computing)Product (business)CodeWordProjective planePresentation of a groupSymbolic dynamicsComputer animation
Type theoryOrder (biology)Attribute grammarCuboidDifferent (Kate Ryan album)Functional (mathematics)Self-organizationRight angleLecture/Conference
Transcript: English(auto-generated)
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.
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.
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
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
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.
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.
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.
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
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.
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.
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.
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.
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
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.
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
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
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.
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
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.
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
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
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
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.
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
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
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.
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.
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.
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.
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
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
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
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
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.
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.
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.
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
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.
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
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.
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.
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.
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
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.
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
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.
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
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.
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.
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.
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.
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.
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.
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
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
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.
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.
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.
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.
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.
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
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
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
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.
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.
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.
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
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
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
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.
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
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
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
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.
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?
Thank you very much. Could you contrast this technique that you showed us with two other ways of doing
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?
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?
Anyone else? More questions? Okay. And that's lunch. Let's thank Kyle. Thank you.