Zugriff per Modul auf Grafikobjekte

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
Jan42
User
Beiträge: 14
Registriert: Dienstag 24. Februar 2009, 20:20

Hallo,
Ich schreibe für Info ein rss-reader und habe alles bis auf die Ausgabe.
Der Grafikcode:

Code: Alles auswählen

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'gui.ui'
#
# Created: Wed Mar 18 18:12:28 2009
#      by: PyQt4 UI code generator 4.4.3
#
# WARNING! All changes made in this file will be lost!

from PyQt4 import QtCore, QtGui

class Ui_pthreader(object):
    def setupUi(self, pthreader):
        pthreader.setObjectName("pthreader")
        pthreader.resize(702, 545)
        self.TextEditAusgabe = QtGui.QPlainTextEdit(pthreader)
        self.TextEditAusgabe.setGeometry(QtCore.QRect(10, 50, 681, 441))
        self.TextEditAusgabe.setObjectName("TextEditAusgabe")
        self.widget = QtGui.QWidget(pthreader)
        self.widget.setGeometry(QtCore.QRect(10, 10, 681, 45))
        self.widget.setObjectName("widget")
        self.gridLayout = QtGui.QGridLayout(self.widget)
        self.gridLayout.setObjectName("gridLayout")
        self.label = QtGui.QLabel(self.widget)
        self.label.setObjectName("label")
        self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
        self.rss_link = QtGui.QLineEdit(self.widget)
        self.rss_link.setObjectName("rss_link")
        self.gridLayout.addWidget(self.rss_link, 0, 1, 1, 1)
        self.buttonSpeichern = QtGui.QPushButton(self.widget)
        self.buttonSpeichern.setObjectName("buttonSpeichern")
        self.gridLayout.addWidget(self.buttonSpeichern, 0, 2, 1, 1)
        self.widget1 = QtGui.QWidget(pthreader)
        self.widget1.setGeometry(QtCore.QRect(10, 500, 681, 45))
        self.widget1.setObjectName("widget1")
        self.gridLayout_2 = QtGui.QGridLayout(self.widget1)
        self.gridLayout_2.setObjectName("gridLayout_2")
        self.buttonAbrufen = QtGui.QPushButton(self.widget1)
        self.buttonAbrufen.setObjectName("buttonAbrufen")
        self.gridLayout_2.addWidget(self.buttonAbrufen, 0, 0, 1, 1)
        self.buttonBeenden = QtGui.QPushButton(self.widget1)
        self.buttonBeenden.setObjectName("buttonBeenden")
        self.gridLayout_2.addWidget(self.buttonBeenden, 0, 1, 1, 1)

        self.retranslateUi(pthreader)
        QtCore.QMetaObject.connectSlotsByName(pthreader)

    def retranslateUi(self, pthreader):
        pthreader.setWindowTitle(QtGui.QApplication.translate("pthreader", "Dialog", None, QtGui.QApplication.UnicodeUTF8))
        self.label.setText(QtGui.QApplication.translate("pthreader", "RSS-Link", None, QtGui.QApplication.UnicodeUTF8))
        self.buttonSpeichern.setText(QtGui.QApplication.translate("pthreader", "Speichern", None, QtGui.QApplication.UnicodeUTF8))
        self.buttonAbrufen.setText(QtGui.QApplication.translate("pthreader", "Abrufen", None, QtGui.QApplication.UnicodeUTF8))
        self.buttonBeenden.setText(QtGui.QApplication.translate("pthreader", "Beenden", None, QtGui.QApplication.UnicodeUTF8))

das Hauptprogramm:

Code: Alles auswählen

# -*- coding: cp1252 -*-
import sys
import rss_save
import urlcon
import search
import output
from PyQt4 import QtGui, QtCore
from gui import Ui_pthreader as Uipt

