patternpythonMinor
Python 3 class constructor
Viewed 0 times
classpythonconstructor
Problem
I have implemented a metaclass in Python 3 that, apart from the usual instance constructor (i.e.
I've written up some tests for it, they pass, and the metaclass also works in practice for me, but I'd like to know:
Implementation:
Tests:
```
import unittest
class MyTypeTests(unittest.TestCase):
""" Unit tests for MyType metaclass. """
def test_class_constructor(self):
""" Is the class constructor invoked? """
class WithoutInit(metaclass=MyType): # pylint: disable=R0903
""" Dummy class. """
initialized = False
class AbstractWithoutInit(metaclass=MyType): # pylint: disable=R0903
""" Dummy abstract class. """
initialized = False
@abc.abstractmethod
def spam(self):
""" Dummy abstract method. """
pass
class WithInit(metaclass=MyType): # pylint: disable=R0903
""" Dummy
__init__), enables you to define a class constructor (as a class method that I called __init_class__). The metaclass extends abc.ABCMeta so it supports abstract classes, i.e. the class constructor is not called if a class is abstract.I've written up some tests for it, they pass, and the metaclass also works in practice for me, but I'd like to know:
- Is this even a good approach for adding a class constructor functionality?
- Are there any problematic corner-cases that I might run into with my code?
- Am I testing it correctly?
- Any comments about my code being (or not) pythonic?
Implementation:
import abc
import inspect
class MyType(abc.ABCMeta):
"""
Metaclass.
It adds extra functionality of an optional class constructor, which might by added
to the class by adding a class method __init_class__(cls) to the class definition.
Class constructor takes no arguments.
"""
def __init__(cls, name, bases, nmspc):
super().__init__(name, bases, nmspc)
cls.__has_init_class__ = hasattr(cls, '__init_class__')
if cls.__has_init_class__ and not inspect.isabstract(cls):
cls.__init_class__()Tests:
```
import unittest
class MyTypeTests(unittest.TestCase):
""" Unit tests for MyType metaclass. """
def test_class_constructor(self):
""" Is the class constructor invoked? """
class WithoutInit(metaclass=MyType): # pylint: disable=R0903
""" Dummy class. """
initialized = False
class AbstractWithoutInit(metaclass=MyType): # pylint: disable=R0903
""" Dummy abstract class. """
initialized = False
@abc.abstractmethod
def spam(self):
""" Dummy abstract method. """
pass
class WithInit(metaclass=MyType): # pylint: disable=R0903
""" Dummy
Solution
- Any comments about my code being (or not) pythonic?
It's 100% not pythonic code. If we go to PEP8 -- Descriptive: Naming Styles we'll see:
__double_leading_and_trailing_underscore__: "magic" objects or attributes that live in user-controlled namespaces.E.g.
__init__, __import__ or __file__. Never invent such names; only use them as documented.Yes never create these.
I can give you one example for why this is in place.
Say a novice to Python comes across this magic attribute and method that you have created.
They don't know what it does, they've not come across a magic method that has not been documented in the Python docs.
But they have yours.
They laboriously search the Python docs, but just can't find it.
They then don't use your code, as they don't know what it does.
Now, I maybe being a foolish hobgoblin, and so sure your new 'init' could possible be a magic method.
But I'd still argue that
__has_init_class__ should defiantly not be.Instead that could be
_has_init_class.I'd argue that
_has_init_class is somewhat pointless and you should just use hasattr(cls, '__init_class__').You also don't test this, and so it seems to me that you don't use it.
In short it may be some nice sugar, but you probably don't need or use it.
I don't think
__init_class__ is a good name.It makes me think you'll be running it near
__init__,which from your tests in definitely not the case.
You may say, you'll use
__new_class__ instead.No,
__new__ works together with __init__ to construct objects, and so would shift confusion.I would name it more closely to how you are using it.
From what I know I'd call it
_nonabstract_meta_constructor.That's a bit long for my liking so I'd just use
_meta_const.And so I'd use:
class MyType(abc.ABCMeta):
"""
Metaclass.
It adds extra functionality of an optional class constructor, which might by added
to the class by adding a class method __init_class__(cls) to the class definition.
Class constructor takes no arguments.
"""
def __init__(cls, name, bases, nmspc):
super().__init__(name, bases, nmspc)
if hasattr(cls, '_meta_const') and not inspect.isabstract(cls):
cls._meta_const()Ok it's not "100% not pythonic code". If you remove the magic methods, your code has no glairing PEP8 errors.
You probably don't need all the docstrings on the tests, and your
MyType docstring should be changed to follow PEP257.You should have a summery line, and then your multi-line docstring as you have it.
But it's good. Apart from the magic I'd have no, non-petty, reason to complain.
Code Snippets
class MyType(abc.ABCMeta):
"""
Metaclass.
It adds extra functionality of an optional class constructor, which might by added
to the class by adding a class method __init_class__(cls) to the class definition.
Class constructor takes no arguments.
"""
def __init__(cls, name, bases, nmspc):
super().__init__(name, bases, nmspc)
if hasattr(cls, '_meta_const') and not inspect.isabstract(cls):
cls._meta_const()Context
StackExchange Code Review Q#129073, answer score: 3
Revisions (0)
No revisions yet.