lahmende Animation

Fragen zu Tkinter.
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

Warum bloß wird meine folgende Animation langsamer und immer laaaaangsamer, obwohl sie doch immer wieder die gleiche Schleife durchläuft? Ist vielleicht der Garbage-Collector von tkinter schlecht programmiert?

Code: Alles auswählen

#!/usr/bin/python3
import tkinter as t
from math import pi,cos
from time import sleep

root = t.Tk()
cv = t.Canvas(root,bg='white')
cv.pack()

cx = 150; cy = 100; rx = 120; ry = 80
old_coords = (cx,cy, cx,cy)
#
tt = 0
while True:
  tt += 1
  xx = rx*cos(tt*pi/32)
  coords = (cx-xx,cy-ry, cx+xx,cy+ry)
  ov = cv.create_oval(old_coords,outline='white')
  ov = cv.create_oval(coords)
  #
  cv.update()
  sleep(0.005)
  old_coords = coords

t.mainloop()
Vielen Dank für Hinweise aller Art!
Benutzeravatar
Masaru
User
Beiträge: 425
Registriert: Mittwoch 4. August 2004, 22:17

Ich würde mal bei dem kleinen sleep und dem create_oval darauf tippen, dass in zu kurzer zu viele Objekte erzeugt und im Speicher angelegt werden, so dass der GC nicht schnell genug hinterherkommt und Dein Python Interpreter auf maximale Prozess-Auslastung geht.

>>Masaru<<
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Goswin hat geschrieben:Ist vielleicht der Garbage-Collector von tkinter schlecht programmiert?
Lustig. Du erzeugst überhaupt kein "Garbage". Jedes Objekt, das du auf dem Canvas erzeugst, wird weiterhin schön aufbewahrt - wenn du es gelöscht haben willst, dann musst du das schon selbst tun. Das, was du erreichen willst, würde man so auch nicht machen.

Dieser Tage gab es einen Thread mit einer Tkinter-Animation (Roulette) hier im Forum. Sieh dir den Code von wuf mal an, dann kommst du selbst drauf.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hallo Goswin

Hier etwas mit einer konstanten Animation:

Code: Alles auswählen

#!/usr/bin/python3

# wuf_ref: animation_01_01.py

import tkinter as t
from math import pi,cos

class Oval(object):
    def __init__(self, canvas, cx, cy, rx, ry, color='black'):

        self.canvas = canvas
        self.cx = cx
        self.cy = cy
        self.rx = rx
        self.ry = ry
        self.color = color

        self.tt = 0
        self.old_coords = (self.cx, self.cy, self.cx, self.cy)
        self.graph_obj = self.canvas.create_oval(
            self.old_coords, outline=self.color)

        self.animate()

    def animate(self):
        self.tt += 1
        self.xx = self.rx*cos(self.tt*pi/32)
        self.coords = (
            self.cx - self.xx,
            self.cy - self.ry,
            self.cx + self.xx,
            self.cy + self.ry
            )

        self.canvas.coords(self.graph_obj, self.old_coords)
        self.canvas.update_idletasks()
        self.old_coords = self.coords

        self.canvas.after(5, self.animate)

root = t.Tk()

canvas = t.Canvas(root,bg='white')
canvas.pack()

cx = 150; cy = 100; rx = 120; ry = 80
oval = Oval(canvas, cx, cy, rx, ry)

cx = 250; cy = 140; rx = 80; ry = 120
oval = Oval(canvas, cx, cy, rx, ry, 'red')

t.mainloop()
while-Schleife in einer tkinter.mainloop() kann Probleme verursachen.

Gruss wuf :wink:
Take it easy Mates!
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

Schön, dass ich (ganz unfreiwillig) wenigstens zur Erheiterung von numerix beitragen konnte. In der Tat löst eine Anweisung tk.Canvas.delete(ov) mein Problem (danach ist man immer schlauer).

Darf es vielleicht noch eine dumme Zusatzfrage sein:
Warum funktioniert del(ov) nicht? Man könnte denken: weil tkinter nicht wissen kann, worauf ov zeigt. Aber das stimmt nicht, denn bei tk.Canvas.delete(ov) weiß tkinter sehr wohl, was mit ov gemeint ist.
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

