Jwan622
Jwan622

Reputation: 11639

How to initialize a class in python, not an instance

I have a class like this:

class Metrics:
    meters = {}

    for subscription in SUBSCRIPTION_TYPES:
        meters[subscription] = metrics.new_meter(subscription)

    @staticmethod
    def get_meter_data(field, key):
        return Metrics.meters[field].get()[key]

    @staticmethod
    def track(subscription, value):
        Metrics.meters[subscription].notify(value)

        gauge_for_passport.set_function(lambda: Metrics.meters[subscription].get()["count"])

basically, I only want the meters dictionary to be initialized once since it's going to be keeping track of certain events in my app so I have the for loop right in the class body. Is this the right way to do this?

should I also use a classmethod so I can use cls and pass the class itself to the method instead of calling Metrics directly?

Upvotes: 2

Views: 56

Answers (1)

Rick
Rick

Reputation: 45241

Classes are "initialized" at the point in time the module is run. So when you write this:

# my_module.py

class Metrics:
    meters = {}

    for subscription in SUBSCRIPTION_TYPES:
        meters[subscription] = metrics.new_meter(subscription)

...the for loop executes when my_module is imported.

It sounds to me like what you really want is a module, not a class. There is only one instance of any given module.

# metrics.py module

meters = {}

for subscription in SUBSCRIPTION_TYPES:
    meters[subscription] = new_meter(subscription)

def get_meter_data(field, key):
    return meters[field].get()[key]

def track(subscription, value):
    meters[subscription].notify(value)

    gauge_for_passport.set_function(lambda: meters[subscription].get()["count"])

When you want it "instantiated", you just import it:

# other_module.py

import metrics

If you really don't want this module in a separate file, there are ways of dynamically creating a module inside of another module. But honestly, it probably isn't worth the trouble, and I'm not even sure how you would write the module but also delay its execution.

One other option to delay class creation might be to write a class factory, which is a pythonic approach and might make sense depending on your use case:

def get_metrics():

    class Metrics:
        meters = {}

        for subscription in SUBSCRIPTION_TYPES:
            meters[subscription] = metrics.new_meter(subscription)

        @staticmethod
        def get_meter_data(field, key):
            return Metrics.meters[field].get()[key]

        @staticmethod
        def track(subscription, value):
            Metrics.meters[subscription].notify(value)

            gauge_for_passport.set_function(lambda: Metrics.meters[subscription].get()["count"])

    return Metrics

In this way, you can control when the class is run. But your example code really doesn't look like much of a class to me; it looks like a bunch of tools that belong in a namespace together.


Let's Get a Little Ridiculous

Back to the idea of dynamic module creation: you could pair these two ideas together and do something like this:

from types import ModuleType

def init_metrics_module():
    """This is a function to delay creation of the module below."""

    meters = {}

    for subscription in SUBSCRIPTION_TYPES:
        meters[subscription] = new_meter(subscription)

    def get_meter_data(field, key):
        return meters[field].get()[key]

    def track(subscription, value):
        meters[subscription].notify(value)

        gauge_for_passport.set_function(lambda: meters[subscription].get()["count"])


    # create module
    module_dict = locals().copy()
    metrics = ModuleType("metrics")
    metrics.__dict__.update(module_dict)
    return metrics

Then when you want it:

metrics = init_metrics_module()
type(metrics)  # <class 'module'>

Upvotes: 3

Related Questions