Προς το περιεχόμενο

python 3.2.3 + tkinter + cx_freeze


migf1

Προτεινόμενες αναρτήσεις

  • Απαντ. 41
  • Δημ.
  • Τελ. απάντηση

Συχνή συμμετοχή στο θέμα

Executable σε python3 δεν έχω δοκιμάσει. Σε python2 με PyQt το είχα κάνει χωρίς ιδιαίτερα προβλήματα μέσω του py2exe αλλά νομίζω ότι το py2exe δεν παίζει σε python3. Αν θες πάντως μπορώ να σου ανεβάσω ένα μικρό παράδειγμα μαζί με το setup.py

 

Υπάρχουν και άλλες επιλογές, αλλά έχω καιρό να ασχοληθώ και δεν έχω υπόψη μου αν έχει αλλάξει κάτι τον τελευταίο καιρό. Μια άλλη λύση που μπορείς να δοκιμάσεις είναι το nuitka μέσω του standalone mode, ενώ πρέπει να γίνεται και με Cython. Δεν έχω δοκιμάσει κανένα από τα δύο. Παρακολουθώ τη mailing list του nuitka και ξέρω ότι δουλεύει για python2 + PyQt. Python3 νομίζω δουλεύει αναλογα με ποια modules χρησιμοποιείς. Το καλό του nuitka είναι ότι είναι πολύ εύκολο να το δοκιμάσεις. Δεν θέλει ούτε setup.py ούτε τίποτα. Μία εντολή σε κονσόλα είναι. To Cython είναι αρκετά ώριμο project και εικάζω ότι θα δουλεύει χωρίς πολλά πολλά. Έχουν και παράδειγμα

 

tip: Η Python 3.2 είναι η πρώτη έκδοση της σειράς 3.# που μπορεί να χρησιμοποιηθεί σε πραγματικό project. H Python 3.0 και η 3.1 ήταν μεταβατικές εκδόσεις. H 3.2 ήταν ας πούμε η πρώτη stable, συνήθως όμως προτείνουν την 3.3. Πριν κανά μήνα βγήκε και η 3.4. Αν δεν έχεις έχεις κάποιον ιδιαίτερο λόγο να μείνεις στην 3.2, πήγαινε είτε σε 3.3+ είτε σε 2.7.6

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Thanks για την απάντηση.

 

Μόλις έβαλα Python 3.4 και κατέβασα την αντίστοιχη, latest version του cx_freeze. Ο installer του cx_freeze μου έγραψε πως δεν μπόρεσε να ολοκληρωθεί και πως το σύστημά μου δεν ενημερώθηκε (το ίδιο μου είχε πει και ο installer του cx_freeze για την Python 3.2.3, αλλά όταν κατέβασα παλιότερη έκδοση του cx_freeze που να υποστηρίζει Python 3.2 εγκαταστάθηκε χωρίς πρόβλημα, αλλά το executable που μου έφτιαχνε δεν λειουργούσε).

 

Δεν ολοκληρώθηκε ο installer του cx_freeze για την Python 3.4 λοιπόν, αλλά παραδόξως μου δούλεψε τελικά (αλλά χρειάστηκε να βάλω χειροκίνητα τα διάφορα assets -εικόνες, μουσικές, κλπ- στον build φάκελο που φτιάχνει το cx_freeze). Το μόνο πρόβλημα (προς το παρόν) είναι πως μου ανοίγει και την κονσόλα των Windows. Αυτό εδώ: http://stackoverflow.com/a/11374527δεν λειτούργησε στη δική μου περίπτωση (δεν δοκίμασα όμως αυτό εδώ: http://stackoverflow.com/a/3237924)

 

Βασικά, ο installer της Python θέλει να τη βάλει στο C:\PythonX αλλά εγώ του λέω να τη βάλει στο C:\Program Files\PythonX. Οπότε θα δοκιμάσω να την εγκαταστήσω όντως σε path που δεν περιέχει κενό, και θα επανέλθω.

 

EDIT:

 

Λοιπόν, χωρίς κενά στο path της εγκατεστημένης Python, ο installer του cx_freeze δουλεύει μια χαρά (κουφό όμως αυτό, δηλαδή στις παλιότερες versions του να μην είχε πρόβλημα με τα κενά και στις πρόσφατες να έχει).

 

