Wie mache ich das ohne globale Variable?

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Das ist etwa eine tkinter GUI:

Code: Alles auswählen

import tkinter as tk
from PIL import Image,ImageTk

class Application(tk.Tk):

    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        self.label_img = ImageTk.PhotoImage(Image.open('Images/Blumeklein.jpg'))
        self.label = tk.Label(self,image=self.label_img, text='label')
        self.label.pack()

Application().mainloop()
Solange jemand so etwas macht, interessiert es mich nicht.
Aber wenn jemand von DyntkInter importiert:

Code: Alles auswählen

import DynTkInter as tk
from DynTkInter import Image,ImageTk
Dann will ich darin bei Zeile 9 infolge von image=self.label_img den bei Zeile 8 durch ImageTk.PhotoImage(Image.open('Images/Blumeklein.jpg')) übergebenen Filenamen wissen.
BlackJack

@Alfons Mittelmeyer: Das geht nicht.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

BlackJack hat geschrieben:@Alfons Mittelmeyer: Das geht nicht.
Meinst Du, das geht nicht, oder es geht nicht ohne globale Variable?
BlackJack

@Alfons Mittelmeyer: Okay, ich sehe gerade das Du `Image` und `ImageTk` auch unter Kontrolle hast. Dann geht es natürlich und eigentlich offensichtlich doch. Dann kannst Du dem Rückgabewert von `Image.open()` den Namen ja mitgeben und abfragbar machen und `ImageTk.PhotoImage` kann den dann abfragen und seinerseits von sich abfragbar machen.
Sirius3
User
Beiträge: 17740
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: jetzt weiß ich wirklich nicht, wie hier globale Variablen weiterhelfen sollen.

Das einfachste ist wohl, eine Funktion dafür zu schreiben:

Code: Alles auswählen

def load_photoimage(filename):
    image = ImageTk.PhotoImage(Image.open(filename))
    image.filename = filename
    return filename
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Sirius3: Das geht natürlich aus zwei Gründen nicht. Hier lädst Du ein Photoimage. Dann hängst Du, den Filenamen dran. Könnte gehen, wenn das ein Objekt ist. Aber dann gibst Du den Filenamen zurück und wirfst das Photoimage weg. Das macht jetzt wenig Sinn. Naja, mann könnte es dann ja bei der Übergabe durch image=someimage nochmals neu aus dem Filenamen laden.

Aber der zweite Grund ist der Wichtigste. Ich hatte schon einmal eine Funktion, mit der ein Photoimage geladen wurde. Doch der Anwender soll keine vom normalen tkinter abweichende Funktion brauchen, sondern es in der gewohnte Weise handhaben können also wie in Zeile 8 und 9.

Eine Möglichkeit wäre schon, daß man nun jedes Photoimage in etwas anderes umwandelt, etwa ein Tuple aus Photoimage und Namen. Das wäre aber eine gewagte Änderung, die ich mir ja mal durch den Kopf gehen lassen kann.

Die jetzige relativ frische Lösung arbeitet mit zwei globalen Variablen. Eine davon kann man unbedenklich beseitigen, nämlich was Image.open('Images/Blumeklein.jpg') zurückliefert.

Zur Zeit liefert diese Funktion ein PIL image zurück und speichert den Filenamen in einer globalen Variablen, aus der sich dann ein bald nachfolgendes ImageTk.Photoimage den Namen abholt. Da müßte man mal schauen, ob man mit so einem PIL Image noch etwas anderes machen will, außer es lediglich an ImageTk.Photoimage zu übergeben.

Und dann gibt es eine zweite globale Variable, nämlich ein Dictionary mit key ein tkinter Photoimage und als Wert den Filenamen. Das muss man sich gründlicher überlegen, ob man das erwartete tkinter Photoimage in etwas anderes umwandelt. Sollte ich irgend etwas vergessen haben, habe ich eben Pech gehabt und bekomme den Filenamen nicht, aber wichtig ist, dass dann trotzdem das tkinter PhotoImage funktioniert.

Also die jetzige Lösung ist so:

So bindet der Anwender mein Image und ImageTk ein:

Code: Alles auswählen

import DynTkInter as tk
from DynTkInter import Image,ImageTk
Und so binde ich das dann in DynTkInter ein:

Code: Alles auswählen

import dyntkinter.DynTkPil as Image
import dyntkinter.DynTkPil as ImageTk
Und dort sieht man die globalen Variablen:

Code: Alles auswählen

from PIL import Image as Pil_Image
from PIL import ImageTk as Pil_ImageTk

def open(filename):
    global Pil_Imagefile
    Pil_Imagefile = filename
    return Pil_Image.open(filename)

def PhotoImage(pil_image):
    image = Pil_ImageTk.PhotoImage(pil_image)
    _image_dictionary[image] = Pil_Imagefile
    return image

def init(image_dict):
    global _image_dictionary
    _image_dictionary = image_dict
Das mit Pil_Imagefile mache ich wohl besser weg, weil das sowieso nicht funktionieren würde, wenn jemand zuerst mehrere PIL images speichert und erst danach mit ImageTkPhotoImage daraus tkinter PhotoImages macht.

Das Dictionary wegmachen und aus dem tkinter PhotoImage etwas anderes machen, wäre ein Änderung mit weitreichenden globalen Folgen, nur um eine globale Variable wegzumachen. Das muss gut überlegt werden.

