Please help me, my competition is around the corner
I tried to use OOP in Kivy. This is my simple Python code for testing:
class location:
def __init__(self, total_house, total_land):
self.total_house = total_house
self.total_land = total_land
class test(BoxLayout):
def addNum(self):
App.get_running_app().x.total_house += 1
class testApp(App):
x = location(NumericProperty(10),NumericProperty(5))
testApp().run()
this is my kv file:
<test>:
orientation: 'vertical'
Label:
text: str(app.x.total_house)
Button:
text: 'add'
on_press: root.addNum()
This is the output
I want the output to be 10 and when the button is pressed the number is added by one.
Please help me, I am new to KIVY
One way of getting pure value from Kivy Property is to use the built-in .get(EventDispatcher obj) method from the kivy.properties.Property class:
class test(BoxLayout):
def addNum(self):
App.get_running_app().x.get(EventDispatcher()) += 1
But before that, you need to import the EventDispatcher class first:
from kivy._event import EventDispatcher
Also please note that while this works in theory and it will indeed change the value of the x variable, I would recommend directly changing the label's own text, something like this:
.py
def numberify(*args):
# This functions is for universally changing str to either int or float
# so that it doesn't happen to return something like 8.0 which isn't that great
a = []
for w in range(0, len(args)):
try:
a.append(int(args[w]))
except ValueError:
a.append(float(args[w]))
return a if len(a) > 1 else a[0]
class test(BoxLayout):
def addNum(self):
self.ids.label1.text = str(numberify(self.ids.label1.text) + 1)
.kv
<test>:
orientation: 'vertical'
Label:
id: label1
text: str(app.x.total_house)
Button:
id: button1
text: 'add'
on_press: root.addNum()
Learn more about Kivy Property here and understand how it's not always necessary to use them :)
Related
I want to display the color name that i found at { closest_colour(requested_colour) } function
in popup window.
In practice application supposed to ask for file from your computer (only specific image types) then ask for mouse input while displaying image that you choose.Finally opens popup window to display color of pixel that you clicked.
.py
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.factory import Factory
from kivy.properties import ObjectProperty, StringProperty
from kivy.uix.popup import Popup
import cv2
import webcolors
class LoadDialog(FloatLayout):
load = ObjectProperty(None)
cancel = ObjectProperty(None)
class Root(FloatLayout):
loadfile = ObjectProperty(None)
def dismiss_popup(self):
self._popup.dismiss()
def show_load(self):
content = LoadDialog(load=self.load, cancel=self.dismiss_popup)
self._popup = Popup(title="Load file", content=content,
size_hint=(0.9, 0.9))
self._popup.open()
def load (self,path, filename):
filename=str(filename)
filename = filename.replace("[", "")
filename = filename.replace("]", "")
filename = filename.replace("\\\\", "\\")
filename = filename.replace("'", "")
print(filename)
img=cv2.imread(filename)
cv2.imshow('image', img)
self.dismiss_popup()
def closest_colour(requested_colour):
min_colours = {}
for key, name in webcolors.CSS3_HEX_TO_NAMES.items():
b_c, g_c, r_c = webcolors.hex_to_rgb(key)
rd = (r_c - requested_colour[0]) ** 2
gd = (g_c - requested_colour[1]) ** 2
bd = (b_c - requested_colour[2]) ** 2
min_colours[(rd + gd + bd)] = name
return min_colours[min(min_colours.keys())]
def click_event(event, x, y, flags, params):
if event == cv2.EVENT_LBUTTONDOWN:
print(x, ' ', y)
requested_colour=img[y,x]
global answer
answer=closest_colour(requested_colour)
print(closest_colour(requested_colour))
print(img[y,x])
cv2.destroyAllWindows()
cv2.setMouseCallback('image', click_event)
cv2.waitKey(0)
return answer
class MyPopup(Popup):
PopUp=ObjectProperty(None)
text = StringProperty(Root.answer)
def MyPopup(self):
content = MyPopup(PopUp=self.PopUp)
self._popup = Popup(title="Color Found!!", content=content, size_hint=(0.2, 0.4))
self._popup.open()
class main(App):
pass
Factory.register('Root', cls=Root)
Factory.register('LoadDialog', cls=LoadDialog)
Factory.register('MyPopup', cls=MyPopup)
if __name__ == '__main__':
main().run()
.kv
#:kivy 1.1.0
#:import Factory kivy.factory.Factory
Root:
FloatLayout:
Button:
id: load
size_hint:(1,.1)
pos_hint:{'x':0, 'y':0}
text: 'Load'
on_release: root.show_load()
background_color :(0, 0, 1, 1)
Image:
id: img
size_hint:(1,.9)
pos_hint:{'x':0, 'y':.1}
source: 'palet.jpg'
size: self.texture_size
keep_ratio: False
allow_stretch: True
<LoadDialog>:
id:load_dialog
BoxLayout:
size: root.size
pos: root.pos
orientation: "vertical"
FileChooserIconView:
id: filechooser
filters:['*.png','*.jpg','*.jpeg']
BoxLayout:
size_hint_y: None
height: 30
Button:
text: "Cancel"
on_release: root.cancel()
Button:
text: "Load"
on_release: root.load(filechooser.path, filechooser.selection)
on_release: Factory.MyPopup().open()
<MyPopup#Popup>
auto_dismiss: False
size_hint:0.4,0.2
pos_hint:{"x":0.3,"y":0.4}
title:"Color Found!!"
BoxLayout:
Label:
text:root.text
font_size:24
Button:
text:"Ok"
width:.5
size_hint:0.2,0.2
pos_hint:{"x":0.2,"y":0}
on_release: root.dismiss()
I tried to create global answer variable but it didnt work out.
Okay, the structure of what you're trying to do is a little confusing, the class LoadDialog(FloatLayout) looks like it's being called just to make the connection between the two classes, is that it?
But from the looks of it, you are trying to pass the text from variable
answer
that is returned when you call the function def load(). if so, try creating a variable eg text_answer = None in class Root()
then, before returning the result, pass the variable's value to the new variable created within the class, in the code snippet
cv2.setMouseCallback('image', click_event)
cv2.waitKey(0)
# add this
self.text_answer = answer
# don't need the return
return answer
later in class MyPopup(Popup), add the variable that was created in the Root() class. But remember that, for object-oriented concepts, you need to use the variable from the instance of class Root() and not directly from the class, and that's the tricky part.
There are many ways to do this:
Pass the Root() class instance to the Popup constructor.
Use an ObjectProperty to use the Root instance.
Use kivy's own tree of objects, doing self.parent.parent.[...] until
finding the instance of Root()
For this case, the easiest way is to use the self.parent method, until you find the Root instance and retrieve the text value, try doing something like this:
class MyPopup(Popup):
PopUp=ObjectProperty(None)
# go looking for the instance of Root in the parentage
text = StringProperty(self.parent.parent.text_answer)
I can't give the answer to your specific problem because I don't have some of the libraries you are using, but I believe this should help with communication between classes.
Always remember to try to reference the instances of the classes, not the classes themselves.
I'm having trouble with correctly calling functions from different classes.
I am making a simple game which calculates the score using the amount of time it takes to clear a level. There's a stopwatch running in the background and I want to add a pause button that popup menu, and a resume button inside this popup menu.
The problem is that when calling the pause function from within the popup menu, it will also be returned inside the popup, instead of inside the main widget.
Here is a simplified version of the code:
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import NumericProperty
from kivy.uix.widget import Widget
from kivy.uix.popup import Popup
from kivy.clock import Clock
root_widget = Builder.load_file('app.kv')
class ExampleWidget(Widget):
time = NumericProperty(0)
paused = False
stop = False
# Keeping time
def increment_time(self, interval):
self.time += .1
print(self.time) # To check if stopwatch is running or not
# Stop should mean that the stopwatch must reset when it starts again.
# When paused it should resume when it starts again
def stop_start_or_pause(self):
# stop stopwatch
if self.stop:
Clock.unschedule(self.increment_time)
print('Stopped')
# Make sure time is 0 when restarting
elif not self.stop and not self.paused:
# Keeping time
self.time = 0
Clock.schedule_interval(self.increment_time, .1)
# Pause stopwatch
elif self.paused:
Clock.unschedule(self.increment_time)
print("!!", self.time) # To make it easier to see if stopwatch actually resumes where it left off
print('unscheduled') # Just to confirm and to make it a bit easier to see
# resume stopwatch
elif not self.paused:
Clock.schedule_interval(self.increment_time, .1)
class PopupMenu(Popup):
example = ExampleWidget()
class MyApp(App):
ExampleWidget = ExampleWidget()
def build(self):
return ExampleWidget()
MyApp().run()
.kv file:
#:import Factory kivy.factory.Factory
<PopupMenu#Popup>
auto_dismiss: False
size_hint_y: .8
size_hint_x: .9
title: 'Pause'
example: app.ExampleWidget
BoxLayout:
Button:
text: 'resume'
on_press: root.example.paused = False
on_release: root.dismiss(); root.example.stop_start_or_pause()
size: self.size
<ExampleWidget>:
GridLayout:
col: 2
rows: 3
size: root.size
Button:
text: 'start'
size: self.size
on_press: root.stop = False; root.stop_start_or_pause()
Button:
text: 'stop'
size: self.size
on_press: root.stop = True; root.stop_start_or_pause()
Button:
text: 'Pause menu'
size: self.size
on_press: root.paused = True
on_release: Factory.PopupMenu().open(); root.stop_start_or_pause()
Label:
text: str(round(root.time))
size: self.size
I tried making a function and using Clock.schedule.interval() to keep checking if paused == True, but it keeps returning:
AttributeError: 'float' object has no attribute 'stopped'
This didn't seem like efficient solution anyways, so I didn't want to spend too much time on this function. I also tried to find 'stupid' mistakes (I.e. ',' instead of '.') but that was before I realised that the resume button returned a 'second' stopwatch instead of updating the one I actually wanted to use.
I hope that someone can help, and that my question is clear. English is not my first language so I sometimes have a hard time finding the best way to explain/ask questions.
Thank you in advance!
If I understand your question, the problem is with your MyApp class:
class MyApp(App):
ExampleWidget = ExampleWidget()
def build(self):
return ExampleWidget()
This code is creating two instances of ExampleWidget. One is returned in the build() method, and one is saved as the ExampleWidget attribute of MyApp. Now, when you use the ExampleWidget attribute of MyApp, you are not referencing the ExampleWidget that is the root of your GUI, so it has no effect on what appears on the screen. The fix is to just creat a single instance of ExampleWidget, like this:
class MyApp(App):
ExampleWidget = ExampleWidget()
def build(self):
return self.ExampleWidget
I'm new in Kivy and i need your help ..
I have a little question:
I need a dynamic array, which the user can enter first value, in the erst TextInput Box , then he can press the Button "new line", he gets the possibility to enter second value in a new TextInput Box,than he can press again the Button "new line".. he has the possibility to enter a third value in the new TextInput Box ..
at any time he can press "Result" .. to get the sum of this Values in a label
How can I make this dynamic array ?
Thanks
H Lothric ..
this is the code
main.py
from kivy.uix.textinput import TextInput
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
class MainWindow(Screen):
def __init__(self, **kwargs):
super(MainWindow, self).__init__(**kwargs)
self.counter = 1
self.textlist = [TextInput()]
self.ids.grid.add_widget(Label(text='Input value ' + self.counter))
self.counter += 1
self.ids.grid.add_widget(self.textlist[0])
# function to create new inputs, that button 'new line' calls:
def addnewtextinput(self):
self.ids.grid.add_widget(Label(text='Input value ' + self.counter))
self.counter += 1
self.textlist.append(TextInput())
self.ids.grid.add_widget(self.textlist[-1])
# function to get a result:
def getresult(self):
result = 0
for i in self.textlist:
# you may convert it to float if you need, like float(i.text)
result += int(i.text)
self.ids.label_id.text = str(result)
class WindowManager(ScreenManager):
pass
class MyMainApp(App):
def build(self):
b1=WindowManager()
MainWindow()
return b1
if __name__ == "__main__":
MyMainApp().run()
and this is the main.kv
<CustButton#Button>:
font_size: 40
WindowManager:
MainWindow:
<MainWindow>:
name: "main"
GridLayout:
cols:1
# you will control that GridLayout from .py so here it's empty
GridLayout:
# set the id to be able to control it from .py file
id: grid
cols: 2
CustButton:
text: "new line"
on_press: root.addnewtextinput()
CustButton:
text: "result"
font_size: "30sp"
on_press: root.getresult()
TextInput:
id:label_id
font_size: 40
multiline: True
this is the code
What's the problem? Make a list of TextInputs and then take the values from it. Something like this:
# create a list with first text input:
self.textlist = [TextInput()]
# I don't know which layout you are using, for example BoxLayout with box variable. Add TextInput to box:
self.box.add_widget(self.textlist[0])
# function to create new inputs, that button 'new line' calls:
def addnewtextinput(self):
self.textlist.append(TextInput())
self.box.add_widget(self.textlist[-1])
# function to get a result:
def getresult(self):
result = 0
for i in self.textlist:
# you may convert it to float if you need, like float(i.text)
result += int(i.text)
return result
Ok, in your case it would be like this:
.py file:
# import this
from kivy.properties import ObjectProperty
from kivy.clock import Clock
...
class MainWindow(Screen):
def __init__(self, **kwargs):
super(MainWindow, self).__init__(**kwargs)
# we have to delay running that function, it will run when kv file will be ready to provide widgets by id
Clock.schedule_once(self.getids)
self.counter = 1
self.textlist = [TextInput()]
self.grid.add_widget(Label(text='Input value ' + counter))
self.counter += 1
self.grid.add_widget(self.textlist[0])
# don't forget this
grid = ObjectProperty(None)
# function to connect layout with the variable
def getids(self):
self.grid = self.ids.grid
# function to create new inputs, that button 'new line' calls:
def addnewtextinput(self):
self.grid.add_widget(Label(text='Input value ' + self.counter))
self.counter += 1
self.textlist.append(TextInput())
self.grid.add_widget(self.textlist[-1])
# function to get a result:
def getresult(self):
result = 0
for i in self.textlist:
# you may convert it to float if you need, like float(i.text)
result += int(i.text)
self.ids.label_id.text = str(result)
class MyMainApp(App):
def build(self):
self.b1 = WindowManager()
self.b1.add_widget(MainWindow())
return self.b1
.kv file:
<MainWindow>:
name: "main"
# don't forget to add this
grid: grid.__self__
GridLayout:
cols:1
# you will control that GridLayout from .py so here it's empty
GridLayout:
# set the id to be able to control it from .py file
id: grid
cols: 2
CustButton:
text: "new line"
on_press: root.addnewtextinput()
CustButton:
text: "result"
font_size: "30sp"
on_press: root.getresult()
TextInput:
id:label_id
font_size: 40
multiline: True
I am currently developing a GUI with Python/ Kivy and have some issues when it comes to call a function from another class. I set up a screen, which includes a TextInput widget, that can be used to insert an E-Mail address. By clicking the Submit-Button, the function 'check_mail' is called, which checks the E-Mail using regular expressions and then either prints a text ('E-Mail not valid') or changes the screen (The E-Mail Address will later be transferred to a database, for now its fine to just change the screen after submitting). However, the function does print me the text, if the E-Mail format is not valid, but when it comes to call the change_screen function from the InsertData class, it is not working (AttributeError: 'NoneType' object has no attribute 'ids') If I call the change_screen function within the .kv file {on_release: app.change_screen('home_screen')}, it works fine. How can I access the change_screen function from my InsertData class?
main.py
class HomeScreen(Screen):
pass
class InsertData(Screen):
def check_mail(self):
addressToVerify = self.ids.email_main.text
match = re.match('^[_a-z0-9-]+(\.[_a-z0-9-]+)*#[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$', addressToVerify)
if match == None:
print('Email not valid!')
else:
MainApp().change_screen('home_screen')
GUI = Builder.load_file("main.kv")
class MainApp(App):
def build(self):
return GUI
def change_screen(self, screen_name):
screen_manager = self.root.ids[
'screen_manager']
screen_manager.transition = CardTransition()
screen_manager.transition.direction = 'up'
screen_manager.transition.duration = .3
screen_manager.current = screen_name
MainApp().run()
insert_data.kv
<InsertData>:
FloatLayout:
canvas:
Rectangle:
size: self.size
pos: self.pos
source: "background/background_main.png"
GridLayout:
rows: 1
pos_hint: {"top": 1, "right": 1}
size_hint: 1, .8
TextInput:
id: email_main
hint_text: "E-Mail Address"
LabelButton:
text: "Submit"
on_release:
root.check_mail()
This is how you can call function x for class B
class A:
def __init__(self):
pass
def x(self):
print('x')
class B:
def __init__(self):
A().x()
Update Managed to solve the problem.
Looking at the error I noticed that every time, the function change_screen is executed (due to a not-valid E-Mail format), it will run the following line in change_screen:
screen_manager = self.root.ids['screen_manager']
The problem was, that self.root was referring to the InsertData class, which does not have the attributes. Since self.roots refers to the GUI in the MainApp class, I changed the line in the change_screen function as follows:
screen_manager = GUI.ids['screen_manager']
Now the function is referring to the fixed GUI instead of the self.root and is running without any problems.
I have some solution just to show how does it will work.
class A (self):
def some_method(self):
print("Print Here")
class B (self):
def some_more_method(self):
print("Let see here")
Details:
Let's say you want to use method/function from class B in class A.
'Add this line'
B.some_more_method(self)
It works for me.
I have a bunch of lights I'm trying to control. Rather than have each button state change call a unique function I want to try and have a multipurpose function as thats what functions are for (as far as I understand).
Button calling function:
ToggleButton:
id: KitchenSpot1Toggle
text: "Kitchen Spot 1"
on_press: root.changeKS1(1)
The function:
def changeKS1(self,change):
if change==1 and b.get_light(1, 'on'):
self.KitchenSpot1(False)
else:
self.KitchenSpot1(True)
That function then calls this function to physically change the state of the light using a 3rd part library.
def KitchenSpot1(self,state):
lights[0].name
lights[0].on = state
The reason I passed "1" inside of the function is because it didn't like having nothing passed in it (I don't know why it didn't like it). If you hadn't already guessed it, I am new at this. I have a bit of a cpp micro controller background but I'm trying to get my head around python and PC based programming. I'm looking for a bit of advice on how best I can condense this and make it as efficient as possible. I may not know much about python, but, I know I shouldn't be typing practically the same thing out 30 times.
Thanks in advance to anyone that can share some of their wisdom.
Its with noting I am using kivy with python to generate the button.
Full main.py code:
from kivy.properties import StringProperty
import kivy
from kivy.uix.togglebutton import ToggleButton
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.app import App
kivy.require('1.10.0')
from phue import Bridge
import nest
b = Bridge('xx.xxx.xxx.xxx')
b.connect()
b.get_api()
lights = b.lights
class Controller(GridLayout):
state = StringProperty('down')
def __init__(self, **kwargs):
super(Controller, self).__init__(**kwargs)
Clock.schedule_interval(self.update, 1.0 / 60.0)
def KitchenSpot1(self,state):
lights[0].name
lights[0].on = state
def changeKS1(self,change):
if change==1 and b.get_light(1, 'on'):
self.KitchenSpot1(False)
else:
self.KitchenSpot1(True)
def KitchenSpot2(self,state):
lights[1].name
lights[1].on = state
def KitchenSpot3(self,state):
lights[2].name
lights[2].on = state
def OfficeSpot1(self,state):
lights[3].name
lights[3].on = state
def OfficeSpot2(self,state):
lights[4].name
lights[4].on = state
def OfficeSpot3(self,state):
lights[5].name
lights[5].on = state
def OfficeSpot4(self,state):
lights[6].name
lights[6].on = state
def JuliaBedside(self,state):
lights[7].name
lights[7].on = state
def JohnBedside(self,state):
lights[8].name
lights[8].on = state
def update(self, dt):
if b.get_light(1, 'on'):
self.state = 'down'
else:
self.state = 'normal'
class ActionApp(App):
def build(self):
return Controller()
if __name__ == "__main__":
myApp = ActionApp()
myApp.run()
Full action.kv code
<Controller>:
cols: 4
rows: 3
spacing: 10
state: "normal"
ToggleButton:
id: KitchenSpot1Toggle
text: "Kitchen Spot 1"
on_press: root.changeKS1(1)
#on_release: root.KitchenSpot1(False)
#state1 = app.update.h
state: root.state
ToggleButton:
text: "Kitchen Spot 2"
Button:
text: "Kitchen Spot 3"
Button:
text: "Kitchen Spot 4"
Button:
text: "Office Spot 1"
Button:
text: "Office Spot 2"
Button:
text: "Office Spot 3"
Button:
text: "Office Spot 4"
Update:
Python program:
def lightcontrol(self,lightnumber):
if b.get_light(1, 'on'):
lights[lightnumber].name
lights[lightnumber].on (False)
#self.KitchenSpot1(False)
else:
lights[lightnumber].name
lights[lightnumber].on (True)
#self.KitchenSpot1(True)
Kivy button:
ToggleButton:
id: KitchenSpot1Toggle
text: "Kitchen Spot 1"
on_press: root.lightcontrol(0)
Have each button call the same function but with a different parameter.
# Add the a number parameter here based on what you've
def KitchenSpot(self,state, light_index):
lights[light_index].name
lights[light_index].on = state
Then in the KV file,
Button:
text: "Kitchen Spot 3"
on_press: root.KitchenSpot(state, light_index = 3)
Button:
text: "Kitchen Spot 4"
on_press: root.KitchenSpot(state, light_index = 4)
You only have to create the function one, with each button passing in the relevant light_index number.
Neither knowing kivy nor phue I've tried to reduce the problem to your code redundancy by abstracting the definition of your methods (and also the creation of your action.kv file).
So I hope this what you are looking for: First, I would define all the relevant data of the buttons in a global variable, like:
BUTTONS = [
{'id': 0, 'methodname': 'KitchenSpot1', 'text': 'Kitchen Spot 1'},
{'id': 1, 'methodname': 'KitchenSpot2', 'text': 'Kitchen Spot 2'},
...
]
Then define your Controller-class with all unique methods just like you did (__init__ and update in your case; however I don't see what update should do, I just left it be):
class Controller(GridLayout):
state = StringProperty('down')
def __init__(self, **kwargs):
super(Controller, self).__init__(**kwargs)
Clock.schedule_interval(self.update, 1.0 / 60.0)
def update(self, dt):
if b.get_light(1, 'on'):
self.state = 'down'
else:
self.state = 'normal'
# this iteratively creates your button-individual methods
for button_dict in BUTTONS:
def func(self, state):
lights[button_dict['id']].name # whatever this might do just adressing the name attribute. Is it a python-property on lights that does some action?
lights[button_dict['id']].on = state
def func_change(self, change):
if change == True and b.get_light(button_dict['id'], 'on'):
getattr(self, button_dict['methodname'])(False)
else:
getattr(self, button_dict['methodname'])(True)
# create .KitchenSpot1, .KitchenSpot2, ...
setattr(Controller, button_dict['methodname'], func)
# create .changeKitchenSpot1, .changeKitchenSpot2, ...
setattr(Controller, "change{}".format(button_dict['methodname']), func_change)
The instantiated Controller will have bound methods named accordingly to all methodnames and change-methodnames.
Finally you can create your action.kv file dynamically
actionkv_toggle_button = """
ToggleButton:
id: {methodname}Toggle
text: "{text}"
on_press: root.change{methodname}(1)
#on_release: root.{methodname}(False)
#state1 = app.update.h
state: root.state
"""
actionkv_str = """
<Controller>:
cols: 4
rows: 3
spacing: 10
state: "normal"
{buttons}
""".format(
buttons="".join([
actionkv_toggle_button.format(
methodname=button_dict['methodname'],
text=button_dict['text']
) for button_dict in BUTTONS
])
)
this gives the output
<Controller>:
cols: 4
rows: 3
spacing: 10
state: "normal"
ToggleButton:
id: KitchenSpot1Toggle
text: "Kitchen Spot 1"
on_press: root.changeKitchenSpot1(1)
#on_release: root.KitchenSpot1(False)
#state1 = app.update.h
state: root.state
ToggleButton:
id: KitchenSpot2Toggle
text: "Kitchen Spot 2"
on_press: root.changeKitchenSpot2(1)
#on_release: root.KitchenSpot2(False)
#state1 = app.update.h
state: root.state
Save it to a file
with open('action.kv', 'w') as f:
f.write(actionkv_str)
Helpful links:
Dynamically attaching methods to a class
String formatting in python
List comprehension