How to write proper decorator to alter classes - python

I know I can use closure and inheritance to create a decorator that alter classes.
def wrapper(cls, *args, **kwargs):
class Wrapped(cls):
"""Modify your class here."""
return Wrapped
But if I need to test my new classes to know if they inherit Wrapped or not, I can't access Wrapped itself to do a straightforward isinstance or issubclass test.
On the other hand, straightforward inheritance isn't an option. I have about 10 different wrapper which can need to be added to a class. That burden the hierarchy tree way too much.
So I need a way to access the closure from the outside. Or an alternative way to build decorator.

It sounds like you want to check whether a class has been wrapped by this particular decorator. The most efficacious method to do so may simply be to add a field to that effect, to wit:
def wrapper(cls, *args, **kwargs):
class Wrapped(cls):
"""Modify your class here."""
Wrapped._is_wrapped_by_this_wrapper = True
return Wrapped
Then you can check hasattr and getattr of _is_wrapped_by_this_wrapper.
If you have multiple wrapper classes that work with each other you may be able to come up with a solution that works better together, e.g. perhaps a set consisting of all the names of the wrappers that have been applied.

You could inherit from two classes, a base class and cls:
class WrapperBase:
pass
def wrapper(cls, *args, **kwargs):
class Wrapped(cls, WrapperBase):
"""Modify your class here."""
return Wrapped
Now all instances of generated classes test True for isinstance(obj, WrapperBase).
Note that WrapperBase has no impact on finding inherited methods in the MRO; it comes dead last in any hierarchy (on Python 2, not inheriting from object puts it dead last in the MRO, in Python 3 it'll sit between object and whatever came before object in the MRO of the wrapped class.

Related

Python class design with metaclass

I have a class design where the Children classes inheriting from a certain Parent class just differ in some parameters, but the Parent class contains all methods, which are using the parameters provided as class variables on the Children. So, in other words, each of my Child classes is fully described by the list of parameters and the inheritance of the Parent class.
So, let's say, I have the following classes:
class Parent():
def __init__(self, **kwargs):
for param in self.__class__.parameters:
self.setattr(param, kwargs.get(param))
def compare(self, other):
for param in self.__class__.parameters:
if self.getattr(param) != other.getattr(param):
return False
return True
class ChildA(Parent):
parameters = ["length", "height", "width"]
def __init__(self, **kwargs):
super().__init__(**kwargs)
class ChildB(Parent):
parameters = ["color", "taste"]
def __init__(self, **kwargs):
super().__init__(**kwargs)
My actual classes are a bit different - I have more and more complex methods on the Parent class and also different kinds of parameters - , but this is sort of a minimum example of the design principle.
Since Parent class is relying on its Children to have the class variable parameters, I thought, I might want to enforce the existence of the class variable on each Child class. I have read that I achieve this by using a metaclass. But I have also read that most developers do not need to use metaclasses, and if in doubt, you probably don't need them. I have never worked with metaclasses before, and so I am in doubt whether I should use them, and so by that rule mentioned, I probably do not need a metaclass. But on the other hand, the term "metaclass" just sounds like a good match to my structure, since Parent really looks like something which could well be called "metaclass" in some sense (technically, not in terms of the way the terminus technicus metaclass is used in OOP, but in terms of: it is fully describing the behaviour of the children classes).
So, I wonder: Is there a different (better) design of classes to reflect my structure? Should I use a metaclass to enforce the existence of the parameters, or is there a better way to do so? Or should I just resign to enforce the existence of the parameters class variable on the Children classes in the first place?
If using python3.6 or above, you can accomplish this using __init_subclass__ which I personally reason better with than a metaclass.
An example of __init_subclass__ based on the usecase described:
class Parent:
def __init_subclass__(cls):
if not hasattr(cls, 'parameters'):
raise TypeError(f'Subclass of {cls} does not have a parameters class attribute')
def __init__(self, **kwargs):
for param in self.__class__.parameters:
self.setattr(param, kwargs.get(param))
def compare(self, other):
for param in self.__class__.parameters:
if self.getattr(param) != other.getattr(param):
return False
return True
class GoodChild(Parent):
parameters = ['length', 'height', 'width']
class BadChild(Parent):
pass
Which results in raising a TypeError exception when the BadChild class is created (not when it is instantiated):
TypeError: Subclass of <class '__main__.BadChild'> does not have a parameters class attribute

How to inherit from a class instantiated with a builder?

I have a class Document, this class is really complex to instantiate so I have a builder object to create them. Both elements are not mine, so I can't change them
Now, I want to create a subclass of Document, just to add some specific methods. In order to keep using the provided builder I tried this:
class SpecialDocument(Document):
def __new__(cls, *args):
return DocumentBuilder(*args)
def __init__(self, *args, **kwargs):
#My initialization
The problem here is that the __init__ method never executes cause the __new__ method doesn't return a SpecialDocument (It returns a Document)
In my particular case I don't need to build my SpecialDocument differently from how I build a Document. Is there a way to use the same builder? If not, how can I achieve this? I just want to inherit from Document to add particular functionalities, maybe it could be achieved with metaclasses but I never used them (Probably cause I don't fully understand it), a little insight on them would be nice if it can help solving my problem
You don't actually need a metaclass here - you just have to proper call the superclass' __new__ method. The way you are doing it, the instantiation of the superclass does not "know" it is being called from a subclass at all.
So, just write your code like this instead:
class SpecialDocument(Document):
def __new__(cls, *args):
return super().__new__(cls, *args)
def __init__(self, *args, **kwargs):
#My initialization
Now, that is the ordinary way to do it - and would work if the code in your "builder" function was correctly placed inside Docment's __new__ or __init__.
Since the code there does nt do that, and you can[ t pass your subclass as a parameter to the builder, a working solution might be to create a normal document, and swap its class after it has been built:
def special_document_init(special_document):
...
class SpecialDocument(Document):
def my_special_method(self, ...):
...
def overriden_method(self):
...
result = super().overriden_method()
...
def build_special_document(*args):
document = DocumentBuilder(*args)
document.__class__ = SpecialDocument
special_document_init(document)
return document

How do I override base class methods in Python conditionally on whether they exist?

I have a classes BasicEvidenceTarget and SchedulableSoma. Sometimes I inherit from SchedulableSoma, BasicEvidenceTarget, sometimes I inherit from SchedulableSoma alone. When I inherit from SchedulableSoma, BasicEvidenceTarget, I want SchedulableSoma to override the method BasicEvidenceTarget.inject_basic_evidence. What's a nice way of doing that?
The override looks like this:
class SchedulableSoma(SchedulableCluster, Soma):
# This is a possible overload of this method in BasicEvidenceTarget.
def inject_basic_evidence(self, *args, **kwargs):
super().inject_basic_evidence(*args, **kwargs)
self.ask_for_reschedule()
Right now I am unconditionally overriding the base class method, and so if it doesn't exist, there is a bit of method pollution: if the override is called, the call super will fail. It would be nicer to conditionally generate the override.
I feel like there is a __prepare_subclass__ magic that might work, but I'm not sure exactly how to do it.
You could use a factory function to generate an appropriate class at the time you define the class that is inheriting it. This is just a sketch:
def makeSubclass(otherclass):
class MixedClass(otherclass):
if otherClass == BasicEvidenceTarget:
def inject_basic_evidence(...):
# ...
elif otherClass == WhateverOtherClass:
def some_other_method(...):
# ...
Then you would do:
class SchedulableSoma(makeSubclass(BasicEvidenceTarget)):
# ...
Another possibility is simply to define all the methods in SchedulableSoma, but include checks in them that raise exceptions if the current instance does not inherit from the appropriate class. Something like:
class SchedulableSoma(object):
def inject_basic_evidence(self, *args, **kwargs):
if not isinstance(self, BasicEvidenceTarget):
raise TypeError("Cannot call inject_basic_evidence unless you inherit from BasicEvidenceTarget")
def some_other_method(self, *args, **kwargs):
if not isinstance(self, SomeOtherClass):
raise TypeError("Cannot call some_other_method unless you inherit from SomeOtherClass")
# similar checks for other classes
This way the call to inject_basic_evidence will fail right away with a more specific error message, rather than failing on the super call with a more obscure message about "super object has no attribute" or the like.
Ultimately you might want to think about whether there is a more robust way to structure your class hierarchy. It is somewhat magical to have classes alter their own behavior depending on whether certain other classes also appear in the inheritance hierarchy, and it may confuse users or lead to unforeseen interactions among the classes down the road.
What you attempting to do is impossible at class definition time without funcy metaclass business. This could be done at instance creation time with the following code:
class SchedulableSoma(SchedulableCluster, Soma):
def __init__(self,*args,**kwargs):
super().__init__(self,*args,**kwargs)
if hasattr(self,"inject_basic_evidence"):
def inject_basic_evidence(*args, **kwargs):
super().inject_basic_evidence(*args, **kwargs)
self.ask_for_reschedule()
self.inject_basic_evidence = inject_basic_evidence
NOTE: This does not work if a subclass of SchedulableSoma overrides inject_basic_evidence
I think that this problem could be solved by different inheritence schema. Using your idea:
class YourClass(SchedulableSoma, BasicEvidenceTarget):
def inject_basic_evidence(self, *args, **kwargs):
if isinstance(self, BasicEvidenceTarget):
BasicEvidenceTarget.inject_basic_evidence(self, *args, **kwargs)
else:
super().inject_basic_evidence(*args, **kwargs)
self.ask_for_reschedule()
Or
class YourClass(SchedulableSoma, BasicEvidenceTarget):
if issubclass(YourClass, BasicEvidenceTarget):
def inject_basic_evidence(self, *args, **kwargs):
super().inject_basic_evidence(*args, **kwargs)
self.ask_for_reschedule()

Is there a nice way to express the opposite of an "override specifier" in Python?

C++11 added the override specifier, which is a promise that a method overrides a parent class's method. I would like to express the opposite of this, namely that a method is not implemented by any of the parent classes. Can I express that without metaclasses, for example with a decorator?
This is what I'm currently doing
class EchoSoma(Soma):
def __init__(self, **kwargs):
super().__init__(**kwargs)
assert not hasattr(super(), 'inject_basic_evidence')
def inject_basic_evidence(self, basic_in):
super().fire(basic_in)
No, you can't do that without a metaclass.
Code executing inside a class block has no knowledge of the superclass(es), and "this class" doesn't exist yet. You need some pre- or post-processing which can only be provided by a metaclass. Alternatively, you'd need to pass the superclass(es) to the decorator, which would need to reconstruct the MRO, most likely by building a temporary class and checking its __mro__ attribute. This is messier than just writing the metaclass you're trying to avoid.
Checking in the __init__ is not good enough, because that's only done when you instantiate the class, not when the class is initially created.
The metaclass solution looks something like this:
class NoOverrideMeta(type):
def __new__(mcs, name, bases, dct, no_override=None):
if no_override is None:
no_override = []
cls = super().__new__(name, bases, dct)
for meth_name in no_override:
assert not hasattr(super(cls, cls), meth_name)
return cls
class EchoSoma(Soma, metaclass=NoOverideMeta, no_override=['inject_basic_evidence']):
def inject_basic_evidence(self, basic_in):
super().fire(basic_in)
This example passes the method names by keyword argument, a new feature in 3.x. Decorators would be cleaner but a bit more complex; you would iterate over dct looking for decorated methods.

Python metaclasses vs class decorators

What are the main differences between Python metaclasses and class decorators? Is there something I can do with one but not with the other?
Decorators are much, much simpler and more limited -- and therefore should be preferred whenever the desired effect can be achieved with either a metaclass or a class decorator.
Anything you can do with a class decorator, you can of course do with a custom metaclass (just apply the functionality of the "decorator function", i.e., the one that takes a class object and modifies it, in the course of the metaclass's __new__ or __init__ that make the class object!-).
There are many things you can do in a custom metaclass but not in a decorator (unless the decorator internally generates and applies a custom metaclass, of course -- but that's cheating;-)... and even then, in Python 3, there are things you can only do with a custom metaclass, not after the fact... but that's a pretty advanced sub-niche of your question, so let me give simpler examples).
For example, suppose you want to make a class object X such that print X (or in Python 3 print(X) of course;-) displays peekaboo!. You cannot possibly do that without a custom metaclass, because the metaclass's override of __str__ is the crucial actor here, i.e., you need a def __str__(cls): return "peekaboo!" in the custom metaclass of class X.
The same applies to all magic methods, i.e., to all kinds of operations as applied to the class object itself (as opposed to, ones applied to its instances, which use magic methods as defined in the class -- operations on the class object itself use magic methods as defined in the metaclass).
As given in the chapter 21 of the book 'fluent python', one difference is related to inheritance. Please see these two scripts. The python version is 3.5. One point is that the use of metaclass affects its children while the decorator affects only the current class.
The script use class-decorator to replace/overwirte the method 'func1'.
def deco4cls(cls):
cls.func1 = lambda self: 2
return cls
#deco4cls
class Cls1:
pass
class Cls1_1(Cls1):
def func1(self):
return 3
obj1_1 = Cls1_1()
print(obj1_1.func1()) # 3
The script use metaclass to replace/overwrite the method 'func1'.
class Deco4cls(type):
def __init__(cls, name, bases, attr_dict):
# print(cls, name, bases, attr_dict)
super().__init__(name, bases, attr_dict)
cls.func1 = lambda self: 2
class Cls2(metaclass=Deco4cls):
pass
class Cls2_1(Cls2):
def func1(self):
return 3
obj2_1 = Cls2_1()
print(obj2_1.func1()) # 2!! the original Cls2_1.func1 is replaced by metaclass

Categories