Το θέμα με το άνοιγμα της κονσόλας όμως στο generated executable παραμένει. Βασικά είδα πως το cx_freeze δέχεται διάφορες παραμέτρους (συμπεριλαμβανομένων των paths για τα assets -αν το κατάλαβα καλά-_

 

Μάλλον όλες αυτές τις παραμέτρους μπορείς να τις ορίσεις μέσα στο setup.py script... προφανώς πρέπει να κάτσω να διαβάσω επιμελώς το documentation του cx_freeze. Προς το παρόν θα το αφήσω έτσι και βλέπουμε.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Λοιπόν, όλα ΟΚ!

 

Τελικά είχα λάθος στο setup.py. To σωστό το βρήκα εδώ: http://cx-freeze.readthedocs.org/en/latest/distutils.html

 

Έφτιαξα κι ένα πρόχειρο Guess the Number με Tkinter...

 

 

 

import tkinter
import tkinter.messagebox

from random import randint

#-----------------------------------------------------
def is_valid_guess( n ):
	try:
		return 1 <= int(n) <= 10
	except:
		return False

#-----------------------------------------------------
class Gui:
	def __init__( self, r  ):
		self.r = r
		self.i = 0

		self.mw = tkinter.Tk()                   # mw for main window
		self.mw.geometry( '+250+200')
		self.mw.resizable( 0,0 )
		self.mw.title( 'Μάντεψε τον Αριθμό' )
		self.mw.wm_iconbitmap( 'guess.ico' )

		# create a frame as main grid container
		self.frame =  tkinter.Frame( self.mw )
		self.frame.grid()

		self.prompt = tkinter.Label( self.frame, text= 'Μάντεψε αριθμό από 1 έως 10:' )

		self.strGuess = tkinter.StringVar()
		self.strGuess.set( '' )
		self.guess = tkinter.Entry( self.frame, textvariable= self.strGuess, bd= 5, width= 3 )
		self.guess.focus()

		self.ok = tkinter.Button( self.frame, text='ΟΚ', command= self.onBtnClickOk )
		ok = self.ok
		self.mw.bind( '<Return>', (lambda e, ok= ok: ok.invoke()) )

		self.rounds = tkinter.Label( self.frame, text='Προσπάθειες: 0' )

		self.exit = tkinter.Button( self.frame, text='Έξοδος', command= self.onBtnClickExit )

		self.status = tkinter.Label( self.frame, text= '', bg= 'light gray' )

		# put created widgets on the grid layout
		self.prompt.grid( row= 0, column= 0 )
		self.guess.grid( row= 0, column= 1 )
		self.ok.grid( row= 0, column=2 )
		self.exit.grid( row= 0, column= 3 )
		self.rounds.grid( row= 0, column= 4 )
		self.status.grid( row= 1, column= 0, columnspan= 5, sticky= 'N'+'S'+'E'+'W' )

		self.mw.mainloop()

	#---------------------------------------------
	def reset( self ):
		self.r = randint( 1,10 )
		self.i = 0
		self.mw.attributes("-alpha", 1.0)
		self.strGuess.set( '' )
		self.ok.configure( state= tkinter.ACTIVE )
		self.exit.configure( state= tkinter.ACTIVE )
		self.rounds.configure( text= 'Προσπάθειες: 0' )
		self.status.configure( text= '', bg= 'light gray' )
	#---------------------------------------------
	def quit( self ):
		self.mw.destroy()
	#---------------------------------------------
	def onBtnClickExit( self ):
		self.quit()
	#---------------------------------------------
	def onBtnClickOk( self ):
		
		if ( not is_valid_guess( self.strGuess.get() ) ):
			self.status.configure( bg= 'yellow', text='Άκυρος αριθμός, προσπάθησε ξανά...' );
		else:
			g = int( self.strGuess.get() )
			r = self.r
			if r > g:
				self.status.configure( bg= 'deep sky blue', text= 'Το {0} είναι μικρό'.format(g) )
			elif r < g:
				self.status.configure( bg= 'tomato', text= 'Το {0} είναι μεγάλο'.format(g) )
			else:
				self.status.configure( bg= 'pale green', text='ΒΡΗΚΕΣ ΤΟΝ ΑΡΙΘΜΟ {0}'.format(r) )
				self.mw.attributes("-alpha", 0.5)
				self.ok.configure( state= tkinter.DISABLED )
				self.exit.configure( state= tkinter.DISABLED )
				if ( tkinter.messagebox.askyesno("Ερώτηση", "Θα παίξεις ξανά;") ):
					self.reset()
					return;
				else:
					self.quit()
					return
			self.i += 1
			self.rounds.configure( text= 'Προσπάθειες: {0}'.format(self.i) )

		self.guess.delete( 0, tkinter.END )

#-----------------------------------------------------
if __name__ == "__main__":
	app = Gui( randint(1,10) )

 

Με τούτο εδώ το setup.py για το cx_freeze (το έχω ονομάσει cx_freeze_gui.py)...

 

 

# Use as: python cx_freeze_gui.py build

import sys
from cx_Freeze import setup, Executable

# Dependencies are automatically detected, but it might need fine tuning.
#build_exe_options = {"packages": ["os"], "excludes": ["tkinter"]}

# GUI applications require a different base on Windows (the default is for a
# console application).
base = None
if sys.platform == "win32":
    base = "Win32GUI"    # Tells the build script to hide the console.

setup(  name = "guess",
        version = "0.1",
        description = "Guess the Number",
#        options = {"build_exe": build_exe_options},
        executables = [Executable("guess.pyw", base=base)])

 

και με το command-line που έχω σε comment στην αρχή, φτιάχνει το executable (guess.exe) μαζί με τα dependencies σε έναν φάκελο: build\exe.win32-3.4

 

Απλώς μέχρι να πειραματιστώ με τα path parameters, πρέπει να βάζω τα assets μου στον παραπάνω φάκελο χειροκίνητα (στο παράδειγμα αυτό, το μόνο asset είναι το guess.ico, το οποίο δεν το κάνω attach εδώ πέρα... σβήστε τη σχετική γραμμή στον κώδικα αν θελήσετε να τον δοκιμάσετε).

 

Χρησιμοποίησα την τελευταία 32μπιτη έκδοση του cx_freeze για Windows, δηλαδή τον installer: cx_Freeze-4.3.3.win32-py3.4.msi

 

Τελικά, δεν ήταν κάτι το τραγικό. Απεναντίας, θα έλεγα πως είναι κι εύκολο κιόλας. Μάλλον ήμουν σε άσχημη μέρα εγώ.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

:lol:

 

Να 'ταν μόνο το return, αν τον ψειρίσεις τον κώδικα (σιγά τον κώδικα δηλαδή) μάλλον θα βρεις αρκετή C μέσα του :lol: Βασικά, το 'γραψα τώρα πρόχειρα για να 'χω κάτι μικρό να δοκιμάζω με τα διάφορα tools που μου έγραψες στο 1ο σου ποστ περί executable generation (ή και με άλλα).

 

Έχω σκοπό δηλαδή να ψαχτώ κι άλλο με τα stand alone executables.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Βασικά αναφερόμουν στο semicolon. Το return εννοείται ότι χρειάζεται εκεί που το έβαλες.

 

Εντάξει αν εξαιρέσεις τo style που δεν είναι PEP8 compliant (όπως το 90% του κώδικα σε Python), ψιλοπράγματα βλέπω. Πχ το alias μπορείς να το ορίσεις και έτσι για να γλυτώσεις μια γραμμή

		self.ok = ok = tkinter.Button( self.frame, text='ΟΚ', command= self.onBtnClickOk )

Όταν θες να κάνεις string formatting, αν είναι μόνο για ένα δύο arguments μπορείς να χρησιμοποιήσεις απλά το %. Πχ

text='ΒΡΗΚΕΣ ΤΟΝ ΑΡΙΘΜΟ %d' % r

Η μέθοδος str.format() σου δίνει μεγαλύτερη ευελιξία αλλά για τα απλά είναι νομίζω λίγο overkill. Εκεί που είναι πολύ χρήσιμη είναι όταν θες να χρησιμοποιήσεις πολλές φορές το ίδιο argument. Και με το % γίνεται αυτό αλλά δεν είναι τόσο ωραία η σύνταξη. Πχ

text = 'Mr {last_name} and Mrs {last_name}, I welcome you to our house'.format(last_name="Smith")

Αν δοκιμάσεις και άλλες βιβλιοθήκες, πόσταρε τα αποτελέσματα αν δεν βαριέσαι.

 

 

 

 

 

 

PS. Α ναι! καλείς και την self.strGuess.get() δύο φορές. Πάει το performance :P

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Thanks για τα tips! Ούτε το % το ήξερα, ούτε το κολπάκι με τα multiple {last_name}.

 

Για την self.strGuess.get(), επειδή με κόλλησε σε εκείνο το σημείο συνδυασμένη με το validation που πρέπει να κάνουμε, για πες πως μπορούμε να το βελτιώσουμε. Θέλησα να κρατήσω στο minimum τους ελέγχους (όχι για performance, αλλά για readability) αλλά μάλλον δεν τα κατάφερα καλά :)

 