class MeinDialog(QtGui.QDialog, Uipt):
    def __init__(self):
        QtGui.QDialog.__init__(self)
        self.setupUi(self)

        self.connect(self.buttonSpeichern,
                     QtCore.SIGNAL("clicked()"), self.onSpeichern)
        self.connect(self.buttonBeenden,
                     QtCore.SIGNAL("clicked()"), self.onBeenden)
        self.connect(self.buttonAbrufen,
                     QtCore.SIGNAL("clicked()"), self.onAbrufen)
        self.TextEditAusgabe.setReadOnly(True)

    def onSpeichern(self):
        d = self.rss_link.text()
        rss_save.save(d)
        a = self.rss_link.setText('')

    def onBeenden(self):
        self.close()

    def onAbrufen(self):
        urlcon.connect()
        search.rssparse()
        output.display()

        
app = QtGui.QApplication(sys.argv)
dialog = MeinDialog()
dialog.show()
sys.exit(app.exec_())

und das Modul output, welches mir Sorgen bereitet:

Code: Alles auswählen

def display():
    from PyQt4 import QtGui, QtCore
    from pthreader import MeinDialog
    datei = "C:\Python25\geparsed.txt"
    in_file = open(datei, "r")
    weiter = True
    while weiter:
        line = in_file.readline()
        if line.find("abcde12345") !=-1:
            weiter = False
        else:
            MeinDialog.self.TextEditAusgabe.setPlainText(line)

Das letzte Modul macht folgendes. Die Module connect und search haben die wichtigen Sachen heruntergeladen und gefiltert.
Dieser fertige Text aus Nachrichten ist in der Datei geparsed.txt gespeichert.
Dieses Modul soll jetzt diese Datei öffnen, Zeile für Zeile auslesen und in das PlainTextEdit-Feld Zeile für Zeile eingefügen. Ich bekomm es aber nicht hin, dass das Modul auf die Grafikobjekte zugreift. Kann mir einer helfen? Es geht nur noch um die Ausgabe, wenn das Auszugebende schon in einer Datei existiert.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Hui, da sind aber einige Ungereimtheiten drin (habs nur mal schnell überflogen). Wenn ich Dich richtig verstanden habe (bzw. Deinen Code), willst Du von display() aus das Ganze starten. Hierzu meine Anmerkungen:
  • Imports sollten in der Regel nicht in Funktionen gemacht werden.
  • Du importierst zwar MeinDialog, instanziierst aber nicht. MeinDialog.self.Text..... ist ziemlicher Tobak, schau doch bitte mal in einschlägigen Python-Tutorials, wie das mit den Klassen in Python gemacht wird und was self bedeutet.
  • Deine GUI wird angezeigt. Jepp, weil in pthreader.py Code steht, der während des Imports ausgeführt wird. Siehe hierzu Python-Tutorials, Stichwort import.
  • gui.ui brauchst Du nicht mit pyuic zu übersetzen, besser ist es, das zur Laufzeit in Deiner MeinDialog-Klasse via uic-Loader zu machen. Da gibts hier ein paar Threads zu.
Der zweite und dritte Punkt sind der Grund für das vermeintliche Fehlverhalten.

Grüße, Jerch
Jan42
User
Beiträge: 14
Registriert: Dienstag 24. Februar 2009, 20:20

Gibt es eine Möglichkeit, das auch ohne Modul zu machen? Den Code direkt in die def onAbrufen schreiben?
Was ist instanzieren? In der PyQt4 Doku die ich nutze wird nicht wirklich erklärt, wie und warum die class MeinModul so geschrieben werden muss.
Es nutzt mir auch nicht, was self oder import genauer ist. Das weiß ich ja. Was ich nicht weiß, ist was ich jetzt importieren muss. Prinzipiell ja eigentlich die gesamte Grafik.
So nen Beispiel wär gut. Das versteht man leichter als sich irgendwo allgemeine Sachen durchzulesen ohne das man weiß, wie das auf eigene Programm angewendet wird.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Vergiß bitte Deine display()-Funktion, da stecken noch viel mehr Fehler drin (Dateihandling mit try-finally, Applikationsdaten ins Python verz?)

