I have renamed a python class that is part of a library. I am willing to leave a possibility to use its previous name for some time but would like to warn user that it's deprecated and will be removed in the future.
I think that to provide backward compatibility it will be enough to use an alias like that:
class NewClsName:
pass
OldClsName = NewClsName
I have no idea how to mark the OldClsName
as deprecated in an elegant way. Maybe I could make OldClsName
a function which emits a warning (to logs) and constructs the NewClsName
object from its parameters (using *args
and **kvargs
) but it doesn't seem elegant enough (or maybe it is?).
However, I don't know how Python standard library deprecation warnings work. I imagine that there may be some nice magic to deal with deprecation, e.g. allowing treating it as errors or silencing depending on some interpreter's command line option.
The question is: How to warn users about using an obsolete class alias (or obsolete class in general).
EDIT: The function approach doesn't work for me (I already gave it a try) because the class has some class methods (factory methods) which can't be called when the OldClsName
is defined as a function. Following code won't work:
class NewClsName(object):
@classmethod
def CreateVariant1( cls, ... ):
pass
@classmethod
def CreateVariant2( cls, ... ):
pass
def OldClsName(*args, **kwargs):
warnings.warn("The 'OldClsName' class was renamed [...]",
DeprecationWarning )
return NewClsName(*args, **kwargs)
OldClsName.CreateVariant1( ... )
Because of:
AttributeError: 'function' object has no attribute 'CreateVariant1'
Is inheritance my only option? To be honest, it doesn't look very clean to me - it affects class hierarchy through introduction of unnecessary derivation. Additionally, OldClsName is not NewClsName
what is not an issue in most cases but may be a problem in case of poorly written code using the library.
I could also create a dummy, unrelated OldClsName
class and implement a constructor as well as wrappers for all class methods in it, but it is even worse solution, in my opinion.
Yup, I think that's pretty standard practice:
The only tricky thing is if you have things that subclass from
OldClsName
- then we have to get clever. If you just need to keep access to class methods, this should do it:I haven't tested it, but that should give you the idea -
__call__
will handle the normal-instantation route,__getattr__
will capture accesses to the class methods & still generate the warning, without messing with your class heirarchy.Why don't you just sub-class? This way no user code should be broken.
Here is the list of requirements a solution should satisfy:
isinstance
andissubclass
checksSolution
This can be achieved with a custom metaclass:
Explanation
DeprecatedClassMeta.__new__
method is called not only for a class it is a metaclass of but also for every subclass of this class. That gives an opportunity to ensure that no instance ofDeprecatedClass
will ever be instantiated or subclassed.Instantiation is simple. The metaclass overrides the
__new__
method ofDeprecatedClass
to always return an instance ofNewClass
.Subclassing is not much harder.
DeprecatedClassMeta.__new__
receives a list of base classes and needs to replace instances ofDeprecatedClass
withNewClass
.Finally, the
isinstance
andissubclass
checks are implemented via__instancecheck__
and__subclasscheck__
defined in PEP 3119.Test
Please have a look at
warnings.warn
.As you'll see, the example in the documentation is a deprecation warning:
Use
inspect
module to add placeholder forOldClass
, thenOldClsName is NewClsName
check will pass, and a linter like pylint will inform this as error.deprecate.py
test.py
then run
python -W all test.py
:In python >= 3.6 you can easily handle warning on subclassing:
Overloading
__new__
should allow you to warn when the old class constructor is called directly, but I haven't tested that since I don't need it right now.