Namespaces inside class in Python3
Asked Answered
M

3

11

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.

Miltiades answered 13/8, 2017 at 17:52 Comment(6)
Do these actually need to be methods? Do they need access to any state? Could you just have nested modules?Nichrome
Do the methods like do_smth need access to the instance, like regular methods?Bindweed
I store login information as attributes of instance. I need access to them from every method. Maybe there is a better way to store them.Miltiades
Interesting concept. I guess you could fake it by doing self.subspace = self.another_subspace = self in the __init__ method. Of course, that's not creating separate namespaces, although it does allow the calling syntax you describe. OTOH, it doesn't stop people from doing smth.do_smth().Brandt
wouldn't Something.__init__(... self.subclass = Subclass(owner=self), self.another_subclass = AnotherSubclass(owner=self..., kinda achieve this? where the subclasses could lookup whatever info they need from their owner and your namespace would fit your intent. Subclass would have a do_smth method.Kraft
@JL Peyret I thought about it. I'll check later as I am AFK for a little while.Miltiades
H
4

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.

Hanse answered 13/8, 2017 at 18:43 Comment(6)
This is a very elegant solution and works as intended. Thanks.Miltiades
This works, but each time you access smth.subspace or smth.another_subspace a new inner class object is created. You can see that by printing other_self in the methods. OTOH, I guess it's no worse than the fact that instance methods normally get re-bound on every call. But if those inner classes contain lots of methods it will slow things down having to re-build them every time.Brandt
@mDevv: I disagree. As PM points out, this creates a new class object each time. At least move those classes out of the method. Next, you'd have to worry about sharing state too. I'd instead look into refactoring your API to not need namespaces?Drouin
As mentioned, you could make them singletons if performance is an issue, but for most applications it really should not.Hanse
when the solution involves new class instantiation, I believe it adds unnecessary complexityEmma
New Instantiation will cause hard-to-debug problems. I would advice against using this solution.Bode
M
1

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.

Mandamus answered 1/4, 2021 at 9:8 Comment(0)
G
0

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
Granger answered 7/1 at 17:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.