Ich kuck mal über Dein MeinDialog drüber, die Funktionalität ist hier am besten aufgehoben.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Ein paar Fragen noch:
Wie soll sich das Programm denn verhalten? Soll die Datei bei Aufruf einmal geparst werden oder in regelmäßigen Abständen? Manipuliert an der Datei ein anderes Programm rum, während es von der Gui geparst werden soll?
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Ok, hab jetzt erst gesehen, das Du display() ja in onAbrufen aufrufst. Das einfachste ist, hier den Aufruf mit folgendem Code zu ersetzen:

Code: Alles auswählen

datei = "C:\Python25\geparsed.txt"
with open(datei, "r") as file:
    for i in file:
        if i.find("abcde12345") !=-1:
            break
        self.TextEditAusgabe.setPlainText(i)
(Keine Ahnung, was Du mit dem find('abc123') und dem setPlainText() verfolgst)

Um das with in Python 2.5 nutzen zu können, mußt Du es noch importieren (ab 2.6 Standard):

Code: Alles auswählen

from __future__ import with_statement
Um zu verhindern, das der Code der oberen Ebene bei Modulimport ausgeführt wird, solltest Du den Code mit

Code: Alles auswählen

if __name__ == "__main__":
maskieren.
Jan42
User
Beiträge: 14
Registriert: Dienstag 24. Februar 2009, 20:20

Also die def onSpeichern speichert rss-links untereinander in einer textdatei.
Wenn man auf den Button Abrufen geht, greift ein Modul auf diese Datei zu und läd zu jedem link in der Datei den Inhalt als xml Dokument herunter.
Direkt nach dem runterladen, noch bevor der nächste Link kommt, müsste der parser einschreiten und das wichtigste rausfiltern und in eine seperate Datei schreiben. Direkt danach müsste das ausgegeben werden, noch bevor, die connect funktion den zweiten link geladen hat. also der parser müsste eigentlich in der function connect aufgerufen werden. Das hatte ich schon mal. Hat funktioniert. Doch hier müsste dann auch der Code zum Ausgeben aufgerufen werden. Daher wollte ich das per Modul machen, leider gabs da dann Probleme.

Edit: das abcde12345 ist einfach um zu erkennen, dass das das Ende der Datei ist. Nach Leerzeilen kann ich nicht suchen, da sie zwischendrin vorhanden sind. Anders weiß ich nicht wie man noch das ende der Datei erkenntlich machen kann. Sonst würde er ja unendlich leere Zeilen ausgeben. setPlain...soll laut meinem Pythonbuch der Ausgabebefehl für son großes Textfeld sein.

Edit2: Das Problem bei deiner Lösung wäre auch, dass ich nur ein einzigen Link nehmen darf. Weil bei mehreren einfach der letzte die ersten datein überschreibt. Daher müsste diese Art von Befehlen in die connectfunktion eingefügt werden.
Jan42
User
Beiträge: 14
Registriert: Dienstag 24. Februar 2009, 20:20

Vielen vielen dank. Statt der Abfrage mit readline hab ich einfach

Code: Alles auswählen

datei = "C:\Python25\geparsed.txt"
            with open(datei, "r") as file:
                text = file.read()
                self.TextEditAusgabe.setPlainText(text)
genommen. Aber jetzt bräuchte ich noch Hilfe dabei, wie ich diesen Code als Modul in der funktion connect ausführe. Das Problem ist die Sache, mit dem Import. Ich weiß nicht, was genau ich importieren soll, da ich bei dem Grafikmodul auch schon nicht genau weiß, was die einzelnen Befehle genau machen. So wie es jetzt ist, würde es nur für einen einzelnen Link gehen.

Gruß Jan.
Danke :)
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Was ist instanzieren? In der PyQt4 Doku die ich nutze wird nicht wirklich erklärt, wie und warum die class MeinModul so geschrieben werden muss.
Es nutzt mir auch nicht, was self oder import genauer ist. Das weiß ich ja. Was ich nicht weiß, ist was ich jetzt importieren muss.
Hoppla, wie mir scheint, fehlen Dir ein paar grundsätzliche Dinge in Sachen OOP (Objektorientierte Programmierung) und dessen Paradigmen.
Quellen zum Erlesen:
http://de.wikipedia.org/wiki/Objektorie ... rammierung
http://de.wikipedia.org/wiki/Objektorientierung
http://docs.python.org/tutorial/classes.html <-- das Ganze dann für Python