Εννοείται πως θα γράψω ότι άλλο βρω για stand-alone exectuables.

 

ΥΓ. Καλά ρε, το ότι περνάω τον r ως παράμετρο στον constructor του Gui στην main() μόνο και μόνο για να τον κάνω copy στον self.r κι από εκεί και πέρα δουλεύω μονάχα με τον self.r δεν το είδες; :lol:... αυτό κι αν είναι πατάτα :P

 

EDIT:

 

...

 

Για την self.strGuess.get(), επειδή με κόλλησε σε εκείνο το σημείο συνδυασμένη με το validation που πρέπει να κάνουμε, για πες πως μπορούμε να το βελτιώσουμε. Θέλησα να κρατήσω στο minimum τους ελέγχους (όχι για performance, αλλά για readability) αλλά μάλλον δεν τα κατάφερα καλά :)

...

Μάλλον κάπως έτσι...

	def onBtnClickOk( self ):
		g = self.strGuess.get();
		if ( not is_valid_guess( g ) ):
			self.status.configure( bg= 'yellow', text='Άκυρος αριθμός, προσπάθησε ξανά...' );
		else:
			g = int( g )
			...
Τα παρατάω τώρα, γιατί ταγκάνιασα!
Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Για να μην ανοίγω νέο θέμα, υπάρχει κάποια πρακτική διαφορά αν ορίζουμε attributes μέσα ή έξω από τον constructor μιας κλάσης;

 

