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

python 3.2.3 + tkinter + cx_freeze


migf1

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

@migf1

Νομίζω ότι έχεις μπερδέψει λίγο κάποια πράγματα. Ελπίζω αυτό το παράδειγμα να σε βοηθήσει να τα ξεκαθαρίσεις. Χρησιμοποιώ την συνάρτηση id(). H τιμή που επιστρέφει η id() είναι ουσιαστικά μια θέση μνήμης, είναι κάτι σαν ένας pointer δηλαδή.

class Klass(object):
    def __init__(self, inside):
        self.attr = inside
        alias = self.attr

        print("Inside id:", id(inside))
        print("Attrib id:", id(self.attr))
        print("Alias  id:", id(alias))
       
        self.method(inside)
        self.method(alias)
        self.method(self.attr)
       
    def method(self, argument):
        print("Method argument  id:", id(argument))
        print("Method attribute id:", id(self.attr))

Αν δημιουργήσουμε μια instance για την παραπάνω κλάση

outside = dict(a=1, b=2)

print("Outside id:", id(outside))
ins = Klass(outside)

θα πάρουμε output παρόμοιο με αυτό

Outside id: 140231824351048
Inside id: 140231824351048
Attrib id: 140231824351048
Alias  id: 140231824351048
Method argument  id: 140231824351048
Method attribute id: 140231824351048
Method argument  id: 140231824351048
Method attribute id: 140231824351048
Method argument  id: 140231824351048
Method attribute id: 140231824351048

Όπως βλέπεις ό,τι και αν δοκιμάσαμε «δείχνει» στο ίδιο αντικείμενο.

 

Ας δοκιμάσουμε να αλλάξουμε το αντικείμενο μέσα στην κλάση

class Klass2(Klass):
    def __init__(self, inside):
        inside["c"] = 3
        super(Klass2, self).__init__(inside)

ins2 = Klass2(outside)

To output που θα πάρουμε δεν αλλάζει. Όλα εξακολουθούν να δείχνουν στο αρχικό αντικείμενο

Inside id: 140231824351048
Attrib id: 140231824351048
Alias  id: 140231824351048
Method argument  id: 140231824351048
Method attribute id: 140231824351048
Method argument  id: 140231824351048
Method attribute id: 140231824351048
Method argument  id: 140231824351048
Method attribute id: 140231824351048

Τι γίνεται όμως στο αρχικό αντικείμενο;

print(outside)        #{'a': 1, 'b': 2, 'c': 3}

Όπως είναι πολύ λογικό η τιμή του έχει αλλάξει!

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

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

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

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

...
Και στη main:

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

 

Επειδή είμαι... μακάκας :lol:

 

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

Εδώ σε έχασα τελείως!

 

class Gui:
    foo = None
    def __init__( self, gamedata ):
        self.gamedata = gamedata
        ...
Σύμφωνα με τα όσα έχω δει μέχρι στιγμής περί Python, στον παραπάνω κώδικα η foo είναι class variable, και η self.gamedata είναι instance variable. Το θίξαμε και σε προηγούμενα posts του νήματος. Είμαι λάθος; Νομίζω πως δεν είμαι.

 

Σχετικά με το aliasing τώρα, ο παρακάτω κώδικας εμένα μου δείχνει πως στον παραπάνω κώδικα ο self.gamedata πρέπει να είναι alias του gamedata...

 

	class Foo:
		def __init__( self, arg ):
			self.alias = arg
			print( id(arg), id(self.alias) )

	tuple = Foo( (1, 2, 3, 4,) )
	string = Foo( "1234" )
	list   = Foo( [1, 2, 3, 4,] )
	dictionary = Foo( {1, 2, 3, 4} )

'''
Έξοδος:
2153312 12153312
12118560 12118560
12573752 12573752
12561624 12561624
'''
Πάντως σήμερα με πιο καθαρό μυαλό, αφαίρεσα όλα τα περάσματα του αυθεντικού ορίσματος gamedata από τις μεθόδους της κλάσης, και τελικά μου δούλεψε as expected (expected από μένα εννοώ :lol:)

 

Το πρόβλημα πρέπει να μου το δημιουργούσε μια lambda που χρησιμοποιούσα για να κάνω bind μια συνάρτηση (που καλούσε την quit() ) σε ένα widget. Βέβαια η πρόταση για gamedata getter μου φαίνεται όντως πολύ πιο καθαρή.

 

EDIT:

 

@pmav99: Γράφαμε μαζί, πάω να διαβάσω το ποστ σου.

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

Απλά για να το ολοκληρώσω, να γράψω και ορισμένα πράγματα για το is γιατί το is και η χρήση του συχνά μπερδεύει τους noobs στην Python.

 

Η σύνταξη

obj_a is obj_b

δεν είναι παρά syntactic sugar για το εξής