Auch bin ich mir nicht sicher, ob für Dich die "volle Breitseite" mit PyQt hier der richtige Weg ist, da Dir offensichtlich grundlegenderes Wissen zu OOP fehlt und PyQt nicht die besten Docs hat (viele C++ Bsp.)

zu Deinem connect-Problem:
Deine "__main__"-Kontext ist in pthreader.py und Deine Gui-MainThread-Klasse Dein MeinDialog. Versuche bitte nicht in urlcon.py irgendwas von MeinDialog zu importieren. Umgedreht wird ein Schuh draus. (Irgendwie habe ich das Gefühl, das Du die Kirche ums Dorf trägst.) Erklär bitte nochmal genau, was Du hier willst.

zum File-Ende:
Mit "for i in file" interiert Python automatisch bis zum Ende.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Noch eine Frage:
Warum der Umweg über eine Datei, wenn Du eh alles direkt hintereinander weg ausführst und die Datei komplett wieder einliest? (und bitte nicht ins Pythonverzeichnis mit sowas)
Jan42
User
Beiträge: 14
Registriert: Dienstag 24. Februar 2009, 20:20

Bis jetzt habe ich eine Liste von rss-links, die durch einzelnes speichern verschiedener Links mit dem Button Speichern und der Funktion

Code: Alles auswählen

def save(a):
    datei = "C:/Python25/rss_database.txt"
    in_file = open(datei, "a")
    in_file.write(a+"\n")
    in_file.close()
 
erstellt wird. Drückt man auf den Button Abrufen so wird zuerst die Funktion connect inistialisiert.

Code: Alles auswählen

def connect():
    import urllib
    datei = "C:/Python25/rss_database.txt"
    in_file = open(datei, "r")
    while True:
        url = in_file.readline()
        if len(url) == 0:
            break
        rss = urllib.urlretrieve(url, "rss.xml") 
    in_file.close()
Wie man sieht, arbeitet er nacheinander jede Zeile ab, und überschreibt so immer wieder die vorherige xml Datei mit dem nächsten Link.
Direkt nach dem runterladen müsste der Parser

Code: Alles auswählen

def rssparse():
    file = open("c:/Python25/rss.xml")
    weiter = True
    while weiter:
        line = file.readline()
        if len(line) == 0:
            weiter = False
        else:
            if line.find("<item>") != -1:
                line = file.readline()
                zeile = line
                for i in range(1,7):
                    if zeile.find("<title>") != -1:
                        a = zeile.find(">")
                        b = zeile.rfind("<")
                        Titel = zeile[a+1:b]
                    if zeile.find("<link>") != -1:
                        a = zeile.find(">")
                        b = zeile.rfind("<")
                        Link = zeile[a+1:b]
                    if zeile.find("<description>") != -1:
                        a = zeile.find(">")
                        b = zeile.rfind("<")
                        Description = zeile[a+1:b]
                    if zeile.find("<pubDate>") != -1:
                        a = zeile.find(">")
                        b = zeile.rfind("<")
                        Date = zeile[a+1:b-5]
                    if zeile.find("</item>") != -1:
                        break
                    zeile = file.readline()
                datei = "C:/Python25/geparsed.txt"
                in_file = open(datei, "a")
                in_file.write(Titel+"\n")
                in_file.write(Description+"\n")
                in_file.write(Date+"\n")
                in_file.write(Link+"\n\n\n")                
            if line.find("<entry>") != -1:
                line = file.readline()
                zeile = line
                for i in range(1,7):
                    if zeile.find("<title>") != -1:
                        a = zeile.find(">")
                        b = zeile.rfind("<")
                        Titel = zeile[a+1:b]
                    if zeile.find("<link") != -1:
                        a = zeile.find("=")
                        b = zeile.find("/>")
                        Link = zeile[a+2:b-2]
                    if zeile.find("<updated>") != -1:
                        a = zeile.find(">")
                        b = zeile.find("<")
                        Date1 = zeile[a+1:b-10]
                        Date2 = zeile[a+12:b-1]
                    if zeile.find("<summary>") != -1:
                        a = zeile.find(">")
                        b = zeile.rfind("<")
                        Summe = zeile[a+1:b]
                    if zeile.find("</entry>") != -1:
                        break
                    zeile = file.readline()
                datei = "C:/Python25/geparsed.txt"
                in_file = open(datei, "a")
                in_file.write(Titel+"\n")
                in_file.write(Summe+"\n")
                in_file.write(Date1+" "+Date2+"\n")
                in_file.write(Link+"\n\n\n")
    file.close()
    in_file.close()

