I have child classes which inherit some basic functionality from a parent class.
The child classes shall have a generic constructor prepare_and_connect_constructor() which does some magic around the object creation of the parent class.
For simplicity, the magic is done by a simple function based decorator (ultimately, it should be a part of the parent class).
def decorate_with_some_magic(func):
def prepare_and_connect(*args, **kwargs):
print("prepare something")
print("create an object")
obj = func(*args, **kwargs)
print("connect obj to something")
return obj
return prepare_and_connect
class Parent:
def __init__(self, a):
self.a = a
def __repr__(self):
return f"{self.a}"
class Child(Parent):
#classmethod
#decorate_with_some_magic
def prepare_and_connect_constructor(cls, a, b):
""" use the generic connection decorator right on object creation """
obj = super().__init__(a)
# put some more specific attributes (over the parents class)
obj.b = b
return obj
def __init__(self, a, b):
""" init without connecting """
super().__init__(a)
self.b = b
def __repr__(self):
return f"{self.a}, {self.b}"
if __name__ == '__main__':
print(Child.prepare_and_connect_constructor("special child", "needs some help"))
Using this code i finally get
obj = super().__init__(a)
TypeError: __init__() missing 1 required positional argument: 'a'
when running prepare_and_connect_constructor().
Actually I would expect that the super.__init__(a) call should be the same as in Child.__init__.
I guess the reason is related to the classmethod but I can't figure it out.
What's wrong with this call?
Update: In general what was wrong is that __init__ doesn't return an object.
Due to the hints and thoughts from the answers i modified my code to achieve what i need:
class Parent:
def __init__(self, a):
self.a = a
#staticmethod
def decorate_with_some_magic(func):
def prepare_and_connect(*args, **kwargs):
print("prepare something")
print("create an object")
obj = func(*args, **kwargs)
print("connect obj to something")
return obj
return prepare_and_connect
def __repr__(self):
return f"{self.a}"
class ChildWithOneName(Parent):
#classmethod
#Parent.decorate_with_some_magic
def prepare_and_connect_constructor(cls, a, b):
""" use the generic connection decorator right on object creation """
obj = super().__new__(cls)
obj.__init__(a, b)
print("Does the same as in it's __init__ method")
return obj
def __init__(self, a, b):
""" init without connecting """
super().__init__(a)
self.b = b
def __repr__(self):
return f"{self.a}, {self.b}"
class GodChild(Parent):
#classmethod
#Parent.decorate_with_some_magic
def prepare_and_connect_constructor(cls, a, names):
""" use the generic connection decorator right on object creation """
obj = super().__new__(cls)
obj.__init__(a, names)
# perform some more specific operations
obj.register_all_names(names)
print("And does some more stuff than in it's __init__ method")
return obj
def __init__(self, a, already_verified_names):
""" init without connecting """
super().__init__(a)
self.verified_names = already_verified_names
def register_all_names(self, names=[]):
self.verified_names = []
def verify(text):
return True
for name in names:
if verify(name):
self.verified_names.append(name)
def __repr__(self):
return f"{self.a}, {self.verified_names}"
if __name__ == '__main__':
print(ChildWithOneName.prepare_and_connect_constructor("special child", "needs some help"), end='\n\n')
print(GodChild.prepare_and_connect_constructor("unknown child", "needs some verification"), end='\n\n')
print(ChildWithOneName("my child", "is clean and doesn't need extra magic"))
decorate_with_some_magic is now a part of the Parent class (using a staticmethod) as it is a related generic functionality
Each child class (added one more for illustration) has it's own prepare_and_connect_constructor classmethod, which calls its own constructor and does optionally some additional work
You have a slight misunderstanding of the magic methods __init__ and __new__. __new__ creates a new object, e.g. returns a instance of the class. __init__ just modifies the object in place. So an easy fix for your problem would be de following:
#classmethod
#decorate_with_some_magic
def prepare_and_connect_constructor(cls, a, b):
""" use the generic connection decorator right on object creation """
obj = super().__new__(cls)
obj.__init__(a)
# put some more specific attributes (over the parents class)
obj.b = b
return obj
I however don't think you should use it like this. Instead, your probably should overwrite __new__
Decorators work on callables. Since there is no difference between calling a function and initiating a class, you can use your decorator directly on the class:
def decorate_with_some_magic(func):
def prepare_and_connect(*args, **kwargs):
print("prepare something")
print("create an object")
obj = func(*args, **kwargs)
print("connect obj to something")
return obj
return prepare_and_connect
class Parent:
#classmethod
def prepare_and_connect_constructor(cls, a, b):
return decorate_with_some_magic(cls)(a, b)
def __init__(self, a):
self.a = a
def __repr__(self):
return f"{self.a}"
class Child(Parent):
def __init__(self, a, b):
""" init without connecting """
super().__init__(a)
self.b = b
def __repr__(self):
return f"{self.a}, {self.b}"
if __name__ == '__main__':
normal_child = Child("normal child", "no help needed")
print(normal_child)
special_child = Child.prepare_and_connect_constructor("special child", "needs some help")
print(special_child)
Output:
normal child, no help needed
prepare something
create an object
connect obj to something
special child, needs some help
Related
I have a class Stuff that has several methods, some of which have some argument, let's call it argument_x. For example:
class Stuff:
def method_1(self, argument_x, **other_args):
pass
def method_2(self, argument_x, **other_args):
pass
def method_3(self, I_dont_have_argument_x):
pass
Now I want to subclass this class wrapping all methods that have argument_x in the same way. For example if I were to proceed by hand I would do:
class StuffWithConstantX(Stuff):
def __init__(self, argument_x_value):
super().__init__()
self._argument_x_value = argument_x_value
def method_1(self, **other_args):
super().method_1(argument_x=self._argument_x_value, **other_args)
def method_2(self, **other_args):
super().method_2(argument_x=self._argument_x_value, **other_args)
As method_3 does not have argument_x I leave it unchanged.
Is it possible to automate this? How?
Here's how you might define this as a wrapper, rather than a subclass:
class Stuff:
def method_1(self, argument_x, **other_args):
print("method 1:", argument_x)
def method_2(self, argument_x, **other_args):
print("method 2:", argument_x)
def method_3(self, i_dont_have_argument_x):
print("method 3:", i_dont_have_argument_x)
class StuffWithConstantX:
def __init__(self, argument_x_value) -> None:
self._stuff = Stuff()
self._argument_x = argument_x_value
def __getattr__(self, __name: str):
attr = getattr(self._stuff, __name)
if not callable(attr):
return attr
def wrapped(*args, **kwargs):
try:
return attr(argument_x=self._argument_x, *args, **kwargs)
except TypeError:
# Beware -- if there's a TypeError raised from attr itself,
# it will get run twice before the caller sees the exception.
# You can potentially work around this by closely inspecting
# either the exception or the attr object itself.
return attr(*args, **kwargs)
return wrapped
stuff = StuffWithConstantX("foo")
stuff.method_1()
stuff.method_2()
stuff.method_3("bar")
method 1: foo
method 2: foo
method 3: bar
As noted in the comments, this code is more or less impossible to statically typecheck, and I would not recommend actually using this pattern unless you have a really good reason.
Here's another way you could do it.
import inspect
import functools
class StuffWithConstantX(Stuff):
def __init__(self, argument_x_value):
super().__init__()
self._argument_x_value = argument_x_value
for func_name, func in inspect.getmembers(Stuff, inspect.isfunction):
arg_names = inspect.getfullargspec(func).args
if 'argument_x' in arg_names:
setattr(self, func_name, functools.partial(func, self=self, argument_x=self._argument_x_value))
How can I decorate the last function in a class inheritance?
If I decorate a superclass function, the subclass function overrides the decorator.
I'd like to find out if there is a neat way to automatically decorate the top function in the MRO.
def wrapper(f):
def _wrap(*args, **kwargs):
print("In wrapper")
return f(*args, **kwargs)
return _wrap
class A:
#wrapper
def f(self):
print("In class A")
class B(A):
def f(self):
print("In class B")
if __name__ == '__main__':
a = A()
b = B()
print("Calling A:")
a.f()
print("Calling B:")
b.f()
Here is the output. As expected, B.f() does not call the wrapper, though I'd like it to.
Calling A:
In wrapper
In class A
Calling B:
In class B
Here is what I have tried thus far. A metaclass that holds all the decorators and injects them during class instantiation.
from abc import ABCMeta
class WrapperMetaClass(ABCMeta):
def __init__(cls, *args, **kwargs):
wrappers_dict = getattr(cls, "_wrappers")
for attr_name in dir(cls):
if attr_name not in wrappers_dict:
continue
else:
wrapper = wrappers_dict[attr_name]
attr = getattr(cls, attr_name)
if not hasattr(attr, '__call__'):
raise Exception("What you're trying to wrap is not a function!")
attr = wrapper(attr)
setattr(cls, attr_name, attr)
super().__init__(*args, **kwargs)
This works:
class A(metaclass=WrapperMetaClass):
_wrappers = {
"f": wrapper
}
def f(self):
print("In class A")
class B(A):
def f(self):
print("In class B")
The output is what I wanted.
Calling A:
In wrapper
In class A
Calling B:
In wrapper
In class B
However, this runs into a different issue. If B does not override f, the metaclass wraps A.f() twice. This makes sense, as both A and B inherit WrapperMetaClass, so A.f() is wrapped first, and then B.f() is wrapped again.
class A(metaclass=WrapperMetaClass):
_wrappers = {
"f": wrapper
}
def f(self):
print("In class A")
class B(A):
pass
The output becomes:
Calling A:
In wrapper
In class A
Calling B:
In wrapper
In wrapper
In class A
I have no idea what else I could do.
Yes, I remember facing this once or twice - and you are on the right track.
But first things first: if the logic in your "wrapper" is something that
could be put in a method in the base class, then breaking-up the methods
in smaller-tasks, and have a "method slot" system is preferable to this,
as user 2357112 supports monica puts in the comments. If you find out you really need or prefer decorators, the full code is bellow
class A:
def do_things(self):
create_connection() # <- this is the code you'd are putting in the wrapper in the other approach
do_thing_1()
class B(A):
def do_things(self):
# here we have to do thing_1 and thing_2, but
# the connection is created in the superclass method...
# this could be the origin of your question
# Refactor to:
class A:
def do_things(self):
create_connection()
self.internal_do_things()
def internal_do_things(self):
do_thing_1()
class B(A):
def internal_do_things(self):
super().internal_do_things()
do_thing_2()
So, classical inheritance and OO solves this
If you need the decorators anway:
The thing to do is to have the decorator itself, the "wrapper", get
a way to "know" if it already was called in an outer method (i.e. a method
in a subclass which calls super()), and just act as a transparent
wrapper in this case.
It gets a bit further complicated when we want a robust solution:
a wrapper that can work for different methods in the same class,
and does not get confused if they are called concurrently
(in different threads, or a method calling another method,
not super(), which should trigger the wrapper).
And in the end, the mechanisms for that are complicated enough that
they should not get in the way of your actual wrapper - so,
ideally they should be built as a decorator themselves, which will
decorate your wrapper.
[hours later]
So, sorry if it does not look "neat" - it turns out implementing what is described above is a bit more complex than I thought initially - we need an intermediate decorator level (called meta_wrapper_applier in the code), so that the metaclass can re-wrap the methods each time they are redeclared.
I hope the comments in the code and variable names are enough to understand the idea:
from abc import ABCMeta
from functools import wraps
import threading
class WrapperMetaClass(ABCMeta):
def __init__(cls, name, bases, ns, **kw):
super().__init__(name, bases, ns, **kw)
# Get the wrapped methods for all the superclasses
super_wrappers = {}
for supercls in cls.__mro__[::-1]:
super_wrappers.update(supercls.__dict__.get("_wrappers", {}))
# unconditionally install a wrappers dict for each subclass:
sub_wrappers = cls._wrappers = {}
for attrname, attr in ns.items():
if attrname in super_wrappers:
# Applies the wrapper in the baseclass to the subclass method:
setattr(cls, attrname, super_wrappers[attrname]._run_once_wrapper(attr))
elif hasattr(attr, "_run_once_wrapper"):
# Store the wrapper information in the cls for use of the subclasses:
sub_wrappers[attrname] = attr
def run_once_method_decorator(original_wrapper):
re_entering_stacks = {}
# This is the callable used to place a wrapper on the original
# method and on each overriden method.
# All methods with the same name in the subclasses will share the same original wrapper and the
# "re_entering_stacks" data structure.
def meta_wrapper_applier(raw_method):
wrapped_method_in_subclass = None
#wraps(original_wrapper)
def meta_wrapper(*args, **kw):
nonlocal wrapped_method_in_subclass
# uses a plain list to keep track of re-entering the same-named method
# in each thread:
re_entering_stack = re_entering_stacks.setdefault(threading.current_thread(), [])
re_entering = bool(re_entering_stack)
try:
re_entering_stack.append(1)
if re_entering:
result = raw_method(*args, **kw)
else:
if wrapped_method_in_subclass is None:
# Applies the original decorator lazily, and caches the result:
wrapped_method_in_subclass = original_wrapper(raw_method)
result = wrapped_method_in_subclass(*args, **kw)
finally:
re_entering_stack.pop()
return result
# registry = original_wrapper.__dict__.setdefault("_run_once_registry", {})
meta_wrapper._run_once_wrapper = meta_wrapper_applier
return meta_wrapper
return meta_wrapper_applier
# From here on, example code only;
#run_once_method_decorator
def wrapper(f):
#wraps(f)
def _wrap(*args, **kwargs):
print("Entering wrapper")
result = f(*args, **kwargs)
print("Leaving wrapper\n")
return result
return _wrap
#run_once_method_decorator
def other_wrapper(f):
#wraps(f)
def _wrap(*args, **kwargs):
print("Entering other wrapper")
result = f(*args, **kwargs)
print("Leaving other wrapper\n")
return result
return _wrap
class A(metaclass=WrapperMetaClass):
#wrapper
def f(self):
print("In class A")
def g(self):
print("g in A")
class B(A):
def f(self):
print("In class B")
super().f()
#other_wrapper
def g(self):
print("g in B")
super().g()
class C(B):
def g(self):
print("g in C")
super().g()
if __name__ == '__main__':
a = A()
b = B()
print("Calling A:")
a.f()
a.g()
print("Calling B:")
b.f()
b.g()
print("Calling C:")
C().g()
Output:
Calling A:
Entering wrapper
In class A
Leaving wrapper
g in A
Calling B:
Entering wrapper
In class B
In class A
Leaving wrapper
Entering other wrapper
g in B
g in A
Leaving other wrapper
Calling C:
Entering other wrapper
g in C
g in B
g in A
Leaving other wrapper
I suppose I'm missing something obvious, but I can't get the name of methods when I'm using decorators. When I run this code, I get the error:
AttributeError: 'str' object has no attribute "__name__"
Could somebody tell me how I can get the name of these decorated method?
Thanks
def Print(*arg, **kwarg):
func, *arguments = arg
print(func.__name__ + "(): {}".format(func=arguments[0]))
class Bob(object):
def __init__(self):
pass
#property
def stuff(self):
return "value from stuff property"
#stuff.setter
def stuff(self, noise):
return noise
class Tester:
def __init__(self):
self.dylan = Bob()
def randomTest(self):
Print(self.dylan.stuff, 1)
if __name__ == "__main__":
whatever = Tester()
whatever.randomTest()
stuff isn't a function or a method; it's a property. The syntax
#property
def stuff(...):
...
creates an instance of the property class using stuff as the argument to property, equivalent to
def stuff(...):
....
stuff = property(stuff)
and instances of property don't have a __name__ attribute, as you've seen.
(It's a little trickier with the setter, since the function and the property have to have the same name. But defining stuff a "second" time doesn't override the existing property named stuff.)
The individual methods are accessed via attributes of the property.
>>> Bob.stuff.fget.__name__
'stuff'
>>> Bob.stuff.fset.__name__
'stuff'
Note another, longer, way to create the same property:
class Bob:
def stuff_getter(self):
...
def stuff_setter(self, noise):
...
stuff = property(stuff_getter, stuff_setter)
del stuff_getter, stuff_setter # Clean up the namespace
def Print(*arg, **kwarg):
func, *arguments = arg
print(func.__name__ + "(): {}".format(func=arguments[0]))
class Bob():
def __init__(self, s):
self.stuff = s
#property
def myStuff(self):
return self.stuff
#myStuff.setter
def setStuff(self, noise):
self.stuff = noise
class Tester:
def __init__(self):
self.dylan = Bob(1)
def randomTest(self):
print(self.dylan.stuff)
if __name__ == "__main__":
whatever = Tester()
whatever.randomTest()
This should work :)
I have the following two classes A and B. How do I make the do_someting() method call the overriden method, some_method(), in B. Is this doable in Python?
class A:
#staticmethod
def some_method()
# pass
return
#classmethod
def do_something():
A.some_method()
...
return
class B(A):
#staticmethod
def some_method()
# how does do_something call here?
return
#classmethod
def run()
B.do_something()
return
It's pretty simple, just make sure to fix your colons pass in self and cls:
class A:
#staticmethod
def some_method():
# pass
return
#classmethod
def do_something(cls):
cls.some_method()
return
class B(A):
#staticmethod
def some_method():
print("I did stuff!")
return
#classmethod
def run(cls):
B.do_something()
return
k = B()
k.run()
>>>"I did stuff!"
And if you want to call the old do_something (the one in class A) from class B, just pass in the appropriate class. In class B:
#classmethod
def run(cls):
A.do_something()
return
I am trying to implement a custom logger with a decorator that would collect exceptions (to save them to db later) in the following way:
import functools
class Log:
def __init__(self):
self.mssg = ""
self.err = ""
class Parent:
def __init__(self):
self.logger = Log()
def logging(fun):
#functools.wraps(fun)
def inner(*args):
try:
print(fun.__name__)
self.logger.mssg += fun.__name__ +" :ok, "
return fun(*args)
except Exception as e:
self.logger.err += fun.__name__ +": error: "+str(e.args)
return inner
logging = staticmethod(logging)
class Child(Parent):
def __init__(self, a, b):
self.a = a
self.b = b
#Parent.logging
def sum_(self):
return self.a + self.b
However it seems that the decorator "break" the link between the method and the instance, as it cannot use self anymore... when running
c = Child(3,6)
c.sum_()
I receive an error message self is not defined I also tried various combination to pass self.loggeras an argument to the function, but I am a bit confused, and they failed... Anyone have an idea that could solve my problem?
There were a couple of problems with your code. Look at the comments.
import functools
class Log:
def __init__(self):
self.mssg = ""
self.err = ""
class Parent(object):
def __init__(self):
self.logger = Log()
#staticmethod #You can directly use staticmethod decorator!
def logging(fun):
#functools.wraps(fun)
def inner(*args):
self = args[0] #Grab the first arg as self.
try:
print(fun.__name__)
self.logger.mssg += fun.__name__ +" :ok, "
return fun(self, *(args[1:])) # Call the function using
# self that we extracted.
except Exception as e:
self.logger.err += fun.__name__ +": error: "+str(e.args)
return inner
class Child(Parent):
def __init__(self, a, b):
super(Child, self).__init__() #Don't forget call the parent ctor
self.a = a
self.b = b
#Parent.logging
def sum_(self):
return self.a + self.b
c = Child(3,6)
print c.sum_() #Outputs 9
As you may have done it intentionally, making a function in a class a staticmethod makes it "static", making it not able to access the "self" attribute of the instance. Plus, the __init__ of the class never ran because you never created an instance of the class.
You can alternatively do something like this by creating a Parent instance in the Child namescope:
Note: This method does not involve inheritance, if you believe it is a necessity, try #SuperSaiyan 's answer
import functools
class Log:
def __init__(self):
self.mssg = ""
self.err = ""
class Parent:
def __init__(self):
self.logger = Log()
def logging(self, fun): # include the "self" argument as it is no longer static
#functools.wraps(fun)
def inner(*args):
try:
print(fun.__name__)
self.logger.mssg += fun.__name__ +" :ok, "
return fun(*args)
except Exception as e:
self.logger.err += fun.__name__ +": error: "+str(e.args)
return inner
class Child: # you do not need to inherit the Parent class if it's only used for the decorator
myparent = Parent() # initiates the logger and create an instance of that class
def __init__(self, a, b):
self.a = a
self.b = b
#myparent.logging # use myparent instead of Parent
def sum_(self):
return self.a + self.b
c = Child(3, 6)
print(c.sum_()) # prints sum_ and 9
You could do it as shown in the following code, which variess from your approach in two primary ways. It changes logging into a (nested) class and implements it as a singleton so only one instance of it will ever be created.
Doing this means you must invoke the decorator with #Parent.logging() instead of just #Parent.logging. This ensures there's that a Log instance is created and assigned to self.logger where self is that the logging class's singleton instance. Note that __call__() is not a staticmethod.
import functools
class Log(object):
def __init__(self):
self.msg = ""
self.err = ""
class Parent(object):
class logging(object): # singleton decorator class
def __init__(self):
self.logger = Log()
def __new__(cls, *args, **kwargs):
if '_inst_' not in vars(cls):
cls._inst = object.__new__(cls)
return cls._inst
def __call__(self, fun):
#functools.wraps(fun)
def inner(*args, **kwargs):
try:
print(fun.__name__)
self.logger.msg += fun.__name__+" :ok, "
return fun(*args, **kwargs)
except Exception as exc:
self.logger.err += fun.__name__+": error: "+str(exc.args)
return inner
class Child(Parent):
def __init__(self, a, b):
super(Child, self).__init__() # initialize Parent
self.a = a
self.b = b
#Parent.logging() # must call and create decorator instance
def sum_(self):
return self.a + self.b
c = Child(3, 6)
print(c.sum_()) # -> 9