<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: GabrielCappelli</title>
    <description>The latest articles on DEV Community by GabrielCappelli (@gabrielcappelli).</description>
    <link>https://dev.to/gabrielcappelli</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1129624%2Fe58c22f5-ccf7-4a14-9276-6ccb701c96d5.png</url>
      <title>DEV Community: GabrielCappelli</title>
      <link>https://dev.to/gabrielcappelli</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gabrielcappelli"/>
    <language>en</language>
    <item>
      <title>IoC Container in Python using meta programming</title>
      <dc:creator>GabrielCappelli</dc:creator>
      <pubDate>Mon, 31 Jul 2023 00:54:29 +0000</pubDate>
      <link>https://dev.to/gabrielcappelli/ioc-container-in-python-using-meta-programming-3e16</link>
      <guid>https://dev.to/gabrielcappelli/ioc-container-in-python-using-meta-programming-3e16</guid>
      <description>&lt;p&gt;Often while working with python I miss having a DI framework (sadly this is not very common in the python world). There are multiple blog posts on the benefits of DI, so I won't go into that here.&lt;/p&gt;

&lt;p&gt;Either way, I was on the lookout for a DI framework for python and this one caught my eye: &lt;a href="https://github.com/ets-labs/python-dependency-injector"&gt;Dependency Injector&lt;/a&gt;. Mainly because of this listed feature:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Performance. Fast. Written in Cython."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Which peaked my interest, how much overhead does a DI framework add to your code? So I wrote a &lt;a href="https://github.com/GabrielCappelli/meta-di/tree/main/benchmark"&gt;small benchmark&lt;/a&gt; to test this.&lt;/p&gt;

&lt;p&gt;For the benchmark we are requesting the containers to give us an instance of &lt;code&gt;FooService&lt;/code&gt;, with the following dependency tree, with Cache, HTTPClient and Config being singletons and the rest being transient:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FooService
    - Config
    - Database
        - Config
    - Cache
        - Config
    - HttpClient
        - Config
    - BarService
        - Database
            - Config
        - Cache
            - Config
        - HTTPClient
            - Config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And these were the results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python3.11 benchmark/run_benchmark.py 
Direct (No framework) 563.216755 ns
Dependency Injector Container (Cython) 1183.705037 ns
Rodi Container (Pure Python) 1983.972198 ns
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok, so compared to directly instantiating the objects ourselves it seems a pure python container is ~4x slower and the cython implementation is ~2x slower.&lt;/p&gt;

&lt;p&gt;Which makes sense, since a normal container has some overhead to get a service:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check if service is a singleton or transient&lt;/li&gt;
&lt;li&gt;If its a singleton check if its already instantiated&lt;/li&gt;
&lt;li&gt;Find the dependencies for the service and get them (recursively)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Meta programming to the rescue
&lt;/h2&gt;

&lt;p&gt;So how can we make this faster?&lt;/p&gt;

&lt;p&gt;After a few failed attempts using closures and meta classes I had the idea to use meta programming and generate the code for the container at runtime. If we already know all services beforehand, we can generate a class with specialized methods for each service we need to instantiate.&lt;/p&gt;

&lt;p&gt;Here's the optimizations we can perform on each methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No need to check if the service is a singleton or transient&lt;/li&gt;
&lt;li&gt;No need to find the dependencies for the service at runtime&lt;/li&gt;
&lt;li&gt;Inlining the code to instantiate the dependencies

&lt;ul&gt;
&lt;li&gt;This saves us the overhead of recursively calling the container to get the dependencies&lt;/li&gt;
&lt;li&gt;Preloading singletons also avoids the need to check if they are already instantiated&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's a snippet of the generated code for the &lt;code&gt;FooService&lt;/code&gt; method with all optimizations mentioned above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;service_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="c1"&gt;# to provide a clean interface we still have this method as overhead
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_service_getter_map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;service_id&lt;/span&gt;&lt;span class="p"&gt;]()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_services_FooService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FooService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_singleton_instances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;# singleton instance is preloaded and inlined
&lt;/span&gt;            &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="c1"&gt;# transient service is inlined
&lt;/span&gt;                &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_singleton_instances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_singleton_instances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cache&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;http_client&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_singleton_instances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPClient&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;bar_service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BarService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_singleton_instances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_singleton_instances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_singleton_instances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cache&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="n"&gt;http_client&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_singleton_instances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPClient&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;So, did it work? Here's the results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python3.11 benchmark/run_benchmark.py 
Direct 563.216755 ns
MetaDI Container 792.128003 ns
Dependency Injector Container (Cython) 1183.705037 ns
Rodi Container 1983.972198 ns
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which are pretty good! We managed to be faster than the cython implementation while using pure python.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Even though I believe this performance gain is irrelevant for most applications, it was a fun experiment and I learned a lot about meta programming. I also think this is a good example of how meta programming can be used to optimize code, and I will definitely keep this in mind for future projects.&lt;/p&gt;

&lt;p&gt;Source code can be found on &lt;a href="https://github.com/GabrielCappelli/meta-di"&gt;Github&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
    </item>
  </channel>
</rss>
