Can I add specificity to a kwarg in a subclass' constructor? - python

I am trying to get PyCharm to understand that the subclass of my base controller class only takes a specific type of widget.
Minimal example:
import tkinter as tk
class BaseWidgetController:
def __init__(self, parent: 'tk.Widget'): # Parent is always __some__ kind of widget
self._parent = parent
class EntryWidgetController(BaseWidgetController):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._parent: 'tk.Entry' # On this class, I want Pycharm to understand _parent is only ever an Entry (a subclass of tk.Widget), but even adding this line doesn't change its mind.
def say_type(self) -> None:
print(type(self._parent)) # PyCharm still thinks _parent is a tk.Widget
ew = EntryWidgetController(parent=tk.Frame())
ew.say_type() # Obviously this works fine at runtime.

If you want to constrain the EntryWidgetController so that it only accepts tk.Entry or subclasses, the fix is rather simple - just do
class EntryWidgetController(BaseWidgetController):
def __init__(self, parent: 'tk.Entry', **kwargs):
super().__init__(parent=parent, **kwargs)
That way
ew = EntryWidgetController(parent=tk.Frame())
will make PyCharm complain that Expected type 'Entry', got 'Frame' instead.

Related

Is it possible to use a parent class method as a decorator for a child class method?

I have two classes, Manager and DataManager, simplified in the example below:
import numpy as np
class Manager:
def __init__(self, value, delay_init=True):
self.value = value
self.is_init = False
self.data = None
if not delay_init:
self._initialize()
#staticmethod
def delayed_init(fn):
def wrapped_delayed_init(obj, *args, **kwargs):
if not obj.is_init:
obj.data = np.random.randn(obj.value, obj.value)
obj.is_init = True
return fn(obj, *args, **kwargs)
return wrapped_delayed_init
#delayed_init.__get__(object)
def _initialize(self):
pass
class DataManager(Manager):
def __init__(self, value):
super().__init__(value)
#Manager.delayed_init
def calculate_mean(self):
return np.mean(self.data)
data_manager = DataManager(100)
assert data_manager.data is None
mean = data_manager.calculate_mean()
What my code needs to do is pass the method calculate as an argument to some other function as part of a test suite. In order to do this I need to create an instance of DataManager. What I must avoid is the time incurred by the full instance creation (since it involved downloading data), and so I delegate this task to some function in the parent class called delayed_init. There are a subset of methods belonging to DataManager that require this delayed_init to have been run, and so I choose to decorate them with delayed_init to ensure it is run whenever 1) another method requires it and 2) it has not already been run.
Now my problem: Currently it appears I need to explicitly define the decorator as #Manager.delayed_init, but this can be re-written as #<parent>.delayed_init. I would like to write it this way if possible given that in my opinion it is cleaner to not have to explicitly write out a given type if the type is always the parent. However, I cannot find a way to properly reference the parent class before an instance/object is created. Is it possible to access the parent class without the creation of any instances?
Thank you for the assistance.

In multiple inheritance in Python, init of parent class A and B is done at the same time?

I have a question about the instantiation process of a child class with multiple inheritance from parent class A without arg and parent class B with kwargs respectively.
In the code below, I don't know why ParentB's set_kwargs()method is executed while ParentA is inited when a Child instance is created.
(Expecially, why does the results show Child receive {}? How can I avoid this results?)
Any help would be really appreciated.
Thanks!
class GrandParent:
def __init__(self):
print(f"{self.__class__.__name__} initialized")
class ParentA(GrandParent):
def __init__(self):
super().__init__()
class ParentB(GrandParent):
def __init__(self, **kwargs):
super().__init__()
self.set_kwargs(**kwargs)
def set_kwargs(self, **kwargs):
print(f"{self.__class__.__name__} receive {kwargs}")
self.content = kwargs.get('content')
class Child(ParentA, ParentB):
def __init__(self, **kwargs):
ParentA.__init__(self)
ParentB.__init__(self, **kwargs)
c = Child(content = 3)
results:
Child initialized
Child receive {}
Child initialized
Child receive {'content': 3}
For most cases of multiple inheritance, you will want the superclass methods to be called in sequence by the Python runtime itself.
To do that, just place a call to the target method in the return of super().
In your case, the most derived class' init should read like this:
class Child(ParentA, ParentB):
def __init__(self, **kwargs):
super().__init__(self, **kwargs)
And all three superclasses __init__ methods will be correctly run. Note that for that to take place, they have to be built to be able to work cooperatively in a class hierarchy like this - for which two things are needed: one is that each method in any of the superclasses place itself a class to super().method()- and this is ok in your code. The other is that if parameters are to be passed to these methods, which not all classes will know, the method in each superclass should extract only the parameters it does know about, and pass the remaining parameters in its own super() call.
So the correct form is actually:
class GrandParent:
def __init__(self):
print(f"{self.__class__.__name__} initialized")
class ParentA(GrandParent):
def __init__(self, **kwargs):
super().__init__(**kwargs)
class ParentB(GrandParent):
def __init__(self, **kwargs):
content = kwargs.pop('content')
super().__init__(**kwargs)
self.set_kwargs(content)
def set_kwargs(self, content):
print(f"{self.__class__.__name__} receive {content}")
self.content = content
class Child(ParentA, ParentB):
def __init__(self, **kwargs):
super.__init__(**kwargs)
c = Child(content = 3)
The class which will be called next when you place a super() call is calculated by Python when you create a class with multiple parents - so, even though both "ParentA" and "ParentB" inherit directly from grandparent, when the super() call chain bubbles up from "Child", Python will "know" that from within "ParentA" the next superclass is "ClassB" and call its __init__ instead.
The algorithm for finding the "method resolution order" is quite complicated, and it just "works as it should" for most, if not all, usecases. It's exact description can be found here: https://www.python.org/download/releases/2.3/mro/ (really - you don't have to understand it all - there are so many corner cases it handles - just get the "feeling" of it.)