Π.χ.

class Foo:
    attr1 = 0
    attr2 = 0

    def __init__( self ):
        self.attr3 = 0
        ...
vs

class Foo:
    def __init__( self ):
        self.attr1 = 0
        self.attr2 = 0
        self.attr3 = 0
        ...
EDIT:

 

Μόλις το γκούγλαρα με καλύτερα keywords, μεταξύ άλλων βρήκα κι αυτό: http://stackoverflow.com/questions/207000/python-difference-between-class-and-instance-attributes

Οπότε υπάρχει σημαντική, πρακτική διαφορά.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Δημοσ. (επεξεργασμένο)

Για αυτό που θες να κάνεις εσύ πιθανότατα δεν έχει σημασία, αλλά ναι υπάρχουν διαφορές. Αν τα ορίσεις μέσα στην __init__ τότε είναι instance attributes, αν τα ορίσεις απευθείας μέσα στο block της κλάσης τότε είναι class attributes. Τα class attributes είναι κάτι σαν τα static member variables της C++.

 

Πάρε για παράδειγμα τον ορισμό αυτής της κλάσης

class C(object):
    a = 1
    def __init__(self, :
        self.b = b

Στην Python τα πάντα είναι αντικείμενα. Ακόμα και οι κλάσεις. Αφού το a είναι class attribute μπορούμε να το χρησιμοποιήσουμε. Το b όμως όχι γιατί η κλάση δεν γνωρίζει τίποτα για αυτό μέχρι να δημιουργηθεί μία instance.

print(C.a)    # θα τυπώσει 1
print(C.    # θα σηκώσει AttributeError

Ας ορίσουμε τώρα 2 instances

ins1 = C(1)
ins2 = C(2)

Τα instances έχουνε πρόσβαση και στο a και στο b

print(ins1.a, ins1.     # θα τυπώσει 1, 1
print(ins2.a, ins2.     # θα τυπώσει 1, 2

Τι γίνεται αν κάνουμε assign στο a του ενός instance;

ins1.a = 3

print(ins1.a, ins1.     # θα τυπώσει 3, 1
print(ins2.a, ins2.     # θα τυπώσει 1, 2

(για την ακρίβεια το τι ακριβώς γίνεται από πίσω στο ins1.a = 3 είναι λίγο περίπλοκο αλλά μας αρκεί για την ώρα το ότι δουλεύει όπως περιγράφουμε)

 

Τι γίνεται όμως αν κάνουμε assign κατευθείαν στο a της κλάσης;

C.a = 0

print(ins1.a, ins1.     # θα τυπώσει 0, 1
print(ins2.a, ins2.     # θα τυπώσει 0, 2

Βλέπουμε δηλαδή ότι αλλάζει η τιμή του a όλων των instances της κλάσης C!

 

Αν φτιάχνεις ένα απλό GUI, δηλαδή κάτι στο οποίο πιθανότατα θα έχεις ένα και μόνο instance, τότε μάλλον δεν έχει καμία ουσιαστική διαφορά. Εν γένει, εγώ θα προτιμούσα τα instances attributes εκτός και αν είχα συγκεκριμένο λόγο να χρησιμοποιήσω class attributes.

Επεξ/σία από pmav99
  • Like 2
Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Ευχαριστώ, μάλλον όμως έγραφες την ώρα που έκανα EDIT (το είχα βρει ήδη δηλαδή στο link που έβαλα στο EDIT). Καλό είναι όμως να υπάρχει γραμμένο και στα ελληνικά στο νήμα, για παιδιά που δεν τα πάνε πολύ καλά με τα αγγλικά.

 

Γενικώς πάντως κι άσχετα με αυτή μου την ερώτηση γίνεται ένας ψιλο-χοντρο χαμός (και) στην Python, όπως δηλαδή στις περισσότερες scripting γλώσσες με loose types.

 

Π.χ. αυτό το inconsistency με τα shallow και τα deep copies ανάλογα όχι μόνο με το αν έχουμε mutable ή immutable objects, αλλά και με το αν έχουμε nested structure ή όχι είναι μεγάλη παπαριά! Μπορεί να κάνει sense, αλλά εξακολουθεί να είναι παπαριά IMHO.

 

Τέσπα, έχω μπει στο τριπάκι να εμβαθύνω λίγο με το Tkinter, το οποίο btw δεν είναι καθόλου κακό, το αντίθετο μάλιστα (δεν ξέρω αν ήταν πάντα τόσο καλό ή το έφτιαξαν στις τελευταίες εκδόσεις του, γιατί δεν είχα ξανασχοληθεί).

 

Το βασικό μου παράπονο μέχρι στιγμής είναι πως δεν έχω βρει έναν GUI Designer της προκοπής. Αλλά το grid layout manager του API απλά τα σπάει (!) Γενικώς μέχρι στιγμής μου φαίνεται πολύ καλό GUI API, τουλάχιστον για μικρο-μεσαία projects.

 

ΥΓ. Ότι άλλες απορίες προκύψουν (που θα προκύψουν) είτε για την Python είτε για το Tkinter/Tk θα τις ποστάρω εδώ. Βασικά ότι κάνω το κάνω με αυτόν τον toy κώδικα του προηγούμενου ποστ.

 

Μέχρι στιγμής έχω καταφέρει και on-the-fly internationalization μεταξύ αγγλικών κι ελληνικών (με το gettext).

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Για το internationalization μπορείς να δεις και το babel

 

Σχετικά με το mutability και immutability μπορείς να δεις και αυτό. Ο όρος inconsistency δεν νομίζω ότι είναι ο πλέον κατάλληλος. Όπως σε όλα τα πράγματα σε αυτή τη ζωή, κάτι δίνεις και κάτι παίρνεις.

 

Δεν έχω ασχοληθεί πάρα πολύ με το Tkinter. Παλιά είχε διάφορα θέματα με το theming· ήταν δύσκολο να έχεις native widgets. Νομίζω ότι πλέον τα πράγματα είναι αρκετά καλύτερα. Ένα άλλο θέμα που υπήρχε ήταν το documentation. Στο νετ έβρισκες διάφορα outdated πράγματα. Κυρίως γιατί ήταν 1-1 μετάφραση από Tk. Πλέον είναι σίγουρα καλύτερα τα πράγματα. Σχετικά πρόσφατα κυκλοφόρησαν και κανένα 2 βιβλία, αλλά δεν τα έχω κοιτάξει.

 

Το βασικό προσόν του Tkinter είναι ότι είναι στην Standard Library. Προσωπικά προτιμώ το PyQt.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Για τα GUI, εγώ έχω εμπειρία με το GTK+ σε C, οπότε naturally θα επέλεγα PyGObect. Αλλά επειδή το Tkinter δεν χρειάζεται έξτρα εγκαταστάσεις, το προτίμησα (προς το παρόν δεν το έχω μετανιώσει, αλλά μιλάμε πάντα για toy project... θα δείξει).

 

Για τον ίδιο ακριβώς λόγο προτίμησα και το gettexet έναντι των άλλων εναλλακτικών (δηλαδή και το ξέρω ήδη και παρέχεται ως στάνταρ στην Python).

 

Για το "inconsistent", ναι, τα ήξερα ήδη αυτά, αλλιώς δεν θα έπαιρνα θέση ;). Σε κάποιον που είναι εξοικειωμένος με pointers (όπως κι εγώ) "it makes sense" (όπως έγραψα ήδη στο προηγούμενο post) αλλά υποτίθεται πως πας σε very high level language για να μην σε απασχολούν τα internal implementations (δηλαδή τα ενδότερα των state diagrams στη διάλεκτο της Python).

 

Έχω την εντύπωση πως η Pythonistas επενδύουν πάνω στην ευκολία της γλώσσας για την προώθησή της, οπότε το συγκεκριμένο νομίζω μπορούμε να το χαρακτηρίσουμε ως "inconsistency" και υπό αυτή την έννοια (αλλά και υπό την ακριβή του έννοια, ότι δηλαδή αν δεν ξέρεις από pointers δυσκολεύεσαι να καταλάβεις γιατί το = τη μια συμπεριφέρεται έτσι και την άλλη αλλιώς).

 

Ακόμα και εξοικειωμένος να είσαι, είναι θαρρώ τόσοι πολλοί οι συνδυασμοί περιπτώσεων που συμπεριφέρεται διαφορετικά, που δεν είναι καθόλου δύσκολο να την πατήσεις ακόμα κι αν είσαι έμπειρος.

 

Κατά την άποψή μου, η γλώσσα Θα ήταν πολύ πιο consistent σε αυτό το σημείο, αν για παράδειγμα έκανε παντού deep copy (που είναι η πιο safe εκδοχή), και σου έδινε τη δυνατότητα να επιλέξεις εσύ πότε κι αν θα κάνεις shallow copy (ενδεχομένως με κάποιο συντακτικό βοήθημα).

 

Τώρα αλλού κάνει έτσι, αλλού κάνει αλλιώς, κι αλλού κάνει και έτσι και αλλιώς (και για να είσαι πλήρως "οπλισμένος" πρέπει όχι μόνο να θυμάσαι που γίνεται τι, αλλά και να κάνεις import και να χρησιμοποιείς το copy module).

 

Όπως είπα, καταλαβαίνω τα αίτια των επιλογών που έχουν κάνει οι devs της γλώσσας για το συγκεκριμένο (και για άλλα) -εν πολλοίς πρόκειται για trade-offs μεταξύ πόρων, ταχύτητας και ασφάλειας- απλά το θεωρώ ασυνεπές για μια τόσο high level γλώσσα.

 

Άσχετο, ένα άλλο κουφό (αν και πλήρως συνεπές μέσα στη γλώσσα :) ) είναι πως το len() παρέχεται μονάχα ως built-in function (δηλαδή καθόλου ως oop μέθοδος μέσα στις built-in κλάσεις της γλώσσας.

 

Όπως και να ΄χει, η γλώσσα έχει πολλά συν για αυτό είναι και τόσο δημοφιλής. Αλλά κουβέντα κάνουμε, και χρήσιμο είναι νομίζω να επισημαίνουμε και μειονεκτήματά της.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Λοιπόν προχωράει το toy-project (όποτε βρίσκω χρόνο). Λειτουργικά έχει τελειώσει εδώ και μέρες, αλλά κάνω συνέχεια re-factoring όσο μαθαίνω τόσο το Tkinter όσο και την Python. Αυτή τη στιγμή ο κώδικας αλλού είναι imperative, αλλού είναι oop, αλλού είναι functional... αχταρμάς. Το "απέξω" δείχνει κάπως έτσι (το πίσω είναι αχνό γιατί μπροστά υπάρχει ανοιχτό dialog, εγώ του έχω πει να συμπεριφέρεται έτσι)...

 

 

post-38307-0-73531000-1400257989_thumb.png

 

Την τελευταία φορά που άλλαξα τον κώδικα ήταν για να αυτονομήσω πλήρως το GUI, και αντιμετώπισα πρόβλημα. Βασικά το είχα διαχωρίσει εξαρχής, αλλά μονόδρομα. Δηλαδή πέρναγα στο GUI τα "εξωτερικά" gamedata, αλλά δεν είχα ασχοληθεί να μου τα γυρίζει κιόλας. Το κατάφερα, αλλά μάλλον μπακαλίστικα οπότε θέλω λίγη βοήθεια από πιο έμπειρους στη Python.

 

Η λογική λοιπόν είναι τα gamedata να περνάνε μέσα στην Gui κλάση, η κλάση να χρησιμοποιεί και να αλλάζει όσα είναι να αλλάξει, και να τα επιστρέφει πίσω στον caller (δηλαδή στην main() στη δική μου περίπτωση). Προσπαθώ να αποφύγω global variables...

 

Κώδικας:

 

 

from collections import namedtuple

# δικά μου modules
from config import Languages
from gui import Gui
from scores import Scores

#-----------------------------------------------------
if __name__ == "__main__":

	RandLimits = namedtuple( 'RandLimits', 'min max' )
	nscores = 10

	gamedata = {
		'langs'   : Languages('EN'),   # load all supported langs, set EN as default
		'rlimits' : RandLimits(1, 20),
		'nscores' : nscores,
		'plrname' : '<PLR1>',
		'scores'  : Scores( nscores )
	}

	#app = Gui( langs, RandLimits(1,20), nscores, plrname )
	app = Gui( gamedata )

	print( gamedata )

 

Από ότι έχω δει, δημοφιλείς τρόποι περάσματος ορισμάτων είναι είτε ως packed list με *args, είτε ως packed dictionariy με **kwargs. Με τη διαφορά πως κανείς τους δεν δείχνει να με εξυπηρετεί. Διότι από ότι κατάλαβα, με κάποιον από αυτούς τους τρόπους πρέπει η κλάση Gui να δουλέψει με τα unpacked arguments, μετά να τα κάνω χειροκίνητα re-pack και κατόπιν να βρω έναν τρόπο να περάσω τα repacked arguments πίσω στον caller.

 

Αυτό μου φαίνεται "εύκολο στα λόγια, δύσκολο και messy στην πράξη" (πιθανότατα όμως να φταίει πως δεν ξέρω τα μυστικά της γλώσσας). Αν μη τι άλλο, μόλις γίνουν unpacked τα arguments, όποια είναι immutable δεν γίνεται να αλλαχτούν και να περαστούν πίσω στον caller αυτόματα, έτσι δεν είναι;

 

Μετά λοιπόν από αρκετούς πειραματισμούς, κατέληξα τελικά στον τρόπο που δείχνω στον παραπάνω κώδικα. Περνάω δηλαδή τα gamedata ως dictionary, επειδή και είναι mutable αλλά κι επειδή μπορώ να έχω ονοματισμένα τα arguments, δηλαδή με τα keys του dictionary.

 

Αλλά αντιμετωπίζω το εξής ενοχλητικό: το όρισμα gamedata πρέπει να το περνάω ως παράμετρο και σε όσες μεθόδους της κλάσης Gui αλλάζουν τιμές στο dictionary. Όταν φτιάχνω alias του ορίσματος στον constructor της κλάσης, οποιαδήποτε αλλαγή κάνω στο alias ΔΕΝ καθρεφτίζεται στο όρισμα gamedata.

 

Δηλαδή, ο constructor της Gui κλάσης είναι ο εξής (τον βάζω ολόκληρο, για να φανούν και οι μέθοδοι που καλεί ώστε να είναι ορατό πως άλλες παίρνουν ως όρισμα το αυθεντικό gamedata, ενώ άλλες όχι)...

 

Κώδικας:

 

 

#===========================================================
class Gui:
	...
	#---------------------------------------------
	def __init__( self, gamedata ):

		self.gamedata = gamedata

		# internationalization stuff (should be called first)
		#self.langs = gamedata['langs'];
		_guistrings = GuiStrings( self.gamedata['langs'] )
		self.strings = _guistrings.strings

		# misc stuff
		_rlims = self.gamedata['rlimits']
		self.r = randint( _rlims.min, _rlims.max )
		self.i = 0

		# gui specific stuff

		#self.root = tk.Tk()               # root is our main-window
		self.root = GuiTk()                # my GuiTk adds a few more methods to tk.Tk 
		self.root.visibility( 0.0 )        # hide it until filled with widgets
		self.init_main_window( self.root, gamedata )     # init the main window
		self.init_menus( self.root, self.menus, gamedata ) # create & init the menus

		self.init_playframe( self.root, self.frames['play'], gamedata )
		self.init_scoresframe( self.root, self.frames['scores'] )
		self.init_aboutbar( self.root, self.aboutbar )

		self.init_keyboard_bindings( gamedata )

		# center the root window and show it on the screen
		self.root.geometry_at_screen_center()
		self.root.visibility( 1.0 )

		self.root.mainloop()

 

Οι μέθοδοι init_main_window(), init_menus(), init_playframe() και init_keyboard_bindings() παίρνουν ως όρισμα το αυθεντικό gamedata, γιατί (υποτίθεται) πως θέλουν να αλλάξουν τιμές σε κάποια keys του, με τις αλλαγές αυτές να καθρεφτίζονται και στον caller. Αν δεν το κάνω έτσι, και π.χ. περάσω ως όρισμα το alias self.gamedata ή αν δεν περάσω τίποτα και διαχειριστώ απευθείας το self.gamedata εσωτερικά μέσα σε αυτές τις μεθόδους, οι αλλαγές που κάνω δεν καθρεφτίζονται στον caller.

 

Αυτό λοιπόν δουλεύει, αλλά όσο βαθύτερα πηγαίνει η λογική τόσο πληθαίνουν οι περιπτώσεις που άλλοτε χρειάζεται και άλλοτε όχι να περνάω το αυθεντικό gamedata στις διάφορες μεθόδους. Πολύ μπλέξιμο!

 

Τόνισα πριν με κόκκινο εκείνο το υποτίθεται, γιατί στην πραγματικότητα δεν κάνω αυτό που μόλις περιέγραψα. Αυτό που κάνω είναι να δουλεύω παντού με το alias self.gamedata, το οποίο το κάνω copy στο αυθεντικό όρισμα gamedata, αμέσως πριν τερματίσω το Gui. Το κάνω μέσα στην μέθοδο quit() την οποία την έχω binded σε όλες τα events που θέλω να τερματίζουν το gui.

 

Ο απλούστατος κώδικας της quit() είναι ο εξής:

 

Κώδικας:

	#---------------------------------------------
	def quit( self, gamedata ):
		self.root.destroy()
		self.gamedata['scores'].save()
		gamedata = self.gamedata
Άρα λοιπόν, το αυθεντικό όρισμα gamedata το περνάω σε εκείνες τις μεθόδους (και σε αρκετές ακόμα σε βαθύτερα levels) όχι για να διαχειριστώ απευθείας το όρισμα, αλλά επειδή αυτές οι μέθοδοι καλούν την quit().

 

Όπως για παράδειγμα η μέθοδος init_keyboard_bindings()...

 

Κώδικας:

 

	#---------------------------------------------
	def init_keyboard_bindings( self, gamedata ):

		_strings = self.strings

		self.root.bind(
			'<Return>',
			lambda e: self.on_buttonclick_ok( gamedata )
			)
		self.root.bind(
			'<' + _strings['ACCL_GAME_PLAYER'] + '>',
			lambda e: self.on_menuentry_game_player()
			)
		self.root.bind(
			'<' + _strings['ACCL_GAME_EXIT'] + '>',
			lambda e: self.quit( gamedata )
			)
		self.root.bind(
			'<' + _strings['ACCL_HELP_INSTRUCTIONS'] + '>',
			lambda e: self.on_menuentry_help_instructions()
			)
		self.root.bind(
			'<' + _strings['ACCL_HELP_ABOUT'] + '>',
			lambda e: self.on_menuentry_help_about()
			)
		self.root.bind(
			'<' + _strings['ACCL_GAME_SCORES'] + '>',
			lambda e: self.on_keybinding_game_scores()
			)

 

Όπως μπορείτε να δείτε, το αυθεντικό όρισμα gamedata περνιέται σε αυτήν την μέθοδο μόνο και μόνο επειδή πρέπει κατόπιν να περαστεί στην quit() (στο binding του accelerator key: 'ACCL_GAME_EXIT') και στην on_buttonclick_ok() (στο RETURN binding) επειδή κι αυτή καλεί την quit() ... δεν βάζω τον κώδικα της on_buttonclick_ok() γιατί είναι μεγάλος.

 

Γνωρίζει λοιπόν κάποιος να μου υποδείξει/καθοδηγήσει σε πιο καθαρή δομή/λογική υλοποίησης, χωρίς όλο αυτό το messy μπλέξιμο με το πέρασμα του gamedata;

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Δημοσ. (επεξεργασμένο)

Δεν είμαι σίγουρος ότι κατάλαβα ακριβώς αυτό που θες αλλά γιατί δεν προσθέτεις απλά μια μέθοδο στην κλάσση Gui που να σου επιστρέφει το dict με τις ανανεωμένες τιμές;
 

class Gui:
    ...
    #---------------------------------------------
    def __init__( self, gamedata ):
        self.gamedata = gamedata
        ....
        #self.langs = self.gamedata['langs'];
        .....

        self.init_main_window( self.root) # init the main window
        self.init_menus( self.root, self.menus) # create & init the menus
        ....
        self.init_keyboard_bindings()

    def getUpdatedData(self):
        return self.gamedata

Και στη main:

if __name__ == "__main__":
    gamedata = {......}

    app = Gui( gamedata )
    gamedata = app.getUpdatedData()
    print( gamedata )

Έτσι δεν χρειάζεται να περνάς και τίποτα στις μεθόδους σου καθώς χρησιμοποιείς instance variables παντού.
 

Όταν φτιάχνω alias του ορίσματος στον constructor της κλάσης, οποιαδήποτε αλλαγή κάνω στο alias ΔΕΝ καθρεφτίζεται στο όρισμα gamedata.

 

Γιατί δεν πρόκειται περί alias, αλλά για καινούρια μεταβλητή (κλάσσης). Το convention self.mplampla = mplampla όπου mplampla μια παράμετρος του constructor είναι απλά convention και προφανώς η μεταβλητή κλάσης μπορεί να έχει ότι όνομα θέλεις.

Επεξ/σία από Ilias95
Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Δημιουργήστε ένα λογαριασμό ή συνδεθείτε για να σχολιάσετε

Πρέπει να είστε μέλος για να αφήσετε σχόλιο

Δημιουργία λογαριασμού

Εγγραφείτε με νέο λογαριασμό στην κοινότητα μας. Είναι πανεύκολο!

Δημιουργία νέου λογαριασμού

Σύνδεση

Έχετε ήδη λογαριασμό; Συνδεθείτε εδώ.

Συνδεθείτε τώρα

  • Δημιουργία νέου...