The world’s leading publication for data science, AI, and ML professionals.

The .🐍 Extension is Now a Thing in Python ?

Transform your .py files into .🐍 modules

created by @carbon_app
created by @carbon_app

For years now, Python developers have secretly wished they could ditch .py extension to embrace something more… reptilian 🐍 . And while Mojo gets to rock the cool .🔥 extension, Python has been stuck in the Emoji dark ages.

But guess what? What if I told you that 2025 could be the year of the emoji uprising? While CPython may not officially support emoji file extensions (yet!), the beauty of Python’s import system means we can extend it to handle whatever wild and wonderful file extensions our hearts desire.

In this article, I’ll show you how to extend Python’s Import system and finally give your scripts the emoji flair they deserve. Let’s dive in! 🚀


1. Installing emojdule 🐍 ✨

Want to import Python files with emoji extensions? It’s easier than you think! Just install the emojdule package:

pip install emojdule

# Or poetry
poetry add emojdule

# Or uv
uv add emojdule
uv pip install emojdule

And that’s it! 🎉 emojdule automatically hooks into Python’s import system, letting you import files with emoji extensions like it’s no big deal.

💡 In a hurry? Skip to Part 3 for a quick example!

⚠️ Warning: This is purely for fun, please don’t use this in production (unless you enjoy confusing your future self). 😅


2. Extending the Python Import System 🛠 ️

Python’s import system is surprisingly flexible and emojdule takes full advantage of this by providing a custom importer. But before diving in, let’s break down how Python imports work.

🔍 The Import Machinery

Python’s import system is built around a few key components:

  • Finders: A finder locates a module (e.g., searching the file system for a .py file).
  • Loaders: A loader executes the module’s code and creates the corresponding Module object.
  • Meta Path: A list of finder objects that Python uses to locate modules.

By default, Python comes with three built-in finders:

Image by author
Image by author

Here’s what they do:

  • BuiltinImporter: Loads built-in modules (e.g., sys , math ).
  • FrozenImporter: Loads frozen modules (precompiled bytecode embedded into Python).
  • PathFinder: The main finder, responsible for searching import paths and loading your .py files.

Since PathFinder handles standard module imports, we can extend Python’s import system by adding a custom Finder to sys.meta_path .


3. Creating a Custom Import Hook 🎩

Now that we understand how Python’s import system works, let’s build our own Finder and Loader to intercept imports and modify module behavior!

🕵 ️ Creating a Finder

A Finder is responsible for locating modules. To create one, we define a class with a find_spec method:

  • Returns a ModuleSpec object if it finds the module.
  • Returns None if it doesn’t find it, it will allow Python to move to the next import hook.

Let’s implement a custom finder that intercepts imports of a specific module:

import importlib.abc
import importlib.util
from importlib.machinery import PathFinder

class CustomFinder(importlib.abc.MetaPathFinder):
    def __init__(self, intercepted_module) -> None:
        self.intercepted_module = intercepted_module
        self.finder = PathFinder()
        super().__init__()

    def find_spec(self, fullname, path, target=None):
        if not fullname.startswith(self.intercepted_module): 
            return None  # Move to the next finder

        print(f"Intercepting import: {fullname}")
        spec = self.finder.find_spec(fullname, path, target)
        if spec is None:
            return None
        spec.loader = CustomLoader()
        return spec

Now, any attempt to import a module matching intercepted_module will be intercepted by our custom Finder.

⚡Creating a Loader

A Loader is responsible for executing the module code. Our Finder referenced a CustomLoader , so let’s create that next:

To implement a Loader, we need:

  • create_module(self, spec) : Instantiates a new module object.
  • exec_module(self, module) : Executes the module’s code.

Here’s our custom loader in action:

class CustomLoader(importlib.abc.Loader):
    def create_module(self, spec):
        return None  # Use default module creation

    def exec_module(self, module):
        print(f"Executing module: {module.__name__}")

        # Read the module's source code
        with open(module.__spec__.origin, "r") as f:
            source_code = f.read()

        # Modify the source code (add logging)
        modified_code = 'print("Custom Hook: Module Loaded!")n' + source_code

        # Execute the modified code within the module's namespace
        exec(modified_code, module.__dict__)