Calling super() with arguments set in sub-class __init__()?

So I have a class (let's call it ParamClass) which requires a parameter for initialization, and that parameter is something that should be available to the user to configure via some option-setting interface.
ParamClass knows nothing about the configuration interface or how to read them. So I made another class, called Configurator, which does all of that. When a class inherits from Configurator and tells it what configuration keys to read, Configurator's __init__() method will read those keys and assign their values to the correct attributes in self.
The problem I run into, however, is that when I try to pass arguments to super(), including the parameters to be read by Configurator, those parameters have no value yet. But they are passed as constants in the argument list to the super(). Example shown below. MyClass.__init__() can't even get started because self.server_param doesn't exist yet.
class ParamClass:
"""Just some class that needs a parameter for init"""
def __init__(self, param1, **kwargs) -> None:
super().__init__(**kwargs)
self.value = param1
class Configurator:
"""Reads parameters from a configuration source and sets appropriate class
variables.
"""
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
self.server_param = 2
class MyClass(Configurator, ParamClass):
def __init__(self, **kwargs) -> None:
super().__init__(param1=self.server_param, **kwargs)
# <-- Gives AttributeError: 'MyClass' object has no attribute 'server_param'
MyClass()
The only way I can get this to work is to break MRO in Configurator.init() and force the order of initilization. This is bad for obvious reason - I plan to use Configurator throughout my code and can't break MRO with it.
class ParamClass:
"""Just some class that needs a parameter for init"""
def __init__(self, param1, **kwargs) -> None:
super().__init__(**kwargs)
self.value = param1
class Configurator:
"""Reads parameters from a configuration source and sets appropriate class
variables.
"""
def __init__(self, **kwargs) -> None:
# super().__init__(**kwargs)
self.server_param = 2
class MyClass(Configurator, ParamClass):
def __init__(self, **kwargs) -> None:
Configurator.__init__(self, **kwargs)
# <-- After this call, self.server_param is defined.
ParamClass.__init__(self, param1=self.server_param, **kwargs)
MyClass()
How do I accomplish configuration of parameters in while user super? How do I do this in a generalized way that doesn't require Configurator to know little details about ParamClass?
Note: In my particular case, I don't "own" the ParamClass() code. It is library code that I'm using.

Override or remove an object from __init__ parent class from child's class

Currently, I have two classes:
class Parent:
def __init__(self, controller, parent):
# Key press event bind 'Return Key'
self.controller.bind('<Return>', self.averageTesting)
def averageTesting(variable):
if len(variable) > 0:
return variable
else:
messagebox.showerror("Error", "Enter a valid variable")
class Child(Parent):
def __init__(self):
......
The parent class is actually page one and child class is page two of the Tkinter frame pages.
I don't want the child to have the messagebox showing. Right now when I go to page two or the child class and I press return on my keyboard, the message shows up. And I don't want that in the second page. Only the first page which is the parent class.
But I need to inherit everything but the messagebox from the parent class to the child class.
How do I do achieve this?
Personally, I think that if you must exclude parts of your parent's constructor then your design is flawed. You could instead make the parent class more generic and make your current parent and your current child both inherit from the new parent.
If you really want to stick with that pattern then you could just unbind the event. If this is the only callback connected to the event then you can just do.
x = Child()
x.controller.unbind('<Return>')
Edit:
My next suggestion is to instead move the binding to a separate method inside the parent class. For example.
class Parent:
def __init__(self, controller, parent):
self.controller = controller
...
def averageTesting(variable):
if len(variable) > 0:
return variable
else:
messagebox.showerror("Error", "Enter a valid variable")
def initializeBinding(self):
self.controller.bind('<Return>', self.averageTesting)
... # plus anymore bindings that only the parent must have
class Child(Parent):
def __init__(self):
...
So now when you want to use the parent you would do this.
p = Parent(...)
p.initializeBinding()
Whereas when you instantiate a Child object you don't call the method.

Signaling between parent and child widgets in tkinter

I have a moderately complex GUI that I'm building for interacting with and observing some simulations. I would like to be able to continue to refactor and add features as the project progresses. For this reason, I would like as loose as possible a coupling between different widgets in the application.
My application is structured something like this:
import tkinter as tk
class Application(tk.Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.instance_a = ClassA(self)
self.instance_b = ClassB(self)
# ... #
class ClassA(tk.Frame):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# ... #
class ClassB(tk.Frame):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# ... #
def main():
application = Application()
application.mainloop()
if __name__ == '__main__':
main()
I would like to be able to perform some action in one widget (such as selecting an item in a Treeview widget or clicking on part of a canvas) which changes the state of the other widget.
One way to do this is to have the following code in class A:
self.bind('<<SomeEvent>>', self.master.instance_b.callback())
With the accompanying code in class B:
def callback(self): print('The more that things change, ')
The problem that I have with this approach is that class A has to know about class B. Since the project is still a prototype, I'm changing things all the time and I want to be able to rename callback to something else, or get rid of widgets belonging to class B entirely, or make instance_a a child of some PanedWindow object (in which case master needs to be replaced by winfo_toplevel()).
Another approach is to put a method inside the application class which is called whenever some event is triggered:
class Application(tk.Tk):
# ... #
def application_callback():
self.instance_b.callback()
and modify the bound event in class A:
self.bind('<<SomeEvent>>', self.master.application_callback())
This is definitely easier to maintain, but requires more code. It also requires the application class to know about the methods implemented in class B and where instance_b is located in the hierarchy of widgets. In a perfect world, I would like to be able to do something like this:
# in class A:
self.bind('<<SomeEvent>>', lambda _: self.event_generate('<<AnotherEvent>>'))
# in class B:
self.bind('<<AnotherEvent>>', callback)
That way, if I perform an action in one widget, the second widget would automatically know to to respond in some way without either widget knowing about the implementation details of the other. After some testing and head-scratching, I came to the conclusion that this kind of behavior is impossible using tkinter's events system. So, here are my questions:
Is this desired behavior really impossible?
Is it even a good idea?
Is there a better way of achieving the degree of modularity that I want?
What modules/tools can I use in place of tkinter's built-in event system?
My code in answer avoids the issue of class A having to know about internals of class B by calling methods of a handler object. In the following code methods in class Scanner do not need to know about the internals of a ScanWindow instance. The instance of a Scanner class contains a reference to an instance of a handler class, and communicates with the instance of ScannerWindow through the methods of Handler class.
# this class could be replaced with a class inheriting
# a Tkinter widget, threading is not necessary
class Scanner(object):
def __init__(self, handler, *args, **kw):
self.thread = threading.Thread(target=self.run)
self.handler = handler
def run(self):
while True:
if self.handler.need_stop():
break
img = self.cam.read()
self.handler.send_frame(img)
class ScanWindow(tk.Toplevel):
def __init__(self, parent, *args, **kw):
tk.Toplevel.__init__(self, master=parent, *args, **kw)
# a reference to parent widget if virtual events are to be sent
self.parent = parent
self.lock = threading.Lock()
self.stop_event = threading.Event()
self.frames = []
def start(self):
class Handler(object):
# note self and self_ are different
# self refers to the instance of ScanWindow
def need_stop(self_):
return self.stop_event.is_set()
def send_frame(self_, frame):
self.lock.acquire(True)
self.frames.append(frame)
self.lock.release()
# send an event to another widget
# self.parent.event_generate('<<ScannerFrame>>', when='tail')
def send_symbol(self_, data):
self.lock.acquire(True)
self.symbols.append(data)
self.lock.release()
# send an event to another widget
# self.parent.event_generate('<<ScannerSymbol>>', when='tail')
self.stop_event.clear()
self.scanner = Scanner(Handler())

Categories

Resources