initialisert werden, damit für jeden Link die entsprechende xml Datei gleich geparsed und das Ergebnis in der Datei geparsed.txt gespeichert wird. Leider überschreibt diese funkction die Datei auch mit jedem neuen Parsvorgang. Also müsste direkt nach dem Parser das ganz ausgegeben werden. Daher hier das Modul für die Ausgabe. Doch dabei gab es ja Probleme mit dem importieren.

Ich bin sicher das ist alles ganz umständlich gemacht. Man bedenke aber, das das mein erstes Projekt in Python ist. Ich habe mir die Sprache wegen diesem Projekt zu gemüte geführt. Also ist mein Wissen auch dementsprechen klein. Warscheinlich muss ich das Schleifenproblem bei der Abfrage einzelner rss-links nochmal überdenken. Vielleicht hast du Anregungen, inwiefern man das mittels objektorientierter Programmierung verbessern kann. Ich kann mir darunter nix konkretes vorstellen. Deine Links les ich mir durch.
Danke dafür. Ich hoffe du hast dem ganzen entnehmen können, worum es mir geht. Ich hatte auch probiert, für jeden Link einzelne Datein anzulegen, also extra xml und geparsed Datein. Doch damit kam ich nicht klar, bzw. hat das nicht so gefunzt.

Gruß Jan.
Dank für eure tolle Hilfe. Hat mir bereits viel gebracht!

Edit: An das hintereinander ohne Datei hab ich nicht gedacht. Wüsste auch nicht wie genau ich das realisiere. Wenn ich das alles hintereinander machen würde müsste ich mir ein neuen Parser basteln, da ich ja nicht in einem String Zeile für Zeile durchgehen kann, oder?
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Also ich bewundere ja Deinen Elan, mit dem Du Dich an so ein Projekt in einer neuen Sprache ranmachst (ein eigener XML-Parser :shock:). Leider erstreckt sich meine Begeisterung nicht auf Deinen Code, der lehrt mir eher das Fürchten (bitte nicht persönlich nehmen).

Dein Parser dürfte mit einem Großteil valider RSS-Feeds nicht klarkommen, da Du das XML falsch angehst. Ich möchte das auch nicht weiter ausführen, nur soviel dazu: Du kannst eine gültige XML-Datei komplett in eine Zeile packen, da nicht die Zeilenumbrüche sondern die Tags die Delimiter sind. Hier würde Dein Parser versagen. Von XML (lxml) bis hin zu fertigen RSS-Parsern gibt es eine Menge guter Bibliotheken für Python, die Dir das Leben einfacher machen können.

Ok, wie würde ich sowas angehen?
--Stichwort: Softwaredesign
Ich bin ein großer Fan der agilen Entwicklung, würde aber in einer so konkreten Fragestellung mich einiger Hilfestellungen aus der klass. Entwicklung bedienen:
- Zielstellung formulieren --> use cases entwerfen (Inspiration aus anderen ähnlich gelagerten Projekten)
- hieraus OOP-Entwurf stricken
- benutzbare Dritt-Libaries suchen --> Entwurf revidieren
- Implementation (inkl. unittests)
Das ist jetzt nur ein Grobschema F und variiert je nach Fragestellung. Das wirklich tolle an Python ist, das meist nicht viel zum Selberschreiben übrig bleibt. :D

Ich bin mir nicht sicher, ob Du mir noch folgen kannst. Wenn nicht, würde ich Dir dringend raten, mit einfachen Beispielimplementationen und kleinen Problemen unter Zuhilfenahme eines Tutorials in Python anzufangen.