Es könnte sich ja jemand das PhotoImage wieder aus somelabel['image'] holen und es dann anders nochmals weiterverwenden. Da müßte ich auch so etwas kontrollieren anstatt es einfach tkinter zu überlassen. Da müßte ich also dann das gewandelte PhotoImage im Widget speichern und dann alles damit kontrollieren.
Sirius3
User
Beiträge: 17740
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: dass das mit dem return ein Schreibfehler war, darauf hättest Du auch selbst kommen können.

Deine Lösungsversuche haben schon einen richtigen Kern, wenn Du das Interface erhalten möchtest, mußt Du die neue Funktion einfach so nennen, wie die alte:

Code: Alles auswählen

def PhotoImage(pil_image):
    image = Pil_ImageTk.PhotoImage(pil_image)
    image.filename = getattr(pil_image, 'filename', None)
    return image
Das `getattr` ist deshalb nötig, weil man der Funktion auch andere Images übergeben kann, die kein `filename`-Attribut haben.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Also was sein muss:
- Volle Funktionalität für PIL.Image: das sind etliche Funktionen außer lediglich open
- Und für PIL.ImageTk gibt es auch noch BitmapImage

Das heißt also:

Code: Alles auswählen

from PIL.Image import *
from PIL.Image import open as image_open

from PIL.ImageTk import BitmapImage as BitmapImage
from PIL.ImageTk import PhotoImage as imagetk_photoimage


def open(filename,mode):
    global pil_imagefile
    pil_imagefile = filename
    return image_open(filename,mode)


def PhotoImage(pil_image,**kwargs):
    image = imagetk_photoimage(pil_image,**kwargs)
    _image_dictionary[image] = pil_imagefile
    return image

def init(image_dict):
    global _image_dictionary
    _image_dictionary = image_dict
Aus dem PIL.Image darf ich nicht anderes machen, denn es gibt dafür auch diverse Methoden wie rotate.
Und aus dem PhotoImage darf ich auch nichts anderes machen, denn es gibt auch ttk, von dem ich bisher gar nichts kontrolliere.
Um auf das Dictionary verzichten zu können, müßte ich alles ohne Ausnahme kontrollieren
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Und so sollte es dann perfekt sein:

Code: Alles auswählen

from PIL.Image import *
from PIL.Image import open as image_open

from PIL.ImageTk import BitmapImage as BitmapImage
from PIL.ImageTk import PhotoImage as imagetk_photoimage


def open(filename,mode):
    global pil_imagefile
    image = image_open(filename,mode)
    _pil_image_files[image] = filename    
    return image


def PhotoImage(pil_image,**kwargs):
    image = imagetk_photoimage(pil_image,**kwargs)
    image_file = _pil_image_files.pop(pil_image,None)
    if image_file:
        _image_dictionary[image] = image_file
    return image


def init(image_dict):
    global _image_dictionary, _pil_image_files
    _image_dictionary = image_dict
    _pil_image_files = {}
Sirius3
User
Beiträge: 17740
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: hast Du meine Lösung überhaupt angeschaut? Die ist perfekt und braucht keine globalen Variablen.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Sirius3: danke, das mit meinem und Deinem posten hatte sich überschnitten. Das scheint dann die perfekte Lösung zu sein. In DyntkInter hatte ich image = nicht benützt, sondern photoimage = filename und das image und den Filenamen dann beim widget gespeichert.

Mit Deiner Lösung kann ich das neue dictionary wieder herauswerfen. Aber nicht heute.

Da kann ich dann auch das gesonderte Speicher ersparen, weil kich dann widget mit Filenamen aus widget['image'] bekomme.

Beim Canvas allerdings bekomme ich nach create_image, wenn ich das nachher abfrage nur einen Imagenamen wie 'py_image1'.
Kann mann da dann auch daraus auf das Image kommen?

Zur Zeit habe ich beim Canvas ein lokales Dictionary dafür. wenn ich von 'py_image1' auf das Image käme, könnte ich mir auch dieses ersparen.

Die eine globale Variable ist dann mal weg. Die andere erfordert einige Arbeit:

Code: Alles auswählen

from PIL.Image import *
from PIL.Image import open as image_open

from PIL.ImageTk import BitmapImage as BitmapImage
from PIL.ImageTk import PhotoImage as imagetk_photoimage


def open(filename,mode):
    image = image_open(filename,mode)
    image.filename = filename
    return image

    
def PhotoImage(pil_image):
    image = Pil_ImageTk.PhotoImage(pil_image)
    image.filename = getattr(pil_image, 'filename', '')
    if image.filename:
        pil_image.filename = None
    
    # not neccessary later
    if image.filename:
        _image_dictionary[image] = image.filename
        
    return image

# not neccessary later
def init(image_dict):
    global _image_dictionary
    _image_dictionary = image_dict
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Sirius3: Danke, habe jetzt auch dieses zweite globale Dictionary beseitigt. Das waren nur ein paar Zeilen, in denen es vorkam. Beim Widget merke ich mir durch widget.photoimage den Filenamen. Das bräuchte ich jetzt auch nicht mehr. Nur das zu ändern wäre etlicher Aufwand mit einer ganzen Menge von Zeilen.
Antworten