@wuf: Vielen Dank, ich werden dein Beispiel aufmerksam durchstudieren. Ich kenne die meisten Methoden von Canvas noch nicht, insbesondere Canvas->coords() muss ich erst einmal dazulernen, was ich umgehend tun werde.

(Und richtig, das mainloop() hat im Beispiel nichts zu suchen)
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Goswin hat geschrieben:In der Tat löst eine Anweisung tk.Canvas.delete(ov) mein Problem (danach ist man immer schlauer).

Darf es vielleicht noch eine dumme Zusatzfrage sein:
Warum funktioniert del(ov) nicht? Man könnte denken: weil tkinter nicht wissen kann, worauf ov zeigt. Aber das stimmt nicht, denn bei tk.Canvas.delete(ov) weiß tkinter sehr wohl, was mit ov gemeint ist.
Jedes Item, das auf einem Canvas erzeugt wird, hat eine eindeutige ID und ist über diese jederzeit ansprechbar. Solange man ein Item nicht explizit löscht, bleibt es erhalten - ob es per ID an einen Bezeichner gebunden ist oder nicht, ist dafür egal. Mit "del ov" (ohne Klammern, weil del eine Anweisung und keine Funktion ist) entfernst du nur den Bezeichner aus dem entsprechenden Namensraum und kannst dann nicht mehr über diesen Bezeichner auf das Item zugreifen, das Item selbst bleibt aber weiterhin über seine ID (theoretisch) ansprechbar.
Goswin hat geschrieben:(Und richtig, das mainloop() hat im Beispiel nichts zu suchen)
Nein, umgekehrt. Das mainloop() gehört auf jeden Fall dahin, aber die while-Schleife nicht. Stattdessen macht man das mit after().
BlackJack

@goswin: Um das mit dem ``del`` noch einmal zu verdeutlichen:

Code: Alles auswählen

In [100]: a = 42

In [101]: b = [23, a, 4711]

In [102]: del a

In [103]: b
Out[103]: [23, 42, 4711]
Hier dürfte wohl klar sein, warum durch das ``del a`` die 42 nicht aus der Liste verschwindet. Bei Deinem Beispiel ist es genau der gleiche Grund.
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

@BlackJack: Scheint mir nun alles klar, <b> hat impliziterweise noch einen zweiten Zeiger auf <42> gesetzt, und solange der vorhanden ist, kann <42> nicht gegarbaged werden.


@Alle:
Ich glaube, die Namensgebung und Doku von tkinter sind für Neulinge nicht sehr gut. Da steht nämlich:

Code: Alles auswählen

create_oval(self, *args, **kw)
    Create oval with coordinates x1,y1,x2,y2.
und <create oval> kann man leicht als <draw oval> missverstehen. Ich hätte geschrieben:

Code: Alles auswählen

Oval(self, *args, **kw)
    Returns an oval object with coordinates x1,y1,x2,y2.
Findet ihr das nicht auch?
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hallo Goswin
Goswin hat geschrieben:@Alle:
Ich glaube, die Namensgebung und Doku von tkinter sind für Neulinge nicht sehr gut. Da steht nämlich:
Code:
create_oval(self, *args, **kw)
Create oval with coordinates x1,y1,x2,y2.
Ich persönlich finde 'create_oval' schon richtig. 'draw_oval' wäre sogar noch zutreffender. 'create_oval' ist nicht eine Instanz sondern eine Methode der Canvas-Instanz. Mit 'create_oval' gibst du der Canvas-Instanz den Auftrag ein Oval zu zeichnen. Beim erstellen des Oval's gibt die 'create_oval'-Methode eine ID-Nummer zurück (Integer), welche du im weiteren Programm zur Identifizierung des gezeichneten Grafikobjektes Oval verwenden kannst. Du kannst aber auch auf die ID-Nummer verzichten und bei Erstellung des Grafikobjektes mit 'create_oval' der Option 'tag' bzw. 'tags' einen plausiblen Namen als String hinzufügen und dann im weitern Programm mit diesem 'tag' arbeiten.