id(obj_a) == id(obj_

Δεν ελέγχει δηλαδή την ισότητα μεταξύ δύο αντικειμένων αλλά το αν τα δύο ονομάτα (names) δείχνουν στο ίδιο αντικείμενο. Παραδείγματος χάριν:

a = 3.14
b = 3.14
print(a ==      # True
print(a is      # False, γιατί το id(a) είναι διαφορετικό του id(

Άλλο ένα.

Προσοχή! Δεν δημιουργούμε νέο αντικείμενο για το b.

a = [1, 2, 3]
b = a
print(a ==      # True, προφανές. Όλες οι λίστες είναι ίσες με τον εαυτό τους
print(a is      # True, γιατί τα a και b δείχνουν στο ίδιο αντικείμενο

Και ένα τρίτο παράδειγμα

Προσοχή! Εδώ δημιουργούμε νέο αντικείμενο για το b.

a = [1, 2, 3]
b = a[:]       
print(a ==      # True, γιατί η λίστα b είναι αντίγραφο της a
print(a is      # False, γιατί τα a και b δείχνουν σε διαφορετικά αντικείμενα

Στο πρώτο παράδειγμα χρησιμοποιήσα επίτηδες floats και όχι integers γιατί υπάρχει ένα implementation detail του interpreter της CPython που μπορεί να μπερδέψει κάποιον. Ποιο συγκεκριμένα, για λόγους performance, όλοι οι integers στο διάστημα [0, 256] «δημιουργούνται» κατευθείαν μόλις ξεκινάει ο interpreter. Στη συνέχεια όλες οι μεταβλητές που παίρνουν αυτές τις τιμές δείχνουν πάντα στα αντικείμενα που δημιούργησε ο interpreter μόλις ξεκίνησε. Δηλαδή:

a = 1
b = 1
print(a is       # True!

Όμως

a = 257
b = 257
print(a is       # False!

Αν κάποιος χρησιμοποιήσει κάποιον άλλον interpreter (πχ pypy, jython, ironpython κτλ) τότε η συμπεριφορά του is για τους integers μπορεί να είναι διαφορετική

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

@migf1

Νομίζω ότι έχεις μπερδέψει λίγο κάποια πράγματα. Ελπίζω αυτό το παράδειγμα να σε βοηθήσει να τα ξεκαθαρίσεις.

...

Thanks για την αναλυτική απάντηση. Αν και μάλλον με μπερδεύει περισσότερο.

 

Γιατί κάνεις subclass την Klass σε Klass2 προκειμένου να κάνεις modify το 'inside'?

 

Στον κώδικα για τον οποίον ρώτησα δεν κάνω subclassing. Αλλάζω απευθείας τα περιεχόμενα του 'self.attr' αναμένοντας να καθρεφτιστούν οι αλλαγές και στο 'inside', που είναι mutable object (dictionary) και άρα περνάει μέσα στον constructor by reference. Και άρα, επειδή το' self.attr' είναι alias του by reference passed 'inside' αναμένω ότι modifications κάνω στο 'self.attr' να καθρεφτίζονται αυτόματα και στο 'inside'.

 

Έχω λάθος στο παραπάνω; Σε αυτό αν μου απαντήσει κάποιος πραγματικά θα με βοηθήσει!

 

EDIT:

 

Μόλις (ξανα) βρήκα το παρακάτω από την τεκμηρίωση της python (το είχα ξαναβρεί αλλά δεν το θυμόμουν, με τόσες νέες πληροφορίες που βάζω στο κεφάλι μου κάθε μέρα):

 

https://docs.python.org/3/tutorial/classes.html

 

9.1. A Word About Names and Objects

 

Objects have individuality, and multiple names (in multiple scopes) can be bound to the same object. This is known as aliasing in other languages. This is usually not appreciated on a first glance at Python, and can be safely ignored when dealing with immutable basic types (numbers, strings, tuples). However, aliasing has a possibly surprising effect on the semantics of Python code involving mutable objects such as lists, dictionaries, and most other types. This is usually used to the benefit of the program, since aliases behave like pointers in some respects. For example, passing an object is cheap since only a pointer is passed by the implementation; and if a function modifies an object passed as an argument, the caller will see the change — this eliminates the need for two different argument passing mechanisms as in Pascal.

Νομίζω πως επιβεβαιώνει πως όταν το όρισμα 'inside' είναι mutable object, περνάει by reference. Σωστά; Άρα γιατί στο παράδειγμά σου κάνεις subclassing για να το πειράξεις, αντί να το πειράξεις απευθείας;

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

    Στον κώδικα για τον οποίον ρώτησα δεν κάνω subclassing. Αλλάζω απευθείας τα περιεχόμενα του 'self.attr' αναμένοντας να καθρεφτιστούν οι αλλαγές και στο 'inside', που είναι mutable object (dictionary) και άρα περνάει μέσα στον constructor by reference. Και άρα, επειδή το' self.attr' είναι alias του by reference passed 'inside' αναμένω ότι modifications κάνω στο 'self.attr' να καθρεφτίζονται αυτόματα και στο 'inside'.

 

Το παράδειγμα μου (νομίζω τουλάχιστον) απαντά ακριβώς σε αυτό. Ναι οι αλλαγές καθρεφτίζονται αυτόματα, αρκεί όλα τα names να δείχνουν στο ίδιο αντικείμενο. Αν τυχον κάπου δημιουργείς όμως ένα copy του αντικειμένου τότε αυτό παύει να ισχύει. Θα κοιτάξω πιο αναλυτικά τον κώδικά σου και θα σου πω.

 

Ο σκοπός του παραδείγματος είναι να δείξει ότι μπορώ να χρησιμοποιήσω local alias/instance attributes/arguments χωρίς καμία διαφορά γιατί όλα τους δείχνουν στο ίδιο αντίκειμενο. Μπορώ επίσης να κάνω αλλαγές στο αντικείμενο (αν αυτό είναι mutable φυσικά) χωρίς να αλλάζει τίποτα. Όλα τα names (outside, inside, alias, self.attr) εξακολουθούν να δείχνουν στο ίδιο object.

 

Ο λόγος που κάνω subclassing είναι απλά για να μην επαναλαμβάνω τον κώδικα της Klass. Στην συγκεκριμένη περίπτωση, η κληρονομικότητα δεν έχει καμία σημασία.Αντί για τον ορισμό της Klass2 που δίνω στο παραπάνω post, χρησιμοποίησε τον ορισμό που ακολουθεί.

 

Πρόσεξε ότι αλλάζω τις τιμές του dictionary τόσο μέσα στην __init__() όσο και στην method(), όπως επίσης και ότι χρησιμοποιώ κάθε φορά διαφορετικό name για να αποκτήσω πρόσβαση στο αντικείμενο που θέλω να αλλάξω.

class Klass2(object):
    def __init__(self, inside):
        inside["c"] = 3     # Make a change on the object

        self.attr = inside
        alias = self.attr
        print("Inside id:", id(inside))
        print("Attrib id:", id(self.attr))
        print("Alias  id:", id(alias))
     
        self.method(inside)
        self.method(alias)
        self.method(self.attr)
     
    def method(self, argument):
        print("Method argument  id:", id(argument))
        print("Method attribute id:", id(self.attr))

        # Make more changes to the object
        argument["d"] = 4 
        self.attr["e"] = 5
outside = dict(a=1, b=2)

print("Outside id:", id(outside))
ins2 = Klass2(outside)

print()
print(outside)

Θα δώσει output

Outside id: 140231892678408
Inside id: 140231892678408
Attrib id: 140231892678408
Alias  id: 140231892678408
Method argument  id: 140231892678408
Method attribute id: 140231892678408
Method argument  id: 140231892678408
Method attribute id: 140231892678408
Method argument  id: 140231892678408
Method attribute id: 140231892678408
{'a': 1, 'd': 4, 'b': 2, 'c': 3, 'e': 5}
Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Πρέπει να φύγω, θα δω το ποστ μόλις επιστρέψω και αν χρειαστεί θα επανέλθω. Thanks!

 

ΥΓ. Δεν χρειάζεται να δεις τον κώδικα μου. Δεν έχει κάτι διαφορετικό από αυτά που είπαμε με τα απλά παραδείγματα. Επίσης τελικά δουλεύει (το πρόβλημα ήταν μαλλον ότι έκανα κάτι παπαδιές με κάτι lambda functions στα event bindings).

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

Ξέχνα αυτό που έγραψα περί aliasing. Μπερδεύτηκα δεν σκέφτηκα ότι τα dicts είναι mutables παρ' όλο που το ανέφερες κιόλας. Οπότε αφού το έλυσες δεν έχει καμία διαφορά είτε χρησιμοποιήσεις τον getter είτε όχι.

Θα μπορούσες βέβαια έτσι να αποφύγεις την χρήση του dict αν θέλεις, να περνάς δηλαδή όλα τα περιεχόμενα του dict σαν παραμέτρους στον constructor και όποτε χρειάζεσαι ανανεωμένες τιμές να τις παίρνεις από έναν getter που να επιστρέφει τα πάντα ή ακόμα και να δημιουργήσεις ξεχωριστούς getters για συγκεκριμένες μεταβλητές.

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

...

Θα μπορούσες βέβαια έτσι να αποφύγεις την χρήση του dict αν θέλεις, να περνάς δηλαδή όλα τα περιεχόμενα του dict σαν παραμέτρους στον constructor και όποτε χρειάζεσαι ανανεωμένες τιμές να τις παίρνεις από έναν getter που να επιστρέφει τα πάντα ή ακόμα και να δημιουργήσεις ξεχωριστούς getters για συγκεκριμένες μεταβλητές.

Ας υποθέσουμε πως έχουμε ένα dictionary:

d = {
    'a': 1,
    'b': 2,
    'c': 3
}
Σε αυτό που έχω κάνει bold στην παράθεση υποθέτω εννοείς να περνάω τις παραμέτρους είτε μια, μία είτε ως **kwargs, σωστά; Δεν βλέπω να υπάρχει άλλη ερμηνεία, οπότε αν σε έπιασα σωστά εννοείς κάτι σαν το παρακάτω (χρησιμοποιώ **kwargs που είναι πιο γενική περίπτωση και κάπως πιο δομημένη):

class Foo1:
    def __init__( self, **kwargs ):
        self.kwargs = kwargs
        for key in self.kwargs:
            self.kwargs[key] += 1

    def get( self ):
        return self.kwargs
Εδώ λοιπόν είναι υποχρεωτικό να υπάρχει getter, γιατί αλλαγές σε immutable μέλη του kwargs (όπως π.χ. επίτηδες έβαλα στο παράδειγμα) δεν τις βλέπει ο caller...

print( d )
Foo1( **d )
print( d )

'''
Έξοδος:
{'b': 2, 'a': 1, 'c': 3}
{'b': 2, 'a': 1, 'c': 3}
'''
Οπότε πρέπει υποχρεωτικά:

print( d )
d = Foo1( **d ).get()
print( d )

'''
Έξοδος:
{'c': 3, 'b': 2, 'a': 1}
{'c': 4, 'b': 3, 'a': 2}
'''
Στη δική μου προσέγγιση επέλεξα να περάσω το dictionary ως ενιαίο mutable object σκεπτόμενος πως θα μου απλοποιήσει τον κώδικα και μέσα κι έξω από την κλάση...

class Foo2:
    def __init__( self, dct ):
        for key in dct:
            dct[key] += 1

print( d )
Foo2( d )
print( d )

'''
Έξοδος:
{'a': 1, 'c': 3, 'b': 2}
{'a': 2, 'c': 4, 'b': 3}
'''
Το παραπάνω εκτός από απλούστερο κώδικα και μέσα κι έξω από την κλάση δεν επηρεάζεται κιόλας από τυχόν immutable μέλη του dictionary. Επίσης δεν κοπιάρει μνήμη.

 

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

class Foo2:
    def __init__( self, dct ):
        self.dct = dct
        for key in self.dct:
            self.dct[key] += 1
Αν τώρα του βάλουμε και getter...

class Foo2:
    def __init__( self, dct ):
        self.dct = dct
            for key in dct:
                self.dct[key] += 1

    def get( self ):
        return self.dct
τότε μπορούμε να το χρησιμοποιήσουμε στον caller με 3 διαφορετικούς τρόπους και θα φέρνει πάντα τα ίδια αποτελέσματα, και μάλιστα αναφερόμενο πάντα στο ίδιο object...

print( id(d), d )
Foo2( d )
print( id(d), d )
print( id(d), d )
Foo2( d ).get()
print( id(d), d )
print( id(d), d )
d = Foo2( d ).get()
print( id(d), d )
Στα δικά μου (άπειρα σε python) μάτια, ο τρόπος που επέλεξα είναι overall καλύτερος και κυρίως πολύ πιο ευέλικτος, με μόνο συγκριτικό μειονέκτημα (όχι πάντα, ανάλογα τι θέλουμε να κάνουμε) ότι δουλεύει απευθείας στη μνήμη. Αυτό αν δεν το θέλουμε, μπορούμε να φτιάξουμε στον caller ένα χειροκίνητο αντίγραφό του πριν το περάσουμε στην Foo2...

dcopy = d[:]
Foo2(dcopy)
if everything-ok :
    d = dcopy
Αν το κάνουμε με **kwargs (ή με χειροκίνητα περασμένα ορίσματα ένα-ένα) τότε πέραν των υπόλοιπων που ανέφερα παραπάνω είναι νομίζω και αδύνατον να τα ξαναβάλουμε στην αρχική θέση μνήμης που είχε ο d (τώρα αυτό το τελευταίο δεν ξέρω αν θεωρείται σημαντικό για πρόγραμμα γραμμένο σε Python, υποθέτω πως όχι, αλλά εφόσον το σκέφτηκα το είπα... επίσης αν υπάρχει τρόπος, θα ήθελα να τον ξέρω).

 

Για μένα που προέρχομαι κυρίως από πιο low-level background είναι σημαντικό να ξέρω πότε και γιατί περνάω by reference και πότε by value, αλλά δεν ξέρω μήπως είμαι υπερβολικός σε Python context.

 

@pmav99:

 

Τελικά από ότι κατάλαβα, εξαρχής το ίδιο πράγμα λέμε, οπότε ευτυχώς δεν τα είχα μπερδεμένα. Το λέω γιατί μόλις ένιωσα την ικανοποίηση πως επιτέλους κατάφερα να αφομοιώσω αυτό το dual-behavior που έχει το pass by assignment της Python, μου έγραψες πως τα έχω μπερδέψει και πήγε η ψυχή μου στην Κούλουρη ("φτου, πάλι από την αρχή πρέπει να τα πιάσω" σκέφτηκα :P).

 

Ευτυχώς δεν τα είχα μπερδεμένα, είχα (κι έχω) την ανασφάλεια του άπειρου στην Python. Ενώ δηλαδή όταν έγραφα τον κώδικα ήμουν σίγουρος πως και το gamedata περνιέται by reference και πως το self.gamedata = gamedata δημιουργεί alias, όταν είδα πως δεν μου επέστρεφε στον caller τις αλλαγές, άρχισα να αναρωτιέμαι: ρε μπας και δεν είναι alias τελικά; Ή μπας και τελικά το gamedata δεν περνιέται by reference; κλπ, κλπ.

 

Οπότε πρώτα έκατσα και το έκανα treat σαν να μην ήταν alias το self.gamedata (περνώντας δηλαδή το αυθεντικό gamedata επιλεκτικά σε όσες μεθόδους χρειαζόταν) και αφού είδα πως έτσι δούλευε, τότε πόσταρα στο φόρουμ για να πάρω την άποψη των πιο έμπειρων.

 

Εν τω μεταξύ, επειδή ο κώδικας δεν είναι και πάρα πολύ μικρός (ήδη ξεπερνάει τις 1200 γραμμές) και κάνει κι άλλα πράγματα (π.χ. internationalization που από μόνο του σε ζαλίζει όταν έχεις νέα γλώσσα μπροστά σου, ειδικά άμα θέλεις να το κάνεις on-the-fly ή όταν θέλεις να έχεις μαζεμένα όλα τα strings σε ένα μόνο μέρος... τα έχω κάνει και τα 2 αυτά) το μυαλό αρχίζει και κάνει κουκουρούκου από ένα σημείο και μετά... όσο κι αν έχεις κατανοήσει τη θεωρία, εκείνες τις στιγμές μέσα στη θολούρα σου κάνεις 1002 σκέψεις. Αυτό αντιμετωπίζεται καλύτερα μονάχα με εμπειρία, που στην Python δυστυχώς δεν την έχω ακόμα.... αλλά το παλεύω, και με τη βοήθειά σας όπου κολλάω πιστεύω πως κάτι θα κάνω τελικά.

 

Στόχος μου είναι να εξοικειωθώ όσο μπορώ με τη γλώσσα (και με το Tkinter) με αυτό το toy project, για να μπορέσω κατόπιν να πάω σε μεγαλύτερo project (και μετά σε ακόμα μεγαλύτερο, κλπ).

 

Επίσης, σε κάποιο σημείο θέλω να ποστάρω και τον κώδικα για να το κάνετε review και να μου επισημάνετε λάθη μου, κακές πρακτικές, κλπ ώστε να μάθω πιο γρήγορα. Αλλά όχι ακόμα, να τον σουλουπώσω πρώτα λιγάκι, γιατί αυτή τη στιγμή τα βλέπω και μόνος μου σημεία που θέλουν βελτίωση (π.χ. θέλω να φτιάξω κάποια subclasses του Tkinter σε πράγματα που χρησιμοποιώ για να μου είναι λίγο πιο abstract η διαχείρισή τους)

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

Αν το κάνουμε με **kwargs (ή με χειροκίνητα περασμένα ορίσματα ένα-ένα)

Ναι, ένα-ένα τα ορίσματα εννοούσα:

class Gui:
    def __init__(self, langs, rlimits, nscores, plrname, scores):
        self.langs  = langs; self.rlimits = ......

    def getGamedata(self):
        return self.langs, self.rlimits, self.nscores, ...

if __name__ == "__main__":
    langs = ...; rlimits = ...; ......
    app = Gui(langs, rlimits, nscores, plrname, scores)
    langs, rlimits, nscores, plrname, scores = app.getGamedata()
    #ή nscores = app.getNscores()

Βασικά στην πραγματικότητα στην python δεν χρειάζεσαι καν αυτούς τους getters που θυμίζουν java. Μπορείς να κάνεις μέσα στην main:

if __name__ == "__main__":
    langs = ...; rlimits = ...; ......
    app = Gui(langs, rlimits, nscores, plrname, scores)
    langs, rlimits = app.langs, app.rlimits

 

*args και **kwargs που ανέφερες νομίζω ότι έχει νόημα να χρησιμοποιήσεις μόνο όταν δεν ξέρεις το πλήθος των ορισμάτων που θες να περάσεις οπότε δεν μου φαίνεται καθόλου πρακτικό εδώ.

 

 

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

Ναι οκ, αλλά δεν είναι λίγο χύμα αυτή η προσέγγιση; Τόσες μεταβλητές διάσπαρτες και μέσα κι έξω από τη κλάση;

 

Δεν ξέρω, ειδικά για πρότζεκτ μεσαία προς μεγάλα νομίζω πως θα χαθεί η μπάλα αν δεν είναι ομαδοποιημένες ομοειδείς μεταβλητές σε κάποιου είδους δομή (προς το παρόν οι μόνες που έχω βρει να είναι liable για τέτοιες περιπτώσεις είναι τα dictionaries, οι κλάσεις και τα namedtuples, επειδή πέρα από την ομαδοποίηση των μεταβλητών μου επιτρέπουν να τις ονοματίζω κιόλας... δηλαδή κάτι σαν τα struct της C που εκτός από το δικό τους όνομα έχουν ονοματισμένα και τα πεδία τους).

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

Δεν την πρότεινα σαν καλύτερη προσέγγιση αλλά σαν εναλλακτική. Φαντάζομαι εξαρτάται απ' το πόσες μεταβλητές έχεις και από πόσες απ' αυτές σε ενδιαφέρει η ανανεωμένη τιμή τους. Btw, τα dicts είναι τα built-in hashmaps της python.

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

Δεν την πρότεινα σαν καλύτερη προσέγγιση αλλά σαν εναλλακτική...

 

Α οκ :)

 

Λοιπόν, μόλις βρήκα και πως μπορούμε να αυτοματοποιήσουμε στο cx_freeze τη αντιγραφή αρχείων και φακέλων για stand-alone execution. Ουσιαστικά τα βάζουμε στο: include_files του setyp.py

 

Παραθέτω το setup.py του toy-project για να υπάρχει ως παράδειγμα... 

# Save as: setup.py
# Use  as: python setup.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.

buildOptions = { 'include_files': ['config.py', 'scores.py', 'mytk.py', 'gui.py', 'assets/', 'lang/'] }

setup(  name = "guess",
        version = "0.1",
        description = "Guess the Number",
        options = { 'build_exe': buildOptions },
        executables = [Executable("guess_gui_oop.py", base=base)]
)
Τα 'config.py', 'scores.py', 'mytk.py', 'gui.py' είναι τα πηγαία αρχεία, εκτός από το main. Αυτό μπαίνει στα 'executables' που σε αυτή την περίπτωση ονομάζεται: "guess_gui_oop.py"
Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

@pmav99 (αλλά και όποιον άλλον θέλει):

 

Η απάντησή σου στο άλλο νήμα πως για το config.py μάλλον θα προτιμούσες μια γενική κλάση αντί για όλα χύμα ή αντί για πολλές μικρότερες κλάσεις, μου δημιούργησε την εξής απορία. Εγώ το έχω κάνει τώρα με πολλές, μικρότερες κλάσεις, με το σκεπτικό πως θα είναι πιο ευέλικτο στο να καλώ μονάχα ότι χρειάζομαι στα άλλα modules.

 

Δηλαδή, το config.py μου είναι έτσι...

 

Κώδικας

 

 

# -*- coding: cp1253 -*-

import gettext

#-----------------------------------------------------
class Paths:

	#---------------------------------------------
	def __init__( self ):
		self.dirs = {
			'LOCALE': 'lang/',
			'ASSETS': 'assets/',
			'VOICES': 'assets/voices/',
		}
		self.fnames = {
			'VOICE_LESS'        : 'small.wav',
			'VOICE_GREATER'     : 'big.wav',
			'VOICE_EQUAL'       : 'foundit.wav',
			'VOICE_INVALID'     : 'invalid.wav',
			'VOICE_LANGUAGE'    : 'language.wav',
			'VOICE_IS_TOPSCORE' : 'is_topscore.wav',
			'VOICE_IS_BOTSCORE' : 'is_botscore.wav',
			'VOICE_IN_TOPSCORES': 'in_topscores.wav',
			'VOICE_IN_LOWSCORES': 'in_lowscores.wav',

			'IMG_ME_PLAYER'      : 'me_player.png',
			'IMG_ME_ENGLISH'     : 'flag_en.png',
			'IMG_ME_GREEK'       : 'flag_el.png',
			'IMG_ME_INSTRUCTIONS': 'me_instructions.png',
			'IMG_ME_ABOUT'       : 'me_about.png',
			'IMG_PLAYER'         : 'player.png',
			'IMG_QUESTION'       : 'question.png',
			'IMG_SOUNDS'         : 'sounds.png',
		}

#-----------------------------------------------------
class Languages:

	#---------------------------------------------
	def __init__( self, klang='EN' ):

		self.langs = {
			'EN' : {
				'CODE' : 'en',
				'OBJ' : None
			},
			'EL' : {
				'CODE' : 'el',
				'OBJ' : None
			}
		}
		self.klang = self._init_languages( self.langs, klang )

	#---------------------------------------------
	def _init_languages( self, langs, klang='EN' ):
		''' klang: a valid key of the self.langs dict, otherwise 'EN' is assumed '''

		_localedir = Paths().dirs['LOCALE']

		# load all supported translations via gettext
		for key,value in self.langs.items():
			obj = gettext.translation(
					'guess_gui',
					localedir= _localedir, #DIRS['LOCALE'], #'lang/',
					languages= [ value['CODE'] ],
					fallback= True
					)
			value['OBJ'] = obj

		self.set( klang )

		return klang

	#---------------------------------------------
	def set( self, klang ):
		# install specified lang
		if klang not in self.langs:
			klang = 'EN'
		self.langs[ klang ]['OBJ'].install()
		self.klang = klang

	#---------------------------------------------
	def get_klang( self ):
		return self.klang

	#---------------------------------------------
	def get_code( self, klang ):
		if klang not in self.langs:
			return 'ERR'
		return self.langs[klang]['CODE']

	#---------------------------------------------
	def get_active_code( self ):
		return self.langs[ self.klang ]['CODE']

#-----------------------------------------------------
class ScoresStrings:

	#---------------------------------------------
	def __init__( self, langs ):
		self.langs = langs

#-----------------------------------------------------
class GuiStrings:

	#---------------------------------------------
	def __init__( self, langs ):

		self.langs = langs
		self.strings = {                  # gettext MUST have been inited
			'APP_NAME'                : _('Guess the Number'),
			'ALERT_BOX'               : _('Alert Box'),
			'OK'                      : _('OK'),
			'CANCEL'                  : _('Cancel'),
			'YES'                     : _('Yes'),
			'NO'                      : _('No'),
			'MBAR_GAME'               : _('Game'),
			'MENU_GAME_PLAYER'        : _('Player'),
			'MENU_GAME_SCORES'        : _('Scores'),
			'MENU_GAME_SOUNDS'        : _('Sounds'),
			'MENU_GAME_EXIT'          : _('Exit'),
			'MBAR_LANGUAGE'           : _('Language'),
			'MENU_LANGUAGE_ENGLISH'   : '  English',
			'MENU_LANGUAGE_GREEK'     : '  Ελληνικά',
			'MBAR_HELP'               : _('Help'),
			'MENU_HELP_GUIDE'         : _('Instructions'),
			'MENU_HELP_ABOUT'         : _('About'),
			'LABEL_PROMPT'            : _('Pick a number from %d to %d:'),
			'LABEL_TRIES_0'           : _('Tries: 0'),
			'LABEL_TRIES'             : _('Tries: %d'),
			'LABEL_INFO_INVALID'      : _('Invalid input, try again...'),
			'LABEL_INFO_LESS'         : _('%d is small'),
			'LABEL_INFO_GREATER'      : _('%d is large'),
			'LABEL_INFO_EQUAL'        : _('YOU FOUND THE NUMBER %d'),
			'LABEL_INFO_EMPTY'        : ' ',
			'LABEL_INFO_SWITCHED_TO_EL' : 'ενεργή γλώσσα: ελληνικά',
			'LABEL_INFO_SWITCHED_TO_EN' : 'active language: English',
			'LABEL_ABOUT'            : 'v0.7.2014 | freeware © [email protected] | Python 3.4, Tkinter 8.6',
			'LABEL_PROMPT_PLRNAME'    : _('Name : '),
			'ACCL_LANGUAGE_ENGLISH_UPPER': 'Control-E',
			'ACCL_LANGUAGE_ENGLISH_LOWER': 'Control-e',
			'ACCL_LANGUAGE_GREEK_UPPER'  : 'Control-G',
			'ACCL_LANGUAGE_GREEK_LOWER'  : 'Control-g',
			'ACCL_GAME_PLAYER'        : 'F3',
			'ACCL_GAME_SCORES'        : 'F4',
			'ACCL_GAME_SOUNDS'        : 'F5',
			'ACCL_GAME_EXIT'          : 'Escape',
			'ACCL_HELP_INSTRUCTIONS'  : 'F1',
			'ACCL_HELP_ABOUT'         : 'F2',
			'SCORES_TOP_HEADING'      : _('TOP %d'),
			'SCORES_BOT_HEADING'      : _('LOW %d'),
			'SCORES_COLTITLES_POS'    : _('POS'),
			'SCORES_COLTITLES_NAME'   : _('NAME'),
			'SCORES_COLTITLES_POINTS' : _('TR'),
			'SCORES_SAVE'             : _('Save scores'),
			'SCORES_CLEAR'            : _('Clear scores'),
			'SCORES_COLTITLES_DATETIME' : _('DD-MM-YYYY HH:MM.SS'),
			'MSGBOX_PLAYAGAIN_TITLE'  : _('Question'),
			'MSGBOX_PLAYAGAIN_TEXT'   : _('Play again?'),
			'MSGBOX_HELP_TITLE'       : _('Instructions'),
			'MSGBOX_HELP_TΕΧΤ'        : _(
				"%s\n"
				"\n"
				"Find the secret number, spending as few tries as possible.    \n"
				"When you find it, you may start over. You can stop the game\n"
				"at any time, by pressing %s, or by closing the main window,  \n"
				"or by selecting the menu option: %s -> %s\n"
				"\n"
				"To insert a number, type it in the relative field and click\n"
				"the %s button (or you can press ENTER instead, it's easier).\n"
				"\n"
				"Every time you insert a number, the game lets you know if\n"
				"it's smaller or greater than the secret number. Any invalid\n"
				"input (such as letters) get rejected and it doesn't cost you.\n"
				"\n"
				"The game maintains lists of best and worst scores. They are\n"
				"saved and loaded at game exit and start, respectively. You can\n"
				"also save or clear the scores at any time, via the corresponding \n"
				"buttons, at the bottom of the lists. The column: %s displays\n"
				"the actual scores (tries). Less tries means a better score. The\n"
				"last column shows on what date and time the scores have been\n"
				"achieved.\n"
				"\n"
				"The column: %s displays the name of the players who have\n"
				"achieved each score. It defaults to <PLR1>, but you change it\n"
				"via the menu: %s -> %s (only the first 6 letters are used).\n"
				"The currently active name is always shown in the title bar of\n"
				"the main window. Finally, you can toggle on and off the score\n"
				"lists, via the key: %s, or the menu option: %s -> %s\n"
				"\n"
				"The pre-defined language is English, but you may change\n"
				"it to Greek on the fly, via the menu: %s"
				),
		}

 

 

Για παράδειγμα τα GuiStrings έχουν νόημα μονάχα μέσα στο gui.py module, οπότε τα τραβάω επιλεκτικά μονάχα σε εκείνο το module...

 

'''gui.py'''

from config import GuiStrings

#===========================================================
class Gui:

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

		self.gamedata = gamedata

		# internationalization stuff (should be called first)
		self.strings = GuiStrings( self.gamedata['langs'] ).strings
		...
Αν είχα υλοποιήσει το config.py ως μια ενιαία κλάση (ή όλα χύμα), τότε δεν θα ήταν υποχρεωτικό να τη φορτώνω ολόκληρη σε όλα τα modules, παρόλο που παραπάνω από τα μισά της πράγματα δεν θα χρειάζονταν; Π.χ. τα GuiStrings αφορούν μονάχα το gui.py module.

 

Δεν το έχω σκεφτεί σωστά;

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

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

@migf1

 

Σχετικά με τo pythonicness το μόνο που έχω να σου πω είναι ότι βιάστηκες :P

 

Στην Python ο τρόπος για να κάνεις οτιδήποτε σε ένα iterable είναι ένας:

for item in iterable:
    #do whatever

Είναι και απλό και readable. Δουλεύει παντού χωρίς πολλά πολλά. Επίσης είναι πολύ εύκολο να το κάνεις extend. Αν δηλαδή το do whatever γίνει πιο περίπλοκο στο μέλλον δεν έχεις να κάνεις αλλαγές στο loop.

 

Αν για οποιοδήποτε λόγο χρειάζεσαι και το index που αντιστοιχεί σε κάθε element του iterable αλλά και το ίδιο το item τότε χρησιμοποιείς enumerate(). Δηλαδή προτιμούμε αυτό:

for i, item in enumerate(iterable):
    if i % 2 == 0:
        # handle even
    else:
        # handle odd

από αυτό (και τις όποιες παραλλαγές):

i = 0
for item in iterable:
    i += 1
    if i % 2 == 0:
        # handle even
    else:
        # handle odd

Αν χρειάζεσαι μόνο τα indexes τότε

for i in range(len(iterable)):
    # do whatever

Πάμε στα comprehensions τώρα. Πότε χρησιμοποιούνται τα comprehensions;

 

Αν ο σκοπός που διατρέχεις τo iterable είναι για να δημιουργήσεις ένα list/dict/set τότε μπορεις να χρησιμοποιήσεις list/dict/set comprehension.

 

Τα compehensions δεν είναι αυτοσκοπός. Αν δεν σκοπεύεις να χρησιμοποιήσεις τη λίστα που επιστρέφει το list compehension, τότε το for loop είναι προτιμότερο (και ταχύτερο).

 

Δηλαδή αυτό

mylist = [myfunction(item) for item in iterable]

είναι προτιμότερο από αυτό

mylist = []
for append in iterable:
    mylist.append(myfunction(item))

Αλλά αυτό είναι προτιμότερο:

for item in iterable:
    print(item)

από αυτό:

[print(item) for item in iterable]

γιατί δεν χρησιμοποιούμε πουθενά το list που δημιουργεί το comprehension.

 

Επίσης τα comprenensions έχουνε και ένα μεγάλο μειονέκτημα. Δεν μπορείς να τα κάνεις extend. Για την ακρίβεια μπορούν να έχουν μέχρι if/else μέσω ternary expression (αλλά δεν είναι εύκολο να θυμάσαι τη σύνταξη). Μπορείς φυσικά να μεταφέρεις τη λειτουργικότητα που θέλεις μέσα σε ένα function αλλά έτσι εισάγεις overhead. Εν ολίγοις το for loop είναι πιο εύκολο να το κάνεις extend. Ενώ σε ένα comprehension, αν τύχει στο μέλλον να χρειαστεί να κάνεις ένα if/elif/else τότε πρέπει να το μετατρέψεις σε for loops.

 

Αν δηλαδή θες να δημιουργήσεις ένα list/dict/set και τα conditions για τη δημιουργία του ειναι απλά, τότε τα comprehensions είναι αρκετά readable οπότε και προτιμούνται. Αν τα conditions δεν είναι και τόσο απλά τότε for loop από εμένα.

 

Προσωπικά δεν βρίσκω άσχημο και το map, αλλά επειδή στην python 3 επιστρέφει generator και όχι λίστα προτιμώ τα comprehensions. Πάντως δεν έχουν ουσιαστική διαφορά. Απλά τα map επειδή είναι functional και μπορεί να ξενίζουν λίγο όποιον έχει μάθει μόνο imperative paradigm.

mylist = map(myfunction, iterable)          # python 2
mylist = list(map(myfunction, iterable))    # python 3

Μερικές φορές χρησιμοποιώ map μέσα σε comprehensions :P

foo = [map(func, inner) for inner in outer]

Το ίδιο μπορείς να κάνεις και με double comprehension ή με nested loop αλλά βρίσκω το map πιο readable (σε python 2)

 

Σημείωση 1: Το print() μέσα στο list comprehension δεν τρέχει σε python2 γιατί το print ήταν statement και όχι function.

 

Σημείωση 2: Ο όρος one liner δεν είναι ακριβής. Και αυτό one liner είναι:

print(1); print(2); print(3);

Ο όρος single statement νομίζω ότι είναι πιο ορθός.

 

Σημείωση 3: Το ότι δεν μπορείς να χρησιμοποιήσεις κάτι πέρα από if/else σε ένα comprehension είναι αποτέλεσμα σχεδιασμού. Ούτως ή άλλως πιο περίπλοκες εκφράσεις σε single statements θα παρα-είναι unreadable.

 

Σημείωση 4: Αν θες να χρησιμοποιήσεις την python με πιο functional τρόπο, τότε μπορείς να δεις τις recipes και αλλά και ορισμένες 3rd party libraries (1, 2, 3)

 

Σημείωση 5: Ο editor τον παίρνει πολύ άσχημα

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

Thanks για το διαφωτιστικό ποστ, αν και συνεχίζω να διατηρώ σοβαρές επιφυλάξεις ως προς το pythonicness. Π.χ. αναφέρεις πως ο τρόπος για να κάνεις οτιδήποτε πάνω σε ένα iterable είναι με: for i in iterable: αλλά αν το iterable είναι dictionary οι τρόποι που δείχνεις ως pythonic δουλεύουν με τα keys και όχι με τα values.

 

Αν θελήσεις να δουλέψεις με τα keys, πρέπει να αποφασίσεις αν θα το κάνεις μέσα στο #do whatever ή αν π.χ. θα χρησιμοποιήσεις μεθόδους όπως οι items, iteritems, keys κλπ οι οποίες όμως από ότι διαβάζω συμπεριφέρονται αλλιώς στην Python 2 κι αλλιώς στην Python 3 (π.χ. διαβάζω πως η items στην v2.0 επιστρέφει λίστα ενώ στην v3 επιστρέφει ένα view object που είναι ένα απλό reference).

 

Άλλο παράδειγμα, διαβάζω στην επίσημη τεκμηρίωση...

 

4.6.4. Lists

 

Lists are mutable sequences, typically used to store collections of homogeneous items (where the precise degree of similarity will vary by application).

 

class list([iterable])¶

 

Lists may be constructed in several ways:

 

Using a pair of square brackets to denote the empty list: []

Using square brackets, separating items with commas: [a], [a, b, c]

Using a list comprehension: [x for x in iterable]

Using the type constructor: list() or list(iterable)

...

Δεν λέει απολύτως τίποτα περί pytonicness (πολύ ορθά κατά την άποψή μου) οπότε το αβίαστο συμπέρασμα που βγαίνει είναι πως δεν υπάρχει κάποιος τρόπος που μειονεκτεί ή πλεονεκτεί έναντι των υπολοίπων για να δημιουργήσω μια λίστα.

 

Φυσικά έχω απόλυτη συνείδηση της χρησιμότητας των διάφορων guidelines, coding-styles, κλπ (όχι μόνο στη Python αλλά σε όλες τις γλώσσες) αλλά τσινάω (και θα τσινάω) όταν προσπαθούν να μου τα παρουσιάσουν ως επιβεβλημένα ή ως πανάκειες, κλπ (προφανώς δεν αναφέρομαι προσωπικά σε κανέναν, κουβέντα κάνω).

 

Ευτυχώς όλοι οι δημιουργοί των coding-styles, σε όλες τις γλώσσες που έχω συναντήσει, ξεκαθαρίζουν πως πρόκειται απλά για προτάσεις και τίποτα παραπάνω. Τα παραπάνω τα δημιουργούν κατά κανόνα τα fanboys και οι "ειδικοί". Και δεν αναφέρομαι φυσικά σε συλλογικές επαγγελματικές (ή μη) σοβαρές δουλειές... εκεί ακολουθείς τα guidelines που θέλουν (τα οποία όμως μπορεί να είναι πολύ διαφορετικά από τα guidelines άλλης εταιρίας, άλλης ομάδας, και πάει λέγοντας)

 

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

 

Για το πιο άμεσο θέμα με το config.py μου όμως δεν μου απάντησες... το έχω σκεφτεί λάθος;

 

ΥΓ. One liners εγώ λογίζω ότι δεν περιέχει ; Υποθέτω είναι το ίδιο πράγμα με το statement και στην Python.

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

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

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

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

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

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

Σύνδεση

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

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

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