Flagging classes as not intended for direct initialization

Steven D'Aprano steve+comp.lang.python at pearwood.info
Sun Feb 15 20:50:53 EST 2015


Mario Figueiredo wrote:

> Hello everyone,
> 
> [Python 3.X]
> 
> I have the following factory model for the initialization of a class
> tree (code abbreviated for simplicity).
> 
> # item.py
> class BadItemType(Exception):
>     pass

If this is a type error, why aren't you using TypeError? Or at least
inheriting from TypeError?

class BadItemType(TypeError):
    pass



> class Item:
>     def __init__(self, _data):
> 
> class Container(Item):
>     def __init__(self, _data):
>         Item.__init__(self, _data)
>         # ...
> 
> class Tool(Item):
>     def __init__(self, _data):
>         Item.__init__(self, _data)
>         # ...

Unless you have good reason not to, you should use super rather than
directly call the superclass.

https://rhettinger.wordpress.com/2011/05/26/super-considered-super/


 
> def spawn(type_, id_):
>     if type_ not in Item.__subclasses__():
>         raise BadItemType()
>     # ...
>     return type_(data)
> 
> I'd like to know your opinions on an acceptable way to flag the Item
> class and its derived classes so users of this module know they should
> avoid instantiating them directly.
> 
> Other than the obvious notes on the classes docstrings, is it good
> enough I mark the constructor argument as an implementation variable as
> I did?


Oh, so you did. No, that's not good enough. I never even noticed that, and
if I had noticed it, I would have guessed that you had some bizarre naming
scheme and didn't understand the Python underscore conventions. I never
would have guessed that you meant "don't instantiate this class".

Are users supposed to subclass Item themselves? If so, you should make Item
an ABC (Abstract Base Class):

class ItemABC:
    def __init__(self, data):
        if type(self) is ItemABC:
            raise TypeError("You must subclass ItemABC")


But if users aren't supposed to use the classes *at all*, but only use the
spawn factory function, you don't have a lot of good choices in pure Python
code. Python isn't designed to be that restrictive.

The *simplest thing which will work* is simply not worry about it. You
should consider the factory function to be merely for convenience. It might
be *really* convenient, but if the user wants to ignore the factory and
reinvent the wheel, why should you care? Just document the fact that people
are expected to use the factory, but don't expressly prohibit the use of
the classes directly.

Otherwise, you can flag the classes with single underscores to mark them as
private implementation details:

class _Item:
    ...


then declare "consenting adults". If your users decide to instantiate the
classes directly, you can't really stop them, so if they blow their foot
off they have nobody to blame but themselves.

If you want to be a little more strict, you can use a little obfuscation to
discourage direct use. Note that in this case you *must* inherit from
object in Python 2. In Python 3 it doesn't matter.


# Untested
class Item(object):
    def __init__(self):
        raise TypeError('do not instantiate')

    @classmethod
    def _my_secret_constructor(cls, data):
        obj = super(Item, cls).__new__(cls)
        obj.data = data
        return obj


def spawn(type_, id_):
    # error checking as before
    return type_._my_secret_constructor(data)


but of course even here if your users *really* want to instantiate the
class, they can do so.



-- 
Steven




More information about the Python-list mailing list