Warum erzähle ich Dir das alles?
Leider sind in Deinem Code von race conditions (file handling) bis Designschwächen (Modularisierung mit import-Problemen, Überschreiben von Dateien) alles vertreten. Ich bin gerne bereit, Dir einzelne Fehler genauer aufzuzeigen, falls Du daran interessiert bist. Und wenn Du obige Überlegungen mit einbeziehst und "fertige" RSS-Parser einsetzt, dann sollte das Programm in Deiner Gui-Klasse mit ein paar Methoden zu stemmen sein.

Ich hoffe, das waren nicht zu viele negative Schwingungen, wollte Dich nicht abschrecken. :)
Grüße, Jerch
Jan42
User
Beiträge: 14
Registriert: Dienstag 24. Februar 2009, 20:20

Danke für deine Tipps. Ich werd gucken ob ich das umgesetzt bekomme. Doch erstma nicht in dem Umfang. Ich hab noch bis Dienstag Zeit. Bis dahin will ich es nur mit Hilfe von Klassen schaffen, mehrere Link anzeigen zu können. Sobald das Projekt abgegeben ist, dafür muss es nicht soo gut sein. Hauptsache es macht was es soll^^, werde ich es überarbeiten und noch Dinge hinzufügen.
So jetzt erstma zur Schule^^ Nachmittag dann OOP angucken
Gruß Jan
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Hier mal eine kurze Anregung von mir.
Datei "rssgui.ui":

Code: Alles auswählen

<ui version="4.0" >
 <class>RssGui</class>
 <widget class="QWidget" name="RssGui" >
  <property name="geometry" >
   <rect>
    <x>0</x>
    <y>0</y>
    <width>519</width>
    <height>379</height>
   </rect>
  </property>
  <property name="windowTitle" >
   <string>Form</string>
  </property>
  <layout class="QGridLayout" name="gridLayout" >
   <item row="0" column="0" >
    <widget class="QLineEdit" name="lineEdit" />
   </item>
   <item row="1" column="0" >
    <widget class="QTextBrowser" name="textBrowser" />
   </item>
  </layout>
 </widget>
 <resources/>
 <connections/>
</ui>
und die Python-Datei dazu:

Code: Alles auswählen

# -*- coding: utf-8 -*-
import sys
import feedparser
from PyQt4 import QtGui, QtCore, uic

class SimpleFeedReader(QtGui.QWidget):
	def __init__(self, parent=None):
		QtGui.QWidget.__init__(self, parent)
		uic.loadUi('rssgui.ui', self)
		self.connect(self.lineEdit,
			QtCore.SIGNAL("returnPressed()"), self.getFeed)
		# Bsp.-Feed
		self.lineEdit.setText('http://newsfeed.zeit.de/index')
 
	def getFeed(self):
		d = feedparser.parse(unicode(self.lineEdit.text()))
		if d.bozo == 1:
			self.textBrowser.setHtml('<h3>Keine gültigen \
				RSS-Daten.</h3>')
			return
		self.textBrowser.setHtml(''.join('<h3>' + i.title +
			'</h3>' + i.summary + '<hr>' for i in d.entries))

if __name__ == "__main__":
	app = QtGui.QApplication(sys.argv)
	widget = SimpleFeedReader()
	widget.show()
	app.exec_()
Zum Parsen der RSS-Daten nehme ich feedparser, die API ist sehr einfach zu bedienen, da alles in Dictionaries und Listen gepackt wird. Das Bsp. zeigt auch nur den Titel und die Zusammenfassung eines Feedeintrages. Man könnte das noch um Erstellungsdatum, Link zum Artikel, Author etc. erweitern. Die Daten hierfür bietet feedparser schön aufbereitet an.

Für einen "richtigen" Feedreader würde ich allerdings die GUI anders aufbauen und modelbasiert die Daten zur Anzeige bringen. Ich denke aber, daß das zuviel ist für Dich bis Dienstag und Du schon gut beschäftigt bist mit OOP & Konsorten.

Grüße, Jerch
Antworten