Now, when a module is imported, our loader:

  1. Reads its source code.
  2. Modifies it (in this case, adding a log message).
  3. Executes the modified code.

🛠 ️Installing the Custom Hook

We have our Finder and Loader, but Python won’t use them until we register them in sys.meta_path :

Image by author
Image by author
import sys

# Register the custom finder to intercept 'json' imports
sys.meta_path.insert(0, CustomFinder("json"))

# Import the target module (should trigger the hook)
import json

🏆 Output

When you import json , here’s what happens:

Intercepting import: json
Executing module: json
Custom Hook: Module Loaded!
Intercepting import: json.decoder
Executing module: json.decoder
Custom Hook: Module Loaded!
Intercepting import: json.scanner
Executing module: json.scanner
Custom Hook: Module Loaded!
Intercepting import: json.encoder
Executing module: json.encoder
Custom Hook: Module Loaded!

Since json has submodules ( decoder , scanner , encoder ), our Finder intercepts all of them too !

Our CustomFinder hooks into the import system, intercepting modules before the default importers get a chance.


4. Example Repository 🎨

Let’s see emojdule in action! 🚀

📁 Setting Up Your Emoji Module

  1. Create a new directory for your module:
mkdir my_emoji_module
cd my_emoji_module
  1. Add a Python file with an emoji extension:

Create a file called my_module.🐍 and add the following code:

def hello_world():
    print("Hello, World!")

🐍 Importing the Emoji Module

Now, with emojdule installed, you can import your emoji-named module just like a regular Python file:

import my_module

my_module.hello_world()

🏆 Output

Hello, World!

Yep, that’s right – Python is now emoji-compatible (well, kind of 😉 ).

🔗 Check out the full example code here: GitHub Repo


5. Another Type of Import Hook 🔍

In the previous section, we explored meta hooks, but there’s another powerful tool in Python’s import system:

🏗 ️ Import Path Hooks

Instead of modifying sys.meta_path , import path hooks extend PathFinder to search for modules in custom locations.

Normally, PathFinder searches for modules in directories listed in sys.path . But what if sys.path contained a URL instead? Or a database query? Or even links to a PyPI?

🛠 ️ Writing a Custom Import Path Hook

To create an import path hook:

  1. Define a custom "entity" finder (to handle non-traditional paths).
  2. Register it in sys.path_hooks so PathFinder knows about it.

For more details, check out the official Python docs.

🗺 ️ How Import Path Hooks Fit into Python

Image by author
Image by author

Here’s what’s happening:

  • sys.path_hooks contains custom import finders.
  • PathFinder uses these hooks to locate modules.

🐍 How emojdule Uses Import Path Hooks

Under the hood, emojdule leverages import path hooks to recognize .🐍 files. Here’s its full implementation:

import sys
from importlib.machinery import (
    SOURCE_SUFFIXES,
    FileFinder,
    EXTENSION_SUFFIXES,
    ExtensionFileLoader,
    SourceFileLoader,
    BYTECODE_SUFFIXES,
    SourcelessFileLoader,
)

def install() -> None:
    supported_loaders = [
        (ExtensionFileLoader, [*EXTENSION_SUFFIXES]),
        (SourceFileLoader, [*SOURCE_SUFFIXES, ".🐍  "]),  # Add support for .🐍   files!
        (SourcelessFileLoader, BYTECODE_SUFFIXES),
    ]

    # Register the custom FileFinder in sys.path_hooks
    sys.path_hooks.insert(1, FileFinder.path_hook(*supported_loaders))

    # Clear importer cache so changes take effect
    sys.path_importer_cache.clear()

Conclusion 🎯

We’ve taken a deep dive into tweaking Python’s import system, exploring how to import modules with emoji extensions using the emojdule package. Along the way, we:

Built a custom importer. ✅ Explored meta hooks & import path hooks to extend Python’s import system. ✅ Created a fun example to showcase it in action.

Want to dive even deeper? Check out the official Python documentation for more import system magic!

Thank you for sticking this far. Stay safe and see you in the following story!

Other Posts you may like

These Methods Will Change How You Use Pandas

This Decorator will Make Python 30 Times Faster


Related Articles