TL;DR: Python; I have Parent, Child classes. I have an instance of Parent class, parent
. Can I make a Child class instance whose super()
is parent
?
Somewhat specific use case (workaround available) is as follows: I'd like to make an instance of Logger class (from Python logging
module), with _log
method overloaded. Methods like logger.info
or logger.error
call this method with a level
specified as either INFO
or ERROR
etc., I'd like to replace this one method, touch nothing else, and make it all work seamlessly.
Here's some things that don't work (well):
- I can't just inherit from
logging.Logger
instance and overload this one method and constructor, becauseLogger
instances tend to be created via a factory method,logging.getLogger(name)
. So I can't just overload the constructor of the wrapper like:
class WrappedLogger(logging.Logger):
def __init__(self, ...):
super().__init__(...)
def _log(self, ...):
and expect it to all work OK.
- I could make a wrapper class, which provides the methods I'd like to call on the resulting instance, like
.info
or.error
- but then I have to manually come up with all the cases. It also doesn't work well when the_log
method is buried a few calls down the stack - there is basically no way to guarantee that any use of the wrapped class will call my desired_log
method - I can make a little kludge like so:
class WrappedLogger(logging.Logger):
def __init__(self, parent):
self._parent = parent
def _log(...): # overload
def __getattr__(self, method_name):
return getattr(self._parent, method_name)
now whenever I have an instance of this class, and call, say, wrapped.info(...)
, it will retrieve the parent method of info
, call it, which will then call self._log
which in turn points to my wrapped instance. But this feels very ugly.
- Similarly, I could take a regular instance of
Logger
and manually swap out the method; this is maybe a bit less "clever", and less ugly than the above, but similarly underwhelming.
This question has been asked a few times, but in slightly different contexts, where other solutions were proposed. Rather than looking for a workaround, I'm interested in whether there is a native way of constructing a child class instance with the parent instance specified.
Related questions:
- Create child class instances from parent class instance, and call parent methods from child class instance - here effectively a workaround is suggested
- Python construct child class from parent - here the parent can be created in the child's constructor
CodePudding user response:
If your goal is to supply a custom logger class that is used by getLogger
, you can "register" the custom class with the logging manager.
So, let's define a custom logger class
from logging import Logger
class MyLogger(Logger):
def _log(self, level, msg, *args, **kwargs) -> None:
print("my logger wants to log:", msg)
super()._log(level, msg, *args, **kwargs)
Then we tell the global logging manager to use this class instead.
from logging import setLoggerClass
setLoggerClass(MyLogger)
Thank you @Daniil Fajnberg, for pointing out, that setLoggerClass
exists.
Now getLogger
will instantiate your custom class.
from logging import getLogger
logger = getLogger(__file__)
logger.error("Dummy Error")
This will log the error as normal and also print "my logger wants to log: ..."
.
Note: The _log
method you are overloading is undocumented. Maybe there is a better way to achieve what you want.
CodePudding user response:
If i am understanding correctly, what @Bennet wants is - he has some custom logger classes derived from Logger(Logger acts as interface) like Logger1, Logger2 etc(which implementation gets chosen will vary at runtime). On top of each of this he wants to add some functionality which modifies only the _log function of each of these implementations.
IMO there shouldn't be any direct way to do it, since what you are attempting is trying to modify(not extend) the behaviour of an existing class which is not recommended for OOP paradigm. The hacky way is clever (found it cool).
def __getattr__(self, method_name):
return getattr(self._parent, method_name)
(I don't think you can do the same in Java) P.S. Wanted to comment this but i am poor in SO it seems :)
CodePudding user response:
From the way you keep re-phrasing your more general question, it seems you misunderstand how object creation works. You are asking for a
way of constructing a child class instance with the parent instance specified.
There is no such concept as a "parent instance". Inheritance refers to classes, not objects. This means you need to define yourself, what that term is supposed to mean. How would you define a "parent instance"? What should it be and when and how should it be created?
Just to demonstrate that there is no mechanism for creating "parent instances", when a child class instance is created, consider this:
class Foo:
instances = []
def __new__(cls):
print(f"{cls.__name__}.__new__()")
instance = super().__new__(cls)
Foo.instances.append(instance)
return instance
class Bar(Foo):
pass
bar = Bar()
assert len(Foo.instances) == 1
assert Foo.instances[0] is bar
assert type(bar) is Bar
assert isinstance(bar, Foo)
The output is Bar.__new__()
and obviously the assertions are passed. This goes to show that when we create an instance of Bar
it delegates construction further up the MRO chain (because it doesn't implement its own __new__
method), which results in a call to Foo.__new__
. It creates the object (by calling object.__new__
) and puts it into its instances
list. Foo
does not also create another instance of class Foo
.
You also seem to misunderstand, what calling the super
class does, so I suggest checking out the documentation. In short, it is just an elegant tool to access a related class (again: not an instance).
So, again, your question is ill defined.
If you mean (as @Barmar suggested) that you want a way to copy all the attributes of an instance of Foo
over to an instance of Bar
, that is another story. In that case, you still need to be careful to define, what exactly you mean by "all attributes".
Typically this would refer to the instances __dict__
. But do you also want its __slots__
copied? What about methods? Do you want them copied, too? And do you want to just replace everything on the Bar
instance or only update those attributes set on the Foo
instance?
I hope you see, what I am getting at. I guess the simplest way is just update the instances __dict__
with values from the other one:
...
class Bar(Foo):
def update_from(self, obj):
self.__dict__.update(obj.__dict__)
foo = Foo()
foo.n = 1
foo.text = "Hi"
bar = Bar()
bar.update_from(foo)
print(bar.n, bar.text) # output: `1 Hi`
And you could of course do that in the __init__
method of Bar
, if you wanted. If the initialization of Foo
is deterministic and instances keep the initial arguments laying around somewhere, you could instead just call the inherited super().__init__
from Bar.__init__
and pass those initial arguments to it from the instance. Something like this:
class Foo:
def __init__(self, x, y):
self.x = x
self.y = y
self.z = x y
class Bar(Foo):
def __init__(self, foo_obj):
super().__init__(foo_obj.x, foo_obj.y)
foo = Foo(2, 3)
bar = Bar(foo)
print(bar.z) # output: `5`
I hope this makes things clearer for you.