Erstellen des Grafikobjektes mit ID-Nummer:

Code: Alles auswählen

grafic_obj_id = canvas.create_oval(coords, options)
bzw. mit Tag(s):

Code: Alles auswählen

canvas.create_oval(coords, tag='oval', options)
Dann im Programm mit der ID-Nummer:

Code: Alles auswählen

canvas.coords(grafic_id, my_new_coords)
bzw. mit Tag:

Code: Alles auswählen

canvas.coords('oval', my_new_coords)
auf das Grafikobjekt zugreifen.

Übrigens, welche Tkinter-Dokumentation verwendest du?

Gruss wuf :wink:
Take it easy Mates!
yipyip
User
Beiträge: 418
Registriert: Samstag 12. Juli 2008, 01:18

@Goswin:
Da muss es im Kopf einmal "Klick" gemacht haben, um zu verstehen, dass das Tkinter-Canvas keine "banale" Zeichenflaeche ist, auf der man nur einfach Pixel faerbt, sondern ein Container, in dem man graphische Objekte (mit Identitaet!) plazieren und manipulieren kann.

Vielleicht koennte in dem Zusammenhang auch noch
http://www.python-forum.de/post-116169.html#116169
interessant sein.

:wink:
yipyip
yipyip
User
Beiträge: 418
Registriert: Samstag 12. Juli 2008, 01:18

ups, wuf war schneller...
:wink:
yipyip
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

wuf hat geschrieben:welche Tkinter-Dokumentation verwendest du?
Naja, die mitgelieferten Python-Funktionen

Code: Alles auswählen

help(tkinter.Canvas)
help(tkinter.Canvas.create_oval)
(die sollten doch auf meine Version am besten abgestimmt sein)
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hallo Goswin

Sorry mein Unwissen! Jetzt wird es peinlich. Ich habe diese Hilfe noch nie benutzt. Ich kann sie nicht einmal aufrufen. Wie lautet die ganze Befehlzeile für die Python-Hilfe.
Benutze: SuSE11.0, Python2.5, Kwrite, Bash-Terminal
Benutze nicht: Idle
Von wo aus, mit was oder aus welcher Umgebung startest du help(tkinter.Canvas)?

Gruss wuf :wink:
Take it easy Mates!
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

@wuf: Ich benutze Linux Ubuntu und öffne ein Terminal (Console):

Code: Alles auswählen

[Eingabe:] python3.1 [Enter]
...
[Eingabe:] import tkinter [Enter]
[Eingabe:] help(tkinter.Canvas) [Enter]
...
(es erscheint die gesamte Doku von Canvas,
unter anderem die Methode create_oval)
(wenn man bereits weiss, dass create_oval existiert,
geht es auch direkt:)
...
[Eingabe:] q
[Eingabe:] help(tkinter.Canvas.create_oval) [Enter]
...
Bei Python 2.5 sollte es so ähnlich sein, das Problem ist nur, dass tkinter nicht zur Standardbibliothek gehört und nicht immer ohne weiteres importiert werden kann. Aber du benutzt ihn ja bereits und müsstest ihn demzufolge auch importieren können.

Grüße, Goswin
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hallo Goswin

Danke für deine Hilfe. Jetzt kann ich es auch. Bin aber froh, dass ich diese Hilfe für tkinter noch nie angewendet habe.

Denn die Hilfe für dass folgende:

Code: Alles auswählen

coords(self, *args)
muss von einen OOP-Irritierten geschrieben worden sein.

Gruss wuf :wink:
Take it easy Mates!
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

Mir geht es jetzt nicht mehr um meine ursprüngliche Anwendung (die läuft jetzt dank eurer Hilfe), sonder daraum, die Canvas-Methode after() zu verstehen, was ich bisher nicht ganz fertigbringe. Im folgenden (auf wuf beruhenden und von mir eigens dafür verunstalteten) Code beginnt die Animation keineswegs nach x Millisekunden, sondern immer ganz am Ende der for-Schleife. Warum bloß?

