GeeVooL
GeeVooL

Reputation: 172

Namespaces inside class in Python3

I am new to Python and I wonder if there is any way to aggregate methods into 'subspaces'. I mean something similar to this syntax:

smth = Something()
smth.subspace.do_smth()
smth.another_subspace.do_smth_else()

I am writing an API wrapper and I'm going to have a lot of very similar methods (only different URI) so I though it would be good to place them in a few subspaces that refer to the API requests categories. In other words, I want to create namespaces inside a class. I don't know if this is even possible in Python and have know idea what to look for in Google.

I will appreciate any help.

Upvotes: 11

Views: 2825

Answers (3)

RexBarker
RexBarker

Reputation: 1801

A simple way to do it is with SimpleNamespace:

from types import SimpleNamespace

class Something:
    @staticmethod
    def _did_something(input):
        print(input)

    subspace = SimpleNamespace(
        my_var="stuff",
        do_something=_did_something
    )

In simple use:

>>> f = Something()
>>> f.subspace.my_var
stuff

>>> f.subspace.do_something("hello")
hello

In this case, items which do not require reference to internal class variables can be used with a @staticmethod definition, and will operate without instantiation as well:

>>> Something.subspace.my_var
stuff

>>> Something.subspace.do_something("goodbye")
goodbye

For more complex operations, where a reference to another value is needed, the class requires instantiation such that the internal parameters can be set:

from types import SimpleNamespace

class Something:
    def __init__(self):
        # must be assigned after class instantiation
        self.other_subspace.tell_secrets = self._other_func

    @staticmethod
    def _did_something(input):
        print(input)

    subspace = SimpleNamespace(
        my_var="stuff",
        do_something=_did_something
    )

    def _other_func(self,input):
        print(self.other_subspace.secret + " and " + input)

    other_subspace = SimpleNamespace(
        secret="mayonaise",
        sauce="duck",
        tell_secrets=None  #placeholder, set after instantiation
    )

In use, the values can then be referenced after object creation:

>>> f = Something()
>>> f.other_subspace.sauce
duck

>>> f.other_subspace.tell_secrets("ketchup")
mayonaise and ketchup

Upvotes: 0

Kiran Jonnalagadda
Kiran Jonnalagadda

Reputation: 2826

I had this need a couple years ago and came up with this:

class Registry:
    """Namespace within a class."""

    def __get__(self, obj, cls=None):
        if obj is None:
            return self
        else:
            return InstanceRegistry(self, obj)

    def __call__(self, name=None):

        def decorator(f):
            use_name = name or f.__name__
            if hasattr(self, use_name):
                raise ValueError("%s is already registered" % use_name)
            setattr(self, name or f.__name__, f)
            return f

        return decorator


class InstanceRegistry:
    """
    Helper for accessing a namespace from an instance of the class.

    Used internally by :class:`Registry`. Returns a partial that will pass
    the instance as the first parameter.
    """

    def __init__(self, registry, obj):
        self.__registry = registry
        self.__obj = obj

    def __getattr__(self, attr):
        return partial(getattr(self.__registry, attr), self.__obj)


# Usage:

class Something:
    subspace = Registry()
    another_subspace = Registry()

@MyClass.subspace()
def do_smth(self):
    # `self` will be an instance of Something
    pass

@MyClass.another_subspace('do_smth_else')
def this_can_be_called_anything_and_take_any_parameter_name(obj, other):
    # Call it `obj` or whatever else if `self` outside a class is unsettling
    pass

At runtime:

>>> smth = Something()
>>> smth.subspace.do_smth()
>>> smth.another_subspace.do_smth_else('other')

This is compatible with Py2 and Py3. Some performance optimizations are possible in Py3 because __set_name__ tells us what the namespace is called and allows caching the instance registry.

Upvotes: 1

Jonas Adler
Jonas Adler

Reputation: 10810

One way to do this is by defining subspace and another_subspace as properties that return objects that provide do_smth and do_smth_else respectively:

class Something:
    @property
    def subspace(self):
        class SubSpaceClass:
            def do_smth(other_self):
                print('do_smth')
        return SubSpaceClass()

    @property
    def another_subspace(self):
        class AnotherSubSpaceClass:
            def do_smth_else(other_self):
                print('do_smth_else')
        return AnotherSubSpaceClass()

Which does what you want:

>>> smth = Something()
>>> smth.subspace.do_smth()
do_smth
>>> smth.another_subspace.do_smth_else()
do_smth_else

Depending on what you intend to use the methods for, you may want to make SubSpaceClass a singleton, but i doubt the performance gain is worth it.

Upvotes: 4

Related Questions