I have a class as such:
class Line:
def __init__(self, text: str):
self.text = text
A line in my case can have many features. For example,
The line is considered long if it has more than 50 characters.
If the line has non-ascii characters, then a flag is set as non_ascii = True
and so on
Find all the urls in the line
I am able to implement them in terms of class methods:
class Line:
def __init__(self, text: str):
self.text = text
def is_ascii(self):
# check and return
def is_long(self):
# check and return
def urls(self):
# find and return urls
The problem I have is, I need to make the method calls on the Line object multiple times at different stages in the process (some of the method calls are pretty computationally heavy).
What I would like to have is, at initialization, I would like the Line object to have attributes such as is_ascii, is_long, urls to be pre populated so that they can be accessed multiple times without the need to compute them every time they are accessed.
class Line:
def __init__(self, text: str):
self.text = text
self.is_ascii = do some processing on self.text, and assign value
self.is_long = do some processing on self.text, and assign value
self.urls = do some processing on self.text, and assign value
I am not sure if having all that logic live inside the init block makes sense. How would I achieve this in a 'pythonic' manner?
You could just have the methods that you do, and call them from within __init__:
class Line:
def __init__(self, text: str):
self.text = text
self.is_ascii = self.calc_is_ascii(self.text)
self.is_long = self.calc_is_long(self.text)
self.urls = self.calc_urls(self.text)
def calc_is_ascii(self):
# check and return
def calc_is_long(self):
# check and return
def calc_urls(self):
# find and return urls
You could also have it so if a method is called, it checks to see if the value has already been calculated, and uses the cached value, otherwise it calculates and caches it:
class Line:
def __init__(self, text: str):
self.text = text
self.is_ascii = None
self.is_long = None
self.urls = None
def calc_is_ascii(self):
if self.is_ascii is None:
# Do expensive calculation of self.is_ascii
self.is_ascii = expensive_result
# Use the cached, previously calculated value
return self.is_ascii
# Then the same pattern for other methods
This has the benefit that if one of the attributes is never needed, the work to calculate it isn't done.
I'm initializing the attributes to None. If they're None later, I know they haven't been calculated yet. If they aren't None, I know they have already been calculated, so I can just return the calculated result. This of course assumes that None is not a valid value.
I am carrying out XML parsing for a list of XML files. I am using a module which overrides the XMLParser class of element tree. This is the code-
import sys
sys.modules['_elementtree'] = None
try:
sys.modules.pop('xml.etree.ElementTree')
except KeyError:
pass
import xml.etree.ElementTree as ET
class Parse():
def __init__(self):
self.xmlFiles = [list_of_xmlFile_paths]
def parse_xml_files(self):
for filepath in self.xmlFiles:
root = ET.parse(filepath, LineNumberingParser()).getroot()
for elem in root:
print(elem.start_line_numer, elem.end_line_number)
class LineNumberingParser(ET.XMLParser):
def _start(self, *args, **kwargs):
# Here we assume the default XML parser which is expat
# and copy its element position attributes into output Elements
self.element = super(self.__class__, self)._start(*args, **kwargs)
self.element.start_line_number = self.parser.CurrentLineNumber
self.element.start_column_number = self.parser.CurrentColumnNumber
return self.element
def _end(self, *args, **kwargs):
self.element = super(self.__class__, self)._end(*args, **kwargs)
self.element.end_line_number = self.parser.CurrentLineNumber
self.element.end_column_number = self.parser.CurrentColumnNumber
return self.element
The class LineNumberingParser gives me the begin line, end line of an xml node. My issue is that, for every xml file, the class is initialised.So this repetitive initialisation is not efficient. How can I do this by initialising the class only once? Can anyone please suggest.
I am still unsure how do you want to do that? It seems that ET.XMLParser class needs to be initialized on per-file basis....
However, should you find a way to go around that (e.g. by "re-initializing" the ET.XMLParser object's variables manually) you could keep an instance of the parser in LineNumberingParser as a class variable and initialize it only once.
How to get the output of the function executed on_click by ipywidgets.Button
outside the function to use in next steps? For example, I want to get back the value of a after every click to use in the next cell of the jupyter-notebook. So far I only get None.
from ipywidgets import Button
def add_num(ex):
a = b+1
print('a = ', a)
return a
b = 1
buttons = Button(description="Load/reload file list")
a = buttons.on_click(add_num)
display(buttons)
print(a)
The best way that I have found to do this type of thing is by creating a subclass of widgets.Button and then add a new traitlet attribute . That way you can load the value(s) that you want to perform operations on when you create a new button object. You can access this new traitlet attribute whenever you want inside or outside of the function. Here is an example;
from ipywidgets import widgets
from IPython.display import display
from traitlets import traitlets
class LoadedButton(widgets.Button):
"""A button that can holds a value as a attribute."""
def __init__(self, value=None, *args, **kwargs):
super(LoadedButton, self).__init__(*args, **kwargs)
# Create the value attribute.
self.add_traits(value=traitlets.Any(value))
def add_num(ex):
ex.value = ex.value+1
print(ex.value)
lb = LoadedButton(description="Loaded", value=1)
lb.on_click(add_num)
display(lb)
Hope that helps. Please comment below if this does not solve your problem.
I have a Tkinter frame that is essentially showing a group of thumbnails, displayed in Label widgets. I need the Labels to be created dynamically to accommodate differing numbers of thumbnails to generate. I have a generated list of file names, and can create the thumbnails as needed, but when I try to bind a function to each of the created labels, it seems to be over ridden by the last created Label/Binding. The result is that only the final label has the method bound to it.
import tkinter as tk
class test(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args)
self.shell = tk.Frame(self)
self.shell.pack()
self.create_widgets()
def create_widgets(self):
'''
Two rows of labels
'''
for row in range(2):
for i in range(5):
text = 'Thumbnail\nrow{}\ncolumn{}'.format(row,i)
self.thumb = tk.Label(self.shell,
text = text)
self.thumb.grid(row = row, column = i, sticky = 'news')
self.thumb.bind("<Button-1>",lambda x: self.click(self.thumb))
def click(self, *args):
#This should affect only the Label that was clicked
print('CLICK!')
app = test()
root = app
root.mainloop()
The method being called will always be the same, but how do I identify the Label to be effected?
There are at least three solutions.
The first is the simplest: the function is passed an event object that contains a reference to the widget:
label = tk.Label(...)
label.bind("<Button-1>", self.click)
...
def click(self, event):
print("you clicked:", event.widget)
If you prefer to use lambda, you can pass the label itself to the function:
label = tk.Label(...)
label.grid(...)
label.bind("<Button-1>",lambda event, label=label: self.click(label))
Another solution is to keep a reference to every label in a list, and pass the index to the function:
self.labels = []
for row in range(2):
for i in range(5):
label = tk.Label(...)
label.grid(...)
self.labels.append(label)
index = len(self.labels)
label.bind("<Button-1>",lambda event, i=index: self.click(i))
...
def click(self, index):
print("the label is ", self.labels[index])
When you click label then tk runs function with event object which you skip using lambda x.
You need
lambda event:self.click(event, ...)
and then in click you can use event.widget to get clicked widget.
def click(event, ...);
print('Widget text:', event.widget['text'])
You had problem with self.thumb in self.click(self.thumb) because you don't know how works lambda in for loop. (and it is very popular problem :) )
lambda is "lazy". It doesn't get value from self.thumb when you declare lambda x: self.click(self.thumb) but when you click button (and you execute lambda. But when you click label then for loop is finished and self.thumb keeps last value.
You have to use this method to get correct value when you declare lambda
labda event, t=self.thumb: self,clikc(t)
To start off I am super new at using Tkinter,
The problem I am having is that my code works if I have only one of the object type. It will interact correctly if it is the only one of that tag type. So if I have one 'boat' and 100 'shells' each time it executes, it does so correctly.
The code detects if there is collision between two objects and then changes the currently selected item's color to a random. So as long as there is only one tag type currently it will work correctly. So if I click and drag the 'boat' into a 'shell' it will switch it's color. Then if I take 1 of the 100 'shell's and do the same I get this error.
I do not understand why it works correctly when there is only one object of a given type and to interacts a infinite amount of other objects but when there is more than one of a tag type it fails.
It correctly selects the id number for the selected object so I am just lost right now and appreciate any help.
Follows is the error I receive and the code I am using. It is just the vital parts needed to preform the needed task. The collision code is the same as in the code though.
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Python34\lib\tkinter\__init__.py", line 1487, in __call__
return self.func(*args)
File "H:/Charles Engen/PycharmProjects/Battleship/BattleShipGUI.py", line 112, in on_token_motion
self.collision_detection(event)
File "H:/Charles Engen/PycharmProjects/Battleship/BattleShipGUI.py", line 85, in collision_detection
self.canvas.itemconfig(current_token, outline=_random_color())
File "C:\Python34\lib\tkinter\__init__.py", line 2385, in itemconfigure
return self._configure(('itemconfigure', tagOrId), cnf, kw)
File "C:\Python34\lib\tkinter\__init__.py", line 1259, in _configure
self.tk.call(_flatten((self._w, cmd)) + self._options(cnf))
_tkinter.TclError: unknown option "22"
import tkinter as tk
from random import randint
HEIGHT = 400
WIDTH = 680
def _random_color():
'''Creates a random color sequence when called'''
random_color = ("#"+("%06x" % randint(0, 16777215)))
return random_color
class Board(tk.Tk):
'''Creates a Board Class'''
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.menu_item = tk.Menu(self)
self.file_menu = tk.Menu(self.menu_item, tearoff=0)
self.file_menu.add_command(label='New', command=self.new_game)
self.file_menu.add_command(label='Exit', command=self.quit)
self.config(menu=self.file_menu)
self.canvas = tk.Canvas(width=WIDTH, height=HEIGHT)
self.canvas.pack(fill='both', expand=True)
# adds variable that keeps track of location
self._token_location_data = {"x": 0, "y": 0, "item": None}
def _create_object(self, coord, fcolor, color, token_name):
'''Creates an object with a tag, each object is assigned the ability to be clicked and dragged'''
(x, y) = coord
if token_name == 'boat':
points = [10+x, 10+y, 20+x, 20+y, 110+x, 20+y, 120+x, 10+y, 80+x,
10+y, 80+x, 0+y, 60+x, 0+y, 60+x, 10+y]
self.canvas.create_polygon(points, outline=fcolor, fill=color, width=3, tag=token_name)
elif token_name == 'shell':
self.canvas.create_oval(0+x, 0+y, 10+x, 10+y, outline=fcolor, fill=color, width=3, tag=token_name)
self.canvas.tag_bind(token_name, '<ButtonPress-1>', self.on_token_button_press)
self.canvas.tag_bind(token_name, '<ButtonRelease-1>', self.on_token_button_press)
self.canvas.tag_bind(token_name, '<B1-Motion>', self.on_token_motion)
def collision_detection(self, event):
'''This function tracks any collision between the boat and shell objects'''
# I will upgrade this to take any object collision
token = self.canvas.gettags('current')[0]
current_token = self.canvas.find_withtag(token)
x1, y1, x2, y2 = self.canvas.bbox(token)
overlap = self.canvas.find_overlapping(x1, y1, x2, y2)
for item in current_token:
for over in overlap:
if over != item:
# Changes the color of the object that is colliding.
self.canvas.itemconfig(current_token, outline=_random_color())
# The following three functions are required to just move the tokens
def on_token_button_press(self, event):
'''Adds ability to pick up tokens'''
# Stores token item's location data
self._token_location_data['item'] = self.canvas.find_closest(event.x, event.y)[0]
self._token_location_data['x'] = event.x
self._token_location_data['y'] = event.y
def on_token_button_release(self, event):
'''Adds ability to drop token'''
# Resets the drag
self._token_location_data['item'] = self.canvas.find_closest(event.x, event.y)
self._token_location_data['x'] = event.x
self._token_location_data['y'] = event.y
def on_token_motion(self, event):
'''Adds ability to keep track of location of tokens as you drag them'''
# Computes how much the object has moved
delta_x = event.x - self._token_location_data['x']
delta_y = event.y - self._token_location_data['y']
# move the object the appropriate amount
self.canvas.move(self._token_location_data['item'], delta_x, delta_y)
# record the new position
self._token_location_data['x'] = event.x
self._token_location_data['y'] = event.y
# Detects collision between objects
self.collision_detection(event)
def new_game(self):
'''Creates new game by deleting the current canvas and creating new objects'''
# Deletes current canvas
self.canvas.delete('all')
# runs the create board mechanism
self._generate_board()
# adds code to create a shell and boat on the screen from the appropriate function
for i in range(1):
self._create_object((410, 15*i), _random_color(), _random_color(), 'boat')
for i in range(4):
for j in range(10):
self._create_object((590+(i*10), 15*j), _random_color(), _random_color(), 'shell')
def main():
app = Board()
app.mainloop()
if __name__ == '__main__':
main()
To run the code, I removed the call to the non-existent ._generate_board. After this, I get the same error but with unknown option '3'.
The exception is caused by passing to self.canvas.itemconfig a tuple of ids, which you misleadingly call current_token, instead of a tag or id. A singleton is tolerated because of the _flatten call, but anything more becomes an error. I am rather sure that it is not a coincidence that '3' is the second member of the shell tuple. Passing token instead stops the exception. Also, there should be a break after itemconfig is called the first time.
With this, however, the shells are treated as a group, and the bounding box incloses all shells and overlap includes all shells. This is why moving a single shell away from the others is seen as a collision. At this point, all shells are randomized to a single new color if one is moved. To fix this, token should be set to the single item set in on_token_button_press, instead of a tag group. This implements your todo note. Here is the result.
def collision_detection(self, event):
'''Detect collision between selected object and others.'''
token = self._token_location_data['item']
x1, y1, x2, y2 = self.canvas.bbox(token)
overlap = self.canvas.find_overlapping(x1, y1, x2, y2)
for over in overlap:
if over != token:
# Changes the color of the object that is colliding.
self.canvas.itemconfig(token, outline=_random_color())
break
A minor problem is that you do the tag binds for 'shell' for each shell (in _create_object. Instead, put the tag binds in new so they are done once.
for tag in 'boat', 'shell':
self.canvas.tag_bind(tag, '<ButtonPress-1>', self.on_token_button_press)
self.canvas.tag_bind(tag, '<ButtonRelease-1>', self.on_token_button_press)
self.canvas.tag_bind(tag, '<B1-Motion>', self.on_token_motion)