Code: Alles auswählen

#!/usr/bin/python3

import tkinter as t
from math import pi,cos

class Oval(object):
    def __init__(self, canvas, cx, cy, rx, ry, color='black'):
        #
        self.canvas = canvas
        self.cx = cx
        self.cy = cy
        self.rx = rx
        self.ry = ry
        self.color = color
        #
        self.tt = 0
        self.old_coords = (
            self.cx-self.rx, self.cy-self.ry,
            self.cx+self.rx, self.cy+self.ry )
        self.graph_obj = self.canvas.create_oval(
            self.old_coords, fill=self.color)
        #
        self.animate()

    def animate(self):
        #  
        self.tt += 1
        self.xx = self.rx*cos(self.tt*pi/128)
        self.coords = (
            self.cx - self.xx,
            self.cy - self.ry,
            self.cx + self.xx,
            self.cy + self.ry
            )
        #
        self.canvas.coords(self.graph_obj, self.old_coords)
        self.canvas.update_idletasks()
        self.old_coords = self.coords


root = t.Tk()

canvas = t.Canvas(root,bg='white')
canvas.pack()

cx = 150; cy = 100; rx = 120; ry = 80
oval_blk = Oval(canvas, cx, cy, rx, ry)

cx = 250; cy = 140; rx = 80; ry = 120
oval_red = Oval(canvas, cx, cy, rx, ry, 'red')


for ii in range(10000):
    #zuerst alle prints, erst danach die Animation, aber warum?
    #
    print("Hallo1")
    canvas.after(0,oval_blk.animate)
    print("Hallo2")
    canvas.after(0,oval_red.animate)
    print("Hallo3")
print("Ende")

input()
Python nervt. (Nicht immer, und natürlich nur mich, niemals andere)
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hallo Goswin

Könntest du einen kurzen Beschrieb zusammenstellen was du mit dem Code-Stück erreichen willst?

Gruss wuf :wink:
Take it easy Mates!
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

@Goswin: Mit der after()-Methode erreichst du, dass eine von dir gewählte Funktion nach einer von dir festgelegten Zeit einmal aufgerufen wird. Im Unterschied etwa zu einer Schleife und einem time.sleep() blockiert das aber nicht den Programmablauf, sondern geschieht quasi im Hintergrund.

Um also eine animierte Darstellung zu erreichen, ohne den gesamten Programmablauf während dieser Zeit zu blockieren, brauchst du eine Funktion (vergleichbar einem Event-Handler - das Event liefert hier die after()-Methode), die "ein Stück Animation" produziert und sich am Ende mittels after() selbst wieder aufruft. Das ersetzt dann sozusagen die explizite Schleifenkonstruktion.
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

wuf hat geschrieben:Könntest du einen kurzen Beschrieb zusammenstellen was du mit dem Code-Stück erreichen willst?
@wuf: Das habe ich ja bereits (siehe auch die Signatur unten), aber du willst mir offenbar nicht glauben. Meine ursprüngliche Anwendung läuft bereits (mit Hilfe von Canvas.coords), da gibt es nichts mehr zu verbessern, da die Zeitpunkte meiner Bildveränderung ereignisgesteuert und keine voraussehbaren Intervalle sind. Ob und wann ich die Methode Canvas.after in Zukunft gebrauchen werde, das weiß ich selber nicht. Solange ich aber nicht richtig verstehe, wie sie funktioniert, werde ich sie sicher nicht verwenden können.

@numerix: Ja, das von after() gelieferte Event blockiert ganz offenbar nicht den Programmablauf, sondern geschieht im Hintergrund. Was mich erstaunt bei der Geschichte ist das Umgekehrte: der Programmablauf ist anscheinend imstande, die Eventbehandlung zu blockieren; die von mir gewählte Funktion kann garnicht nach der festgelegten Zeit aufgerufen werden, da die Schleife solches anscheinend verhindert. Die Funktion muss auf die Schleife warten, egal wie lange diese braucht um auszulaufen.
Python nervt. (Nicht immer, und natürlich nur mich, niemals andere)
Antworten