Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mimic Python's NoneType

I'm creating several classes to use them as state flags (this is more of an exercise, though I'm going to use them in a real project), just like we use None in Python, i.e.

... some_var is None ...

NoneType has several special properties, most importantly it's a singleton, that is there can't be more than one NoneType instance during any interpreter session, and its instances (None objects) are immutable. I've come up with two possible ways to implement somewhat similar behaviour in pure Python and I'm eager to know which one looks better from the architectural standpoint.

1. Don't use instances at all.

The idea is to have a metaclass, that produces immutable classes. The classes are prohibited to have instances.

class FlagMetaClass(type):
    def __setattr__(self, *args, **kwargs):
        raise TypeError("{} class is immutable".format(self))

    def __delattr__(self, *args, **kwargs):
        self.__setattr__()

    def __repr__(self):
        return self.__name__


class BaseFlag(object):
    __metaclass__ = FlagMetaClass

    def __init__(self):
        raise TypeError("Can't create {} instances".format(type(self)))

    def __repr__(self):
        return str(type(self))


class SomeFlag(BaseFlag):
    pass

And we get the desired behaviour

a = BaseFlag
a is BaseFlag  # -> True
a is SomeFlag  # -> False

Obviously any attempt to set attributes on these classes will fail (of course there are several hacks to overcome this, but the direct way is closed). And the classes themselves are unique objects loaded in a namespace.

2. A proper singleton class

class FlagMetaClass(type):
    _instances = {}

    def __call__(cls):
        if cls not in cls._instances:
            cls._instances[cls] = super(FlagMetaClass, cls).__call__()
        return cls._instances[cls] # This may be slightly modified to 
                                   # raise an error instead of returning
                                   # the same object, e.g.
    # def __call__(cls):
    #     if cls in cls._instances:
    #         raise TypeError("Can't have more than one {} instance".format(cls))
    #     cls._instances[cls] = super(FlagMetaClass, cls).__call__()
    #     return cls._instances[cls]

    def __setattr__(self, *args, **kwargs):
        raise TypeError("{} class is immutable".format(self))

    def __delattr__(self, *args, **kwargs):
        self.__setattr__()

    def __repr__(self):
        return self.__name__


class BaseFlag(object):
    __metaclass__ = FlagMetaClass
    __slots__ = []

    def __repr__(self):
        return str(type(self))


class SomeFlag(BaseFlag):
    pass

Here the Flag classes are real singletons. This particular implementation doesn't raise an error when we try to create another instance, but returns the same object (though it's easy to alter this behaviour). Both classes and instances can't be directly modified. The point is to create an instance of each class upon import like it's done with None.

Both approaches give me somewhat immutable unique objects that can be used for comparison just like the None. To me the second one looks more NoneType-like, since None is an instance, but I'm not sure that it's worth the increase in idealogical complexity. Looking forward to hear from you.

like image 859
Eli Korvigo Avatar asked Nov 10 '22 07:11

Eli Korvigo


1 Answers

Theoretically, it's an interesting exercise. But when you say "though I'm going to use them in a real project" then you lose me.

If the real project is highly unPythonic (using traits or some other package to emulate static typing, using __slots__ to keep people from falling on sharp objects, etc.) -- well, I've got nothing for you, because I've got no use for that, but others do.

If the real project is Pythonic, then do the simplest thing possible.

Your "not use instances at all" answer is the correct one here, but you don't need to do a lot of class definition, either.

For example, if you have a function that could accept None as a real parameter, and you want to tell if the parameter has been defaulted, then just do this:

class NoParameterGiven:
    pass

def my_function(my_parameter=NoParameterGiven):
    if my_parameter is NoParameterGiven:
        <do all my default stuff>

That class is so cheap, there's no reason even to share it between files. Just create it where you need it.

Your state classes are a different story, and you might want to use something like that enum module that @Dunes mentioned -- it has some nice features.

OTOH, if you want to keep it really simple, you could just do something like this:

class MyStates:
    class State1: pass
    class State2: pass
    class State3  pass

You don't need to instantiate anything, and you can refer to them like this: MyStates.State1.

like image 100
Patrick Maupin Avatar answered Nov 25 '22 06:11

Patrick Maupin