FAQ Προγραμματισμού¶
Γενικές Ερωτήσεις¶
Υπάρχει πρόγραμμα εντοπισμού σφαλμάτων σε επίπεδο πηγαίου κώδικα με σημεία διακοπής , με ένα βήμα κλπ.;¶
Ναι.
Πολλοί εντοπιστές σφαλμάτων για την Python περιγράφονται παρακάτω και η ενσωματωμένη συνάρτησηbreakpoint()
σάς επιτρέπει να μεταβείτε σε οποιοδήποτε από αυτά.
Το pdb module είναι ένα απλό αλλά επαρκές πρόγραμμα εντοπισμού σφαλμάτων σε λειτουργία κονσόλας για την Python. Είναι μέρος της τυπικής βιβλιοθήκης Python και είναιdocumentedintheLibraryReferenceManual
. Μπορείτε επίσης να γράψετε το δικό σας πρόγραμμα σφαλμάτων χρησιμοποιώντας τον κώδικα για το pdb ως παράδειγμα.
Το διαδραστικό περιβάλλον ανάπτυξης IDLE, το οποίο αποτελεί μέρος της τυπικής διανομής Python (συνήθως διαθέσιμο ωςTools/scripts/idle3), που περιλαμβάνει ένα γραφικό πρόγραμμα εντοπισμού σφαλμάτων.
Το PythonWin είναι ένα Python IDE που περιλαμβάνει ένα πρόγραμμα εντοπισμού σφαλμάτων GUI που βασίζεται σε pdb. Το πρόγραμμα εντοπισμού σφαλμάτων PythonWin χρωματίζει τα σημεία διακοπής και έχει αρκετά ωραία χαρακτηριστικά, όπως τον εντοπισμό σφαλμάτων σε προγράμματα που δεν είναι PythonWin. Το PythonWin είναι διαθέσιμο ως μέρος τουpywin32 έργου και ως μέρος της διανομήςActivePython .
ΤοEric είναι ένα IDE που βασίζεται στο PyQt και το component επεξεργασίας Scintilla.
Τοtrepan3k είναι ένα πρόγραμμα εντοπισμού σφαλμάτων παρόμοιο με το gdb.
ΤοVisual Studio Code είναι ένας IDE με εργαλεία εντοπισμού σφαλμάτων που ενσωματώνεται με λογισμικό ελέγχου έκδοσης.
Υπάρχει ένας αριθμός εμπορικών Python IDEs που περιλαμβάνουν γραφικούς εντοπισμούς σφαλμάτων. Αυτά περιλαμβάνουν:
Υπάρχουν εργαλεία που βοηθούν στην εύρεση σφαλμάτων ή στην εκτέλεση στατικής ανάλυσης;¶
Ναι.
Pylint καιPyflakes κάνουν βασικό έλεγχο που θα σας βοηθήσει να εντοπίσετε τα σφάλματα νωρίτερα.
Έλεγχοι στατικού τύπου όπωςMypy,Pyre, καιPytype μπορεί να ελέγξει υποδείξεις τύπου στον πηγαίο κώδικα της Python.
Πως μπορώ να δημιουργήσω ένα stand-alone binary από ένα Python script;¶
Δεν χρειάζεστε την δυνατότητα μεταγλώττισης κώδικα Python σε C, εάν το μόνο που θέλετε είναι ένα stand-alone πρόγραμμα που οι χρήστες μπορούν να κατεβάσουν και να εκτελέσουν χωρίς να χρειάζεται να εγκαταστήσουν πρώτα την διανομή της Python. Υπάρχει μια σειρά από εργαλεία που καθορίζουν το σύνολο των modules που απαιτούνται από ένα πρόγραμμα και συνδέουν αυτά τα modules μαζί με ένα δυαδικό αρχείο Python για να παραχθεί ένα μόνο εκτελέσιμο.
Το ένα είναι να χρησιμοποιήσετε το εργαλείο παγώματος, το οποίο περιλαμβάνεται στο δέντρο πηγαίου της Python ωςTools/freeze. Μετατρέπει δυαδικό κώδικα Python σε C πίνακες∙ με έναν C μεταγλωττιστή μπορείτε να ενσωματώσετε όλα τα modules σας σε ένα νέο πρόγραμμα, το οποίο στη συνέχεια συνδέεται με τα τυπικά Python modules.
Λειτουργεί σαρώνοντας τον πηγαίο κώδικα αναδρομικά για δηλώσεις εισαγωγής (και στις δύο μορφές) και αναζητώντας τα modules στην τυπική διαδρομή Python καθώς και στον κατάλογο προέλευσης (για ενσωματωμένα modules). Στην συνέχεια γυρίζει τα bytecode για modules που είναι γραμμένες σε Python σε κώδικα C (initializers πίνακα που μπορούν να μετατραπούν σε αντικείμενα κώδικα χρησιμοποιώντας το marshal module) και δημιουργεί ένα προσαρμοσμένο αρχείο διαμόρφωσης που περιέχει μόνο εκείνα τα ενσωματωμένα modules που χρησιμοποιούνται πραγματικά στο πρόγραμμα και το συνδέει με τον υπόλοιπο διερμηνέα Python για να σχηματίσει ένα αυτόνομο δυαδικό αρχείο που λειτουργεί ακριβώς όπως το σενάριο σας.
Τα παρακάτω πακέτα μπορούν να σας βοηθήσουν για την δημιουργία εκτελέσιμων κονσόλας και GUI:
Nuitka (Cross-platform)
PyInstaller (Cross-platform)
PyOxidizer (Cross-platform)
cx_Freeze (Cross-platform)
py2app (macOS μόνο)
py2exe (Windows μόνο)
Υπάρχουν πρότυπα κωδικοποίησης ή οδηγός στυλ για προγράμματα Python;¶
Ναι. Το στυλ κωδικοποίησης που απαιτείται για τα τυπικά modules βιβλιοθήκης τεκμηριώνεται ωςPEP 8.
Βασική Γλώσσα¶
Γιατί λαμβάνω ένα UnboundLocalError όταν η μεταβλητή έχει μια τιμή;¶
Μπορεί να είναι έκπληξη να λάβετε τοUnboundLocalError
σε κώδικα που λειτουργούσε προηγουμένως όταν τροποποιείται προσθέτοντας μια δήλωση εκχώρησης κάπου στο σώμα μιας συνάρτησης.
Αυτό ο κώδικας:
>>>x=10>>>defbar():...print(x)...>>>bar()10
δουλεύει, αλλά αυτός ο κώδικας:
>>>x=10>>>deffoo():...print(x)...x+=1
καταλήγει σε έναUnboundLocalError
:
>>>foo()Traceback (most recent call last):...UnboundLocalError:local variable 'x' referenced before assignment
Αυτό συμβαίνει επειδή όταν κάνετε μια ανάθεση σε μια μεταβλητή σε ένα εύρος, αυτή η μεταβλητή γίνεται τοπική σε αυτό το εύρος και σκιάζει οποιαδήποτε μεταβλητή με παρόμοιο όνομα στο εξωτερικό εύρος. Εφόσον η τελευταία πρόταση στο foo εκχωρεί μια νέα τιμή στοx
, ο μεταγλωττιστής την αναγνωρίζει ως τοπική μεταβλητή. Κατά συνέπεια όταν η προηγούμενηprint(x)
επιχειρεί να εκτυπώσει την uninitialized τοπική μεταβλητή και προκύπτει σφάλμα.
Στο παραπάνω παράδειγμα, μπορείτε να αποκτήσετε πρόσβαση στη μεταβλητή εξωτερικού εύρους δηλώνοντας την ως καθολική:
>>>x=10>>>deffoobar():...globalx...print(x)...x+=1...>>>foobar()10
Αυτή η ρητή δήλωση απαιτείται για να σας υπενθυμίσει ότι (σε αντίθεση με την επιφανειακά ανάλογη κατάσταση με τις μεταβλητές κλάσης και στιγμιότυπου) στην πραγματικότητα τροποποιείτε την τιμή της μεταβλητής στο εξωτερικό πεδίο:
>>>print(x)11
Μπορείτε να κάνετε κάτι παρόμοιο σε ένα ένθετο πεδίο χρησιμοποιώντας τη λέξη κλειδίnonlocal
:
>>>deffoo():...x=10...defbar():...nonlocalx...print(x)...x+=1...bar()...print(x)...>>>foo()1011
Ποιοι είναι οι κανόνες για τις τοπικές και τις καθολικές μεταβλητές στην Python;¶
Στην Python, οι μεταβλητές που αναφέρονται μόνο μέσα σε μια συνάρτηση είναι έμμεσα καθολικές. Εάν σε μια μεταβλητή εκχωρηθεί μια τιμή οπουδήποτε μέσα στο σώμα της συνάρτησης, θεωρείται ότι είναι τοπική, εκτός αν δηλωθεί ρητά ως καθολική.
Αν και είναι λίγο έκπληξη στην αρχή, αυτό το εξηγεί μια στιγμή. Από τη μία πλευρά η απαίτησηglobal
για εκχωρημένες μεταβλητές παρέχει μια γραμμή έναντι ανεπιθύμητων παρενεργειών. Από την άλλη, εάν απαιτείταιglobal
για όλες τις καθολικές αναφορές, θα χρησιμοποιούσατεglobal
όλη την ώρα. Θα έπρεπε να δηλώσετε ως καθολική κάθε αναφορά σε μια ενσωματωμένη λειτουργία ή σε ένα στοιχείο ενός εισαγόμενου module. Αυτή η ακαταστασία θα νικήσει την χρησιμότητα της δήλωσηςglobal
για τον εντοπισμό παρενεργειών.
Γιατί τα lambdas που ορίζονται σε έναν βρόχο με διαφορετικές τιμές επιστρέφουν όλα το ίδιο αποτέλεσμα;¶
Ας υποθέσουμε ότι χρησιμοποιείτε έναν βρόχο for για να ορίσετε μερικά διαφορετικά lambdas (ή ακόμα και απλές συναρτήσεις), π.χ.:
>>>squares=[]>>>forxinrange(5):...squares.append(lambda:x**2)
Αυτό σας δίνει μια λίστα που περιέχει 5 lambdas που υπολογίζουν τοx**2
. Μπορεί να περιμένετε ότι, όταν καλέσετε, θα επέστρεφαν, αντίστοιχα,0
,1
,4
,9
και16
. Ωστόσο, όταν δοκιμάσετε πραγματικά θα δείτε ότι όλα επιστρέφουν16
:
>>>squares[2]()16>>>squares[4]()16
Αυτό συμβαίνει επειδή τοx
δεν είναι τοπικό για τα lambdas, αλλά ορίζεται στο εξωτερικό εύρος και είναι προσβάσιμο όταν το lambda καλείται — όχι όταν ορίζεται. Στο τέλος του βρόχου, η τιμή τουx
είναι4
, επομένως όλες οι συναρτήσεις επιστρέφουν τώρα4**2
, δηλαδή16
. Μπορείτε επίσης να το επαληθεύσετε αλλάζοντας την τιμή τουx
και δείτε πως αλλάζουν τα αποτελέσματα του lambda:
>>>x=8>>>squares[2]()64
Για να το αποφύγετε αυτό, πρέπει να αποθηκεύσετε τις τιμές σε μεταβλητές τοπικές στο lambda, έτσι ώστε να μην βασίζονται στην τιμή του καθολικούx
:
>>>squares=[]>>>forxinrange(5):...squares.append(lambdan=x:n**2)
Εδώ, τοn=x
δημιουργεί μια νέα μεταβλητήn
τοπική στο lambda και υπολογίζεται όταν το lambda ορίζεται έτσι ώστε να έχει την ίδια τιμή που είχε τοx
σε εκείνο το σημείο βρόχου. Αυτό σημαίνει ότι η τιμή τουn
θα είναι0
στο πρώτο lambda,1
στο δεύτερο ,2
στο τρίτο και ούτω καθεξής. Επομένως κάθε lambda θα επιστρέψει τώρα το σωστό αποτέλεσμα:
>>>squares[2]()4>>>squares[4]()16
Σημειώστε ότι αυτή η συμπεριφορά δεν είναι ιδιόμορφη για το lambdas, αλλά ισχύει και για κανονικές λειτουργίες.
Πως μοιράζομαι καθολικές μεταβλητές σε modules;¶
Ο κανονικός τρόπος για να μοιράζεστε πληροφορίες μεταξύ των λειτουργικών μονάδων μέσα σε ένα μόνο πρόγραμμα είναι η δημιουργία ενός ειδικού module (συχνά ονομάζεται config ή cfg). Απλώς εισαγάγετε το module διαμόρφωσης σε όλες τα modules της εφαρμογής σας∙ το module στην συνέχεια γίνεται διαθέσιμο ως παγκόσμιο όνομα. Επειδή υπάρχει μόνο ένα παράδειγμα για κάθε module , οι αλλαγές που γίνονται στο αντικείμενο του module αντικατοπτρίζονται παντού. Για παράδειγμα:
config.py:
x=0# Default value of the 'x' configuration setting
mod.py:
importconfigconfig.x=1
main.py:
importconfigimportmodprint(config.x)
Λάβετε υπόψη ότι η χρήση ενός module είναι επίσης η βάση για την εφαρμογή του μοτίβου σχεδιασμού singleton, για τον ίδιο λόγο.
Ποιες είναι οι «βέλτιστες πρακτικές» για τη χρήση import σε ένα module;¶
Γενικά, μην χρησιμοποιείτεfrommodulenameimport*
. Κάτι τέτοιο δημιουργεί μια ακαταστασία στο importer’s namespace, και καθιστά πιο δύσκολο για τα linters να εντοπίσουν απροσδιόριστα ονόματα.
Εισαγωγή modules στην κορυφή ενός αρχείου. Με αυτόν τον τρόπο καθιστά σαφές ποια άλλα modules απαιτεί ο κώδικας σας και αποφεύγονται ερωτήσεις σχετικά με το αν το όνομα της μονάδας είναι εντός πεδίου. Χρησιμοποιώντας ένα import ανά γραμμή καθιστά εύκολη την προσθήκη και τη διαγραφή module imports, αλλά χρησιμοποιώντας πολλαπλά imports ανά γραμμής καταναλώνεται λιγότερος χώρος στην οθόνη.
Είναι καλή πρακτική εάν εισάγετε module με την ακόλουθη σειρά:
module βιβλιοθήκης τρίτων (ό, τι είναι εγκατεστημένο στο κατάλογο site-packages της Python) – π.χ.
dateutil
,requests
,PIL.Image
τοπικά αναπτυγμένα modules
Μερικές φορές είναι απαραίτητο να μετακινηθούν οι εισαγωγές σε μια συνάρτηση ή κλάση για να αποφευχθούν προβλήματα με τις κυκλικές εισαγωγές. Ο Gordon McMillan λέει:
Οι κυκλικές εισαγωγές είναι καλές όταν και τα δύο modules χρησιμοποιούν τη μορφή εισαγωγής «import <module>». Αποτυγχάνουν όταν το 2ο module θέλει να πάρει ένα όνομα από το πρώτο («from module import name») και η εισαγωγή είναι στο κορυφαίο επίπεδο. Αυτό συμβαίνει επειδή το πρώτο module είναι απασχολημένο με την εισαγωγή του 2ου.
Σε αυτήν την περίπτωση, εάν το δεύτερο module χρησιμοποιείται μόνο σε μια συνάρτηση, τότε η εισαγωγή μπορεί εύκολα να μεταφερθεί μέσα σε αυτήν την συνάρτηση. Από τη στιγμή που καλείται η εισαγωγή, το πρώτο module θα έχει ολοκληρώσει την αρχικοποίηση, και το δεύτερο module μπορεί να κάνει την εισαγωγή του.
Μπορεί επίσης να είναι απαραίτητο να μετακινήσετε τις εισαγωγές από το ανώτερο επίπεδο κώδικα, εάν ορισμένα από τα module είναι συγκεκριμένα για την πλατφόρμα. Σε αυτήν την περίπτωση, ενδέχεται να μην είναι καν δυνατή η εισαγωγή όλων των modules στο επάνω μέρος του αρχείου. Σε αυτήν την περίπτωση, η εισαγωγή των σωστών modules στον αντίστοιχο κώδικα για συγκεκριμένη πλατφόρμα είναι μια καλή επιλογή.
Μετακινήστε τις εισαγωγές σε τοπικό πεδίο, όπως μέσα σε έναν ορισμό συνάρτησης, μόνο εάν είναι απαραίτητο να λυθεί ένα πρόβλημα όπως η αποφυγή μιας κυκλικής εισαγωγής ή εάν προσπαθείτε να μειώσετε τον χρόνο προετοιμασίας μιας μονάδας. Αυτή η τεχνική είναι ιδιαίτερα χρήσιμη εάν πολλές από τις εισαγωγές δεν είναι απαραίτητες ανάλογα με τον τρόπο εκτέλεσης του προγράμματος. Μπορείτε επίσης να θέλετε να μετακινήσετε τις εισαγωγές σε μια συνάρτηση εάν τα modules χρησιμοποιούνται μόνο σε αυτήν τη συνάρτηση. Λάβετε υπόψη ότι η φόρτωση ενός module την πρώτη φορά μπορεί να είναι δαπανηρή λόγω της μιας φοράς της αρχικοποίησης του module, αλλά η φόρτωση ενός module πολλές φορές είναι σχεδόν δωρεάν, κοστίζοντας μόνο μερικές αναζητήσεις σε λεξικό. Ακόμα και αν το όνομα του module έχει ξεφύγει από το πεδίο εφαρμογής του, το module είναι πιθανώς διαθέσιμο στοsys.modules
.
Γιατί μοιράζονται οι προεπιλεγμένες τιμές μεταξύ των αντικειμένων;¶
Αυτός ο τύπος σφάλματος συνήθως δαγκώνει νεοφυείς προγραμματιστές. Σκεφτείτε αυτήν τη συνάρτηση:
deffoo(mydict={}):# Danger: shared reference to one dict for all calls...computesomething...mydict[key]=valuereturnmydict
Την πρώτη φορά που καλείτε αυτήν την συνάρτηση, τοmydict
περιέχει ένα μεμονωμένο στοιχείο. Τη δεύτερη φορά, τοmydict
περιέχει δύο στοιχεία γιατί όταν τοfoo()
ξεκινά την εκτέλεση, τοmydict
ξεκινά με ένα αντικείμενο ήδη μέσα.
Συχνά αναμένεται ότι μια κλήση συνάρτησης δημιουργεί νέα αντικείμενα για προεπιλεγμένες τιμές. Δεν συμβαίνει αυτό. Οι προεπιλεγμένες τιμές δημιουργούνται ακριβώς μία φορά, όταν ορίζεται η συνάρτηση. Εάν αυτό το αντικείμενο αλλάξει, όπως το λεξικό σε αυτό το παράδειγμα, επόμενες κλήσεις στην συνάρτηση θα αναφέρονται σε αυτό το αλλαγμένο αντικείμενο.
Εξ ορισμού, αμετάβλητα αντικείμενα όπως αριθμοί, συμβολοσειρές, πλειάδες καιNone
, είναι ασφαλή από αλλαγές. Οι αλλαγές σε μεταβλητά αντικείμενα όπως λεξικά, λίστες και παρουσίες κλάσεων μπορεί να οδηγήσουν σε σύγχυση.
Λόγω αυτής της δυνατότητας, είναι καλή πρακτική προγραμματισμού να μην χρησιμοποιείτε μεταβλητά αντικείμενα ως προεπιλεγμένες τιμές. Αντίθετα, χρησιμοποιείστεNone
ως προεπιλεγμένη τιμή και μέσα στην συνάρτηση, ελέγξτε εάν η παράμετρος είναιNone
και δημιουργήστε μια νέα λίστα/λεξικά/ό,τι και αν είναι. Για παράδειγμα μην γράψετε:
deffoo(mydict={}):...
αλλά:
deffoo(mydict=None):ifmydictisNone:mydict={}# create a new dict for local namespace
Αυτή η δυνατότητα μπορεί να είναι χρήσιμη. Όταν έχετε μια συνάρτηση που είναι χρονοβόρα για τον υπολογισμό, μια κοινή τεχνική είναι να αποθηκεύσετε προσωρινά τις παραμέτρους και την επιστρεφόμενη τιμή κάθε κλήσης στη συνάρτηση και να επιστρέφετε την προσωρινά αποθηκευμένη τιμή εάν ζητηθεί ξανά η ίδια τιμή. Αυτό ονομάζεται «memoizing», και μπορεί να εφαρμοστεί ως εξής:
# Callers can only provide two parameters and optionally pass _cache by keyworddefexpensive(arg1,arg2,*,_cache={}):if(arg1,arg2)in_cache:return_cache[(arg1,arg2)]# Calculate the valueresult=...expensivecomputation..._cache[(arg1,arg2)]=result# Store result in the cachereturnresult
Θα μπορούσατε να χρησιμοποιήσετε μια καθολική μεταβλητή που περιέχει ένα λεξικό αντί για την προεπιλεγμένη τιμή∙ Είναι θέμα γούστου.
Πώς μπορώ να μεταβιβάσω προαιρετικές παραμέτρους ή παραμέτρους λέξεων-κλειδιών από τη μια συνάρτηση στην άλλη;¶
Συλλέξτε τα ορίσματα χρησιμοποιώντας τους specifiers*
και**
στη λίστα παραμέτρων της συνάρτησης∙ Αυτό σας δίνει τα ορίσματα θέσης ως πλειάδα και τα ορίσματα λέξεων-κλειδιών ως λεξικό. Στη συνέχεια, μπορείτε να μεταβιβάσετε αυτά τα ορίσματα κατά την κλήση άλλης συνάρτησης με τη χρήση των*
και**
:
deff(x,*args,**kwargs):...kwargs['width']='14.3c'...g(x,*args,**kwargs)
Ποια είναι η διαφορά μεταξύ ορισμάτων και παραμέτρων;¶
Παράμετροι ορίζονται από τα ονόματα που εμφανίζονται στον ορισμό μιας συνάρτησης, ενώ ταορίσματα είναι οι τιμές που πραγματικά μεταβιβάζονται σε μια συνάρτηση κατά την κλήση της. Οι παράμετροι ορίζουν τιkind of arguments μπορεί να δεχτεί μια συνάρτηση. Για παράδειγμα δεδομένου του ορισμού της συνάρτησης:
deffunc(foo,bar=None,**kwargs):pass
foo,bar καιkwargs είναι παράμετροι τηςfunc
. Ωστόσο, όταν καλείται ηfunc
, για παράδειγμα:
func(42,bar=314,extra=somevar)
οι τιμές42
,314
, καιsomevar
είναι ορίσματα.
Γιατί η αλλαγή της λίστας “y” αλλάζει επίσης και τη λίστα “x”;¶
Αν γράψατε κώδικα όπως:
>>>x=[]>>>y=x>>>y.append(10)>>>y[10]>>>x[10]
μπορεί να αναρωτιέστε γιατί η προσθήκη ενός στοιχείου στοy
άλλαξε επίσης τοx
.
Υπάρχουν δύο παράγοντες που παράγουν αυτό το αποτέλεσμα:
Οι μεταβλητές είναι απλά ονόματα που αναφέρονται σε αντικείμενα. Κανόνας
y=x
δεν δημιουργείται αντίγραφο της λίστας – δημιουργεί μια νέα μεταβλητήy
που αναφέρεται στο ίδιο αντικείμενο που αναφέρεται τοx
. Αυτό σημαίνει ότι υπάρχει μόνο ένα αντικείμενο (η λίστα), και τοx
και τοy
αναφέρονται σε αυτό.Οι λίστες είναιmutable, που σημαίνει ότι μπορείτε να αλλάξετε το περιεχόμενό τους.
Μετά την κλήση στοappend()
, το περιεχόμενο του μεταβλητού αντικειμένου έχει αλλάξει από[]
σε[10]
. Επειδή και οι δύο μεταβλητές αναφέρονται στο ίδιο αντικείμενο, χρησιμοποιώντας οποιοδήποτε όνομα αποκτά πρόσβαση στην τροποποιημένη τιμή[10]
.
Αν αντιστοιχίσουμε ένα αμετάβλητο αντικείμενο σεx
:
>>>x=5# ints are immutable>>>y=x>>>x=x+1# 5 can't be mutated, we are creating a new object here>>>x6>>>y5
μπορούμε να δούμε ότι σε αυτήν την περίπτωσηx
καιy
δεν είναι πλέον ίσα. Αυτό συμβαίνει επειδή οι ακέραιοι αριθμοί είναιimmutable, και όταν κάνουμεx=x+1
δεν μεταλλάσσεται το int5
αυξάνοντας την τιμή του∙ αντίθετα, δημιουργούμε ένα νέο αντικείμενο (το int6
) και το εκχωρούμε στοx
(δηλαδή, αλλάζοντας το αντικείμενο στο οποίο αναφέρεται τοx
). Μετά από αυτήν την ανάθεση έχουμε δύο αντικείμενα (τα ints6
and5
) και δύο μεταβλητές που αναφέρονται σε αυτά (x
τώρα αναφέρεται σε6
αλλάy
ακόμα αναφέρεται στο5
).
Ορισμένες λειτουργίες (για παράδειγμαy.append(10)
καιy.sort()
) μεταλλάσσουν το αντικείμενο, ενώ επιφανειακά παρόμοιες πράξεις (για παράδειγμαy=y+[10]
καιsorted(y)
) δημιουργούν ένα νέο αντικείμενο. Γενικά στην Python (και σε όλες τις περιπτώσεις στην τυπική βιβλιοθήκη) μια μέθοδος που μεταλλάσσει ένα αντικείμενο θα επιστρέψειNone
για να αποφύγει τη σύγχυση των δύο τύπων πράξεων. Επομένως, εάν γράψετε κατά λάθοςy.sort()
νομίζοντας ότι θα σας δώσει ένα ταξινομημένο αντίγραφο τουy
, θα καταλήξετε μεNone
, το οποίο πιθανότατα θα προκαλέσει το πρόγραμμά σας να δημιουργήσει ένα λάθος το οποίο διαγιγνώσκεται εύκολα.
Ωστόσο, υπάρχει μία κατηγορία λειτουργιών όπου η ίδια πράξη έχει μερικές φορές διαφορετικές συμπεριφορές με διαφορετικούς τύπους: οι επαυξημένοι τελεστές ανάθεσης. Για παράδειγμα,+=
μεταλλάσσει τις λίστες αλλά όχι τις πλειάδες ή τους ints (a_list+=[1,2,3]
είναι ίσο μεa_list.extend([1,2,3])
και μεταλλάσσειa_list
, ενώ τοsome_tuple+=(1,2,3)
καιsome_int+=1
δημιουργεί νέα αντικείμενα).
Με άλλα λόγια:
Εάν έχουμε ένα μεταβλητό αντικείμενο (
list
,dict
,set
, κλπ.), μπορούμε να χρησιμοποιήσουμε ορισμένες συγκεκριμένες λειτουργίες για να το μεταλλάξουμε και όλες τις μεταβλητές που αναφέρονται σε αυτό θα δουν την αλλαγή.Εάν έχουμε ένα αμετάβλητο αντικείμενο (
str
,int
,tuple
, κλπ.), όλες οι μεταβλητές που αναφέρονται σε αυτό θα βλέπουν πάντα την ίδια τιμή, αλλά οι λειτουργίες που θα μετατρέπουν αυτήν την τιμή σε μια νέα τιμή επιστρέφουν πάντα ένα νέο αντικείμενο.
Εάν θέλετε να γνωρίζετε εάν δύο μεταβλητές αναφέρονται στο ίδιο αντικείμενο ή όχι, μπορείτε να χρησιμοποιήσετε τονis
operator, ή την ενσωματωμένη συνάρτησηid()
.
Πως μπορώ να γράψω μια συνάρτηση με παραμέτρους εξόδου (κλήση με αναφορά);¶
Να θυμάστε ότι τα ορίσματα μεταβιβάζονται με ανάθεση στην Python. Εφόσον η εκχώρηση δημιουργεί απλώς αναφορές σε αντικείμενα, δεν υπάρχει ψευδώνυμο μεταξύ ενός ονόματος ορίσματος σε αυτό που καλεί και σε αυτό που καλείται, και επομένως δεν υπάρχει κλήση προς αναφορά από μόνη της. Μπορείτε να επιτύχετε το επιθυμητό αποτέλεσμα με διάφορους τρόπους.
Επιστρέφοντας μια πλειάδα των αποτελεσμάτων:
>>>deffunc1(a,b):...a='new-value'# a and b are local names...b=b+1# assigned to new objects...returna,b# return new values...>>>x,y='old-value',99>>>func1(x,y)('new-value', 100)
Αυτή είναι σχεδόν πάντα η πιο ξεκάθαρη λύση.
Χρησιμοποιώντας καθολικές μεταβλητές. Αυτό δεν είναι ασφαλές για νήμα και δεν συνίσταται.
Περνώντας ένα μεταβλητό (με δυνατότητα αλλαγής επί τόπου) αντικείμενο:
>>>deffunc2(a):...a[0]='new-value'# 'a' references a mutable list...a[1]=a[1]+1# changes a shared object...>>>args=['old-value',99]>>>func2(args)>>>args['new-value', 100]
Περνώντας σε ένα λεξικό που μεταλλάσσεται:
>>>deffunc3(args):...args['a']='new-value'# args is a mutable dictionary...args['b']=args['b']+1# change it in-place...>>>args={'a':'old-value','b':99}>>>func3(args)>>>args{'a': 'new-value', 'b': 100}
Ή ομαδοποιήστε τιμές σε μια παρουσία κλάσης:
>>>classNamespace:...def__init__(self,/,**args):...forkey,valueinargs.items():...setattr(self,key,value)...>>>deffunc4(args):...args.a='new-value'# args is a mutable Namespace...args.b=args.b+1# change object in-place...>>>args=Namespace(a='old-value',b=99)>>>func4(args)>>>vars(args){'a': 'new-value', 'b': 100}
Δεν υπάρχει σχεδόν ποτέ καλός λόγος να γίνει αυτό περίπλοκο.
Η καλύτερη επιλογή σας είναι να επιστρέψετε μια πλειάδα που περιέχει πολλαπλά αποτελέσματα.
Πως δημιουργείτε μια συνάρτηση υψηλότερης τάξης στην Python;¶
Έχετε δύο επιλογές: μπορείτε να χρησιμοποιήσετε ένθετα πεδία ή μπορείτε να χρησιμοποιήσετε callable αντικείμενα. Για παράδειγμα, ας υποθέσουμε ότι θέλετε να ορίσετε τοlinear(a,b)
που επιστρέφει μια συνάρτησηf(x)
που υπολογίζει την τιμήa*x+b
. Χρησιμοποιώντας ένθετα πεδία:
deflinear(a,b):defresult(x):returna*x+breturnresult
Ή χρησιμοποιώντας ένα callable αντικείμενο:
classlinear:def__init__(self,a,b):self.a,self.b=a,bdef__call__(self,x):returnself.a*x+self.b
Και στις δύο περιπτώσεις:
taxes=linear(0.3,2)
δίνει ένα callable αντικείμενο όπουtaxes(10e6)==0.3*10e6+2
.
Η προσέγγιση του callable αντικειμένου έχει το μειονέκτημα ότι είναι λίγο πιο αργή και οδηγεί σε ελαφρώς μεγαλύτερο κώδικα. Ωστόσο, σημειώστε ότι μια συλλογή από callables μπορεί να μοιραστεί την υπογραφή τους μέσω κληρονομικότητας:
classexponential(linear):# __init__ inheriteddef__call__(self,x):returnself.a*(x**self.b)
Το αντικείμενο μπορεί να ενθυλακώσει την κατάσταση για πολλές μεθόδους:
classcounter:value=0defset(self,x):self.value=xdefup(self):self.value=self.value+1defdown(self):self.value=self.value-1count=counter()inc,dec,reset=count.up,count.down,count.set
Εδώinc()
,dec()
καιreset()
λειτουργούν σαν συναρτήσεις που μοιράζονται την ίδια μεταβλητή μέτρησης.
Πως μπορώ να αντιγράψω ένα αντικείμενο στην Python;¶
Γενικά, δοκιμάστεcopy.copy()
ήcopy.deepcopy()
για τη γενική περίπτωση. Δεν μπορούν να αντιγραφούν όλα τα αντικείμενα, αλλά τα περισσότερα μπορούν.
Ορισμένα αντικείμενα μπορούν να αντιγραφούν πιο εύκολα. Τα λεξικά έχουν μία μέθοδοcopy()
:
newdict=olddict.copy()
Οι ακολουθίες μπορούν να αντιγραφούν με τεμαχισμό:
new_l=l[:]
Πως μπορώ να βρω τις μεθόδους ή τα χαρακτηριστικά ενός αντικειμένου;¶
Για παράδειγμα μια κλάσηx
που ορίζεται από το χρήστη,dir(x)
επιστρέφει μια αλφαβητική λίστα με τα ονόματα που περιέχει τα χαρακτηριστικά της οντότητας και τις μεθόδους και τα χαρακτηριστικά που ορίζονται από την κλάση του.
Πως μπορεί ο κώδικας μου να ανακαλύψει το όνομα ενός αντικειμένου;¶
Γενικά μιλώντας, δεν μπορεί, επειδή τα αντικείμενα δεν έχουνε πραγματικά ονόματα. Ουσιαστικά, η εκχώρηση δεσμεύει πάντα ένα όνομα σε μια τιμή∙ το ίδιο ισχύει για τις δηλώσειςdef
καιclass
, αλλά σε αυτή την περίπτωση η τιμή είναι callable. Λάβετε υπόψη τον ακόλουθο κώδικα:
>>>classA:...pass...>>>B=A>>>a=B()>>>b=a>>>print(b)<__main__.A object at 0x16D07CC>>>>print(a)<__main__.A object at 0x16D07CC>
Αναμφισβήτητα η κλάση έχει ένα όνομα: παρόλο που είναι δεσμευμένη σε δύο ονόματα και καλείται μέσω του ονόματοςB
η δημιουργημένη οντότητα εξακολουθεί να αναφέρεται ως οντότητα της κλάσηςA
. Ωστόσο, είναι αδύνατο να πούμε είτε το όνομα της οντότητας είναιa
ήb
, καθώς και τα δύο ονόματα συνδέονται με την ίδια τιμή.
Γενικά μιλώντας, δεν θα πρέπει να είναι απαραίτητο ο κώδικας σας να «γνωρίζει τα ονόματα» συγκεκριμένων τιμών. Αν δεν γράφετε εσκεμμένα ενδοσκοπικά (introspective) προγράμματα, αυτό είναι συνήθως μια ένδειξη ότι μια αλλαγή προσέγγισης μπορεί να είναι επωφελής.
Στο comp.lang.python, ο Fredrik Lundh έδωσε μια εξαιρετική αναλογία ως απάντηση σε αυτήν την ερώτηση:
Με το ίδιο τρόπο που παίρνετε το όνομα αυτής της γάτας που βρήκατε στη βεράντα σας: ή ίδια γάτα (αντικείμενο) δεν μπορεί να σας πει το όνομά της και δεν την ενδιαφέρει πραγματικά - έτσι ο μόνος τρόπος για να μάθετε πως λέγεται είναι να ρωτήσετε όλους τους γείτονές σας (namespaces) αν είναι η γάτα τους (αντικείμενο)…
….και μην εκπλαγείτε αν διαπιστώσετε ότι είναι γνωστό με πολλά ονόματα, ή κανένα όνομα!
Τι συμβαίνει με την προτεραιότητα του τελεστή κόμματος;¶
Το κόμμα δεν είναι τελεστής στην Python. Σκεφτείτε αυτήν την συνεδρία:
>>>"a"in"b","a"(False, 'a')
Δεδομένου ότι το κόμμα δεν είναι τελεστής, αλλά διαχωριστικό μεταξύ των εκφράσεων, τα παραπάνω αξιολογούνται σαν να είχατε εισαγάγει:
("a"in"b"),"a"
δεν:
"a"in("b","a")
Το ίδιο ισχύει για τους διάφορους τελεστές εκχώρησης (=
,+=
κλπ). Δεν είναι πραγματικά τελεστές αλλά συντακτικοί delimiters σε δηλώσεις εκχώρησης.
Υπάρχει ισοδύναμο του τριαδικού τελεστή «?:» της C;¶
Ναι υπάρχει, Η σύνταξη έχει ως εξής:
[on_true]if[expression]else[on_false]x,y=50,25small=xifx<yelsey
Πριν εισαχθεί αυτή η σύνταξη στην Python 2.5, ένα κοινό ιδίωμα ήταν η χρήση λογικών τελεστών:
[έκφραση]and[αληθές]or[ψευδές]
Ωστόσο, αυτό το ιδίωμα δεν είναι ασφαλές, καθώς μπορεί να δώσει λανθασμένα αποτελέσματα όταν τοon_true έχει ψευδή δυαδική τιμή. Επομένως, είναι πάντα καλύτερο να χρησιμοποιήσετε τη φόρμα...if...else...
.
Είναι δυνατόν να γράψουμε ασαφή one-liners στην Python;¶
Ναι. Συνήθως αυτό γίνεται με ένθεση τουlambda
εντός τουlambda
. Δείτε τα ακόλουθα τρία παραδείγματα, ελαφρώς προσαρμοσμένα από τον Ulf Bartelt:
fromfunctoolsimportreduce# Primes < 1000print(list(filter(None,map(lambday:y*reduce(lambdax,y:x*y!=0,map(lambdax,y=y:y%x,range(2,int(pow(y,0.5)+1))),1),range(2,1000)))))# First 10 Fibonacci numbersprint(list(map(lambdax,f=lambdax,f:(f(x-1,f)+f(x-2,f))ifx>1else1:f(x,f),range(10))))# Mandelbrot setprint((lambdaRu,Ro,Iu,Io,IM,Sx,Sy:reduce(lambdax,y:x+'\n'+y,map(lambday,Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,Sy=Sy,L=lambdayc,Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,i=IM,Sx=Sx,Sy=Sy:reduce(lambdax,y:x+y,map(lambdax,xc=Ru,yc=yc,Ru=Ru,Ro=Ro,i=i,Sx=Sx,F=lambdaxc,yc,x,y,k,f=lambdaxc,yc,x,y,k,f:(k<=0)or(x*x+y*y>=4.0)or1+f(xc,yc,x*x-y*y+xc,2.0*x*y+yc,k-1,f):f(xc,yc,x,y,k,f):chr(64+F(Ru+x*(Ro-Ru)/Sx,yc,0,0,i)),range(Sx))):L(Iu+y*(Io-Iu)/Sy),range(Sy))))(-2.1,0.7,-1.2,1.2,30,80,24))# \___ ___/ \___ ___/ | | |__ lines on screen# V V | |______ columns on screen# | | |__________ maximum of "iterations"# | |_________________ range on y axis# |____________________________ range on x axis
Μην το δοκιμάσετε στο σπίτι, παιδιά!
Τι σημαίνει η κάθετος(/) στη λίστα παραμέτρων μιας συνάρτησης;¶
Μια κάθετος στη λίστα ορισμάτων μιας συνάρτησης υποδηλώνει ότι οι παράμετροι πριν από αυτήν είναι μόνο θέσης. Οι παράμετροι μόνο θέσης είναι εκείνες χωρίς εξωτερικά χρησιμοποιήσιμο όνομα. Κατά την κλήση μιας συνάρτησης που δέχεται παραμέτρους μόνο θέσης, τα ορίσματα αντιστοιχίζονται σε παραμέτρους που βασίζονται αποκλειστικά στη θέση τους. Για παράδειγμα ηdivmod()
είναι μια συνάρτηση που δέχεται μόνο παραμέτρους θέσης. Η τεκμηρίωσή τους μοιάζει με αυτό:
>>>help(divmod)Help on built-in function divmod in module builtins:divmod(x, y, /) Return the tuple (x//y, x%y). Invariant: div*y + mod == x.
Η κάθετος στο τέλος της λίστας παραμέτρων σημαίνει ότι και οι δύο παράμετροι είναι μόνο θέσης. Επομένως, η κλήση τηςdivmod()
με ορίσματα λέξεων κλειδιών θα οδηγούσε σε σφάλμα:
>>>divmod(x=3,y=4)Traceback (most recent call last): File"<stdin>", line1, in<module>TypeError:divmod() takes no keyword arguments
Αριθμοί και συμβολοσειρές¶
Πώς προσδιορίζω δεκαεξαδικούς ή οκταδικούς ακέραιους αριθμούς;¶
Για να καθορίσετε έναν οκταδικό ψηφίο, προηγείται την οκταδικής τιμής με ένα μηδέν, ¨και μετά ένα πεζό ή κεφαλαίο «o». Για παράδειγμα, για να ορίσετε τη μεταβλητή «a» στην οκταδική τιμή «10» (8 σε δεκαδικό), πληκτρολογήστε:
>>>a=0o10>>>a8
Το δεκαεξαδικό είναι εξίσου εύκολο. Απλώς προηγείται του δεκαεξαδικού αριθμού με ένα μηδέν, και μετά ένα πεζό ή κεφαλαίο «x». Τα δεκαεξαδικά ψηφία μπορούν να καθοριστούν με πεζά ή κεφαλαία. Για παράδειγμα, στον διερμηνέα Python:
>>>a=0xa5>>>a165>>>b=0XB2>>>b178
Γιατί το -22 // 10 επιστρέφει -3;¶
Οφείλεται κυρίως στην επιθυμία που τοi%j
να έχει το ίδιο πρόσημο με τοj
. Εάν το θέλετε αυτό, και θέλετε επίσης:
i==(i//j)*j+(i%j)
τότε η διαίρεση ακεραίου αριθμού πρέπει να επιστρέψει το υπόλοιπο. Η C απαιτεί επίσης να διατηρείται αυτή η ταυτότητα και, στη συνέχεια, οι μεταγλωττιστές που περικόπτουν τοi//j
πρέπει να κάνουν τοi%j
να έχει το ίδιο πρόσημο με τοi
.
Υπάρχουν λίγες πραγματικές περιπτώσεις χρήσης για τοi%j
όταν τοj
είναι αρνητικό. Όταν τοj
είναι θετικό, υπάρχουν πολλές, και σχεδόν όλες είναι πιο χρήσιμες γιαi%j
να είναι>=0
. Εάν το ρολόι λέει 10 τι έλεγε πριν από 200 ώρες;-190%12==2
είναι χρήσιμο∙ το-190%12==-10
είναι ένα σφάλμα που περιμένει να δαγκώσει.
Πώς μπορώ να πάρω το literal χαρακτηριστικό int αντί για το SyntaxError;¶
Η προσπάθεια αναζήτησης ενός literal χαρακτηριστικούint
με τον κανονικό τρόπο δίνει έναSyntaxError
επειδή η περίοδος θεωρείται ως υποδιαστολή:
>>>1.__class__ File"<stdin>", line1 1.__class__ ^SyntaxError:invalid decimal literal
Η λύση είναι να διαχωριστεί το literal από την τελεία είτε με κενό είτε με παρένθεση.
>>>1.__class__<class 'int'>>>>(1).__class__<class 'int'>
Πως μετατρέπω μια συμβολοσειρά σε έναν αριθμό;¶
Για ακέραιους αριθμούς, χρησιμοποιήστε τον ενσωματωμένο κατασκευαστή τύπουint()
, π.χ.int('144')==144
. Ομοίως, τοfloat()
μετατρέπει σε έναν αριθμός κινητής υποδιαστολής π.χ.float('144')==144.0
.
Από προεπιλογή, αυτά ερμηνεύουν τον αριθμό ως δεκαδικό, έτσι ώστεint('0144')==144
να είναι αληθές, και τοint('0x144')
να εγείρειValueError
. Τοint(string,base)
παίρνει τη βάση για μετατροπή ως δεύτερο προαιρετικό όρισμα, οπότε τοint('0x144',16)==324
. Εάν η βάση έχει καθοριστεί ως 0, ο αριθμός ερμηνεύεται χρησιμοποιώντας κανόνες της Python: ένα αρχικό “0o” υποδηλώνει οκταδικό , και το “0x” δείχνει έναν δεκαεξαδικό αριθμό.
Μην χρησιμοποιείτε την ενσωματωμένη συνάρτησηeval()
εάν το μόνο που χρειάζεστε είναι να μετατρέψετε συμβολοσειρές σε αριθμούς.eval()
θα είναι σημαντικά πιο αργή και παρουσιάζει κίνδυνο ασφαλείας: κάποιος θα μπορούσε να σας μεταβιβάσει μια έκφραση Python που μπορεί να έχει ανεπιθύμητες παρενέργειες. Για παράδειγμα κάποιος θα μπορούσε να περάσει το__import__('os').system("rm-rf$HOME")
το οποίο θα διαγράψει το home φάκελο.
eval()
έχει επίσης ως αποτέλεσμα την ερμηνεία των αριθμών ως εκφράσεις Python, έτσι ώστε π.χ. τοeval('09')
δίνει ένα συντακτικό σφάλμα επειδή η Python δεν επιτρέπει την εισαγωγή του “0” σε έναν δεκαδικό αριθμό (εκτός “0”).
Πως μετατρέπω έναν αριθμό σε συμβολοσειρά;¶
Για να μετατρέψετε, πχ τον αριθμό144
στη συμβολοσειρά'144'
, χρησιμοποιείστε τον ενσωματωμένο τύπο κατασκευήςstr()
. Εάν θέλετε μια δεκαεξαδική ή οκταδική αναπαράσταση, χρησιμοποιήστε τις ενσωματωμένες συναρτήσειςhex()
ήoct()
. Για φανταχτερή μορφοποίηση, βλ. τις ενότητεςf-strings καιFormat String Syntax π.χ."{:04d}".format(144)
αποδίδει'0144'
και"{:.3f}".format(1.0/3.0)
αποδίδει'0.333'
.
Πώς μπορώ να τροποποιήσω μια συμβολοσειρά στη θέση της;¶
Δεν μπορείτε, γιατί οι συμβολοσειρές είναι αμετάβλητες. Στις περισσότερες περιπτώσεις θα πρέπει απλώς να δημιουργήσετε μια νέα συμβολοσειρά από τα διάφορα μέρη από τα οποία θέλετε να τη συναρμολογήσετε. Ωστόσο, εάν χρειάζεστε ένα αντικείμενο με δυνατότητα τροποποίησης δεδομένων unicode, δοκιμάστε να χρησιμοποιήσετε ένα αντικείμενοio.StringIO
ή το modulearray
:
>>>importio>>>s="Hello, world">>>sio=io.StringIO(s)>>>sio.getvalue()'Hello, world'>>>sio.seek(7)7>>>sio.write("there!")6>>>sio.getvalue()'Hello, there!'>>>importarray>>>a=array.array('u',s)>>>print(a)array('u', 'Hello, world')>>>a[0]='y'>>>print(a)array('u', 'yello, world')>>>a.tounicode()'yello, world'
Πως μπορώ να χρησιμοποιήσω συμβολοσειρές για να καλέσω συναρτήσεις/μεθόδους;¶
Υπάρχουν διάφορες τεχνικές.
Το καλύτερο είναι να χρησιμοποιήσετε ένα λεξικό που αντιστοιχίζει συμβολοσειρές σε συναρτήσεις. Το κύριο πλεονέκτημα αυτής της τεχνικής είναι ότι οι συμβολοσειρές δεν χρειάζεται να ταιριάζουν με τα ονόματα των συναρτήσεων. Αυτή είναι επίσης η κύρια τεχνική που χρησιμοποιείται για την εξομοίωση μιας κατασκευής πεζών-κεφαλαίων:
defa():passdefb():passdispatch={'go':a,'stop':b}# Note lack of parens for funcsdispatch[get_input()]()# Note trailing parens to call function
Χρησιμοποιείστε την ενσωματωμένη συνάρτηση
getattr()
:importfoogetattr(foo,'bar')()
Σημειώστε ότι το
getattr()
λειτουργεί σε οποιοδήποτε αντικείμενο, συμπεριλαμβανομένων κλάσεων, οντοτήτων κλάσεων, modules, και ούτω καθεξής.Αυτό χρησιμοποιείται σε πολλά σημεία της τυπικής βιβλιοθήκης, όπως αυτό:
classFoo:defdo_foo(self):...defdo_bar(self):...f=getattr(foo_instance,'do_'+opname)f()
Χρησιμοποιήστε το
locals()
για να επιλύσετε το όνομα της συνάρτησης:defmyFunc():print("hello")fname="myFunc"f=locals()[fname]f()
Υπάρχει ισοδύναμο με το Perl’schomp()
για την αφαίρεση των νέων γραμμών από τις συμβολοσειρές;¶
Μπορείτε να χρησιμοποιήσετε τοS.rstrip("\r\n")
για να αφαιρέσετε όλες τις εμφανίσεις οποιουδήποτε terminator γραμμής από το τέλος της συμβολοσειράςS
χωρίς να αφαιρέσετε άλλα κενά. Η συμβολοσειράS
αντιπροσωπεύει περισσότερες από μία γραμμές στο τέλος, οι terminators γραμμής για όλες τις κενές γραμμές θα αφαιρεθούν:
>>>lines=("line 1\r\n"..."\r\n"..."\r\n")>>>lines.rstrip("\n\r")'line 1 '
Δεδομένου ότι αυτό είναι συνήθως επιθυμητό μόνο κατά την ανάγνωση κειμένου μία γραμμή τη φορά, η χρήση τουS.rstrip()
λειτουργεί καλά.
Υπάρχει αντίστοιχοscanf()
ήsscanf()
;¶
Όχι ως τέτοιο.
Για απλή ανάλυση εισόδου, η ευκολότερη προσέγγιση είναι συνήθως ο διαχωρισμός της γραμμής σε λέξεις οριοθετημένες με κενά διαστήματα χρησιμοποιώντας τη μέθοδοsplit()
αντικειμένων συμβολοσειρών και στη συνέχεια μετατροπή δεκαδικών συμβολοσειρών σε αριθμητικές τιμές χρησιμοποιώνταςint()
ήfloat()
.split()
υποστηρίζει μια προαιρετική παράμετρο «sep» που είναι χρήσιμη εάν η γραμμή χρησιμοποιεί κάτι διαφορετικό από κενό διάστημα ως διαχωριστικό.
Για πιο περίπλοκη ανάλυση εισόδου, τα regular expressions είναι πιο ισχυρά από τιςsscanf
της C και είναι πιο κατάλληλες για την εργασία.
Τι σημαίνει το σφάλμαUnicodeDecodeError
ήUnicodeEncodeError
;¶
Βλ τοUnicode HOWTO.
Μπορώ να τερματίσω μια ακατέργαστη συμβολοσειρά με περιττό αριθμό backslashes;¶
Μια ακατέργαστη συμβολοσειρά που τελειώνει με περιττό αριθμό backslashes θα ξεφύγει από το απόσπασμα της συμβολοσειράς:
>>>r'C:\this\will\not\work\' File"<stdin>", line1r'C:\this\will\not\work\'^SyntaxError:unterminated string literal (detected at line 1)
Υπάρχουν αρκετές λύσεις για αυτό. Ο ένας είναι να χρησιμοποιήσετε κανονικές συμβολοσειρές και να διπλασιάσετε τα backslashes:
>>>'C:\\this\\will\\work\\''C:\\this\\will\\work\\'
Ένα άλλο είναι να συνδέσετε μια κανονική συμβολοσειρά που περιέχει ένα escaped backslash στην ακατέργαστη συμβολοσειρά:
>>>r'C:\this\will\work''\\''C:\\this\\will\\work\\'
Είναι επίσης δυνατό να χρησιμοποιήσετε τοos.path.join()
για να προσθέσετε ένα backslash στα Windows:
>>>os.path.join(r'C:\this\will\work','')'C:\\this\\will\\work\\'
Λάβετε υπόψη ότι ενώ ένα backslash θα «escape» από ένα εισαγωγικό για τον προσδιορισμό του σημείου που τελειώνει η ακατέργαστη συμβολοσειρά, δεν υπάρχει διαφυγή κατά την ερμηνεία της τιμής της ακατέργαστης συμβολοσειράς. Δηλαδή, το backslash παραμένει παρόν στην τιμή της ακατέργαστης συμβολοσειράς:
>>>r'backslash\'preserved'"backslash\\'preserved"
Δείτε επίσης την προδιαγραφή στηνlanguage reference.
Απόδοση¶
Το πρόγραμμα μου είναι πολύ αργό. Πως μπορώ να το επιταχύνω;¶
Αυτό είναι δύσκολο, γενικά. Πρώτον, εδώ είναι μια λίστα με πράγματα που πρέπει να θυμάστε πριν βουτήξετε περαιτέρω:
Τα χαρακτηριστικά απόδοσης διαφέρουν μεταξύ των υλοποιήσεων Python. Αυτή η FAQ εστιάζει στοCPython.
Η συμπεριφορά μπορεί να διαφέρει μεταξύ των λειτουργικών συστημάτων, ειδικά όταν μιλάμε για I/O ή multi-threading.
Θα πρέπει πάντα να βρίσκετε τα hot spot στο πρόγραμμά σαςπριν επιχειρήσετε να βελτιστοποιήσετε οποιονδήποτε κώδικα (βλ. το module
profile
).Η σύνταξη σεναρίων συγκριτικής αξιολόγησης θα σας επιτρέψει να κάνετε iterate γρήγορα κατά την αναζήτηση βελτιώσεων (βλ. το module
timeit
).Συνίσταται ανεπιφύλακτα να έχετε καλή κάλυψη κώδικα (μέσω unit testing ή οποιασδήποτε άλλης τεχνικής) πριν από την πιθανή εισαγωγή κρυμμένων παλινδρομήσεων (regressions) σε εξελιγμένες βελτιστοποιήσεις.
Τούτου λεχθέντος, υπάρχουν πολλά κόλπα για την επιτάχυνση του κώδικα Python. Ακολουθούν ορισμένες γενικές αρχές που βοηθούν πολύ στην επίτευξη αποδεκτών επιπέδων απόδοσης:
Το να κάνετε τους αλγορίθμους σας πιο γρήγορους (ή να αλλάξετε σε ταχύτερους) μπορεί να αποφέρει πολύ μεγαλύτερα οφέλη από το να προσπαθείτε να σκορπίσετε κόλπα μικρό βελτιστοποίησης σε όλο τον κώδικά σας.
Χρησιμοποιήστε τις σωστές δομές δεδομένων. Μελετήστε την τεκμηρίωση για τοΤύποι Built-in και το module
collections
.Όταν η τυπική βιβλιοθήκη παρέχει ένα πρωτόγονο για να κάνετε κάτι, είναι πιθανό (αν και δεν είναι εγγυημένο) να είναι πιο γρήγορο από οποιαδήποτε εναλλακτική λύση που μπορείτε να βρείτε. Αυτό ισχύει διπλά για πρωτόγονα γραμμένα σε C, όπως ενσωματωμένα και ορισμένους τύπους επεκτάσεων. Για παράδειγμα, φροντίστε να χρησιμοποιήσετε είτε την ενσωματωμένη μέθοδο
list.sort()
είτε τη σχετική συνάρτησηsorted()
για να κάνετε ταξινόμηση (και δείτε τοSorting Techniques για παραδείγματα μέτριος προηγμένης χρήσης).Οι αφαιρέσεις τείνουν να δημιουργούν έμμεσες κατευθύνσεις και αναγκάζουν τον διερμηνέα να εργαστεί περισσότερο. Εάν τα επίπεδα της έμμεσης κατεύθυνσης υπερτερούν του όγκου της χρήσιμης εργασίας που γίνεται, το πρόγραμμα σας θα είναι πιο αργό. Θα πρέπει να αποφύγετε την υπερβολική αφαίρεση, ειδικά με τη μορφή μικροσκοπικών συναρτήσεων ή μεθόδων (που είναι επίσης συχνά επιζήμια για την αναγνωσιμότητα).
Εάν έχετε φτάσει στο όριο του τι μπορεί να επιτρέπει η καθαρή Python, υπάρχουν εργαλεία που θα σας απομακρύνουν. Για παράδειγμα, τοCython μπορεί να μεταγλωττίσει μια ελαφρώς τροποποιημένη έκδοση του κώδικα Python σε μια επέκταση C, και μπορεί να χρησιμοποιηθεί σε πολλές διαφορετικές πλατφόρμες. Η Cython μπορεί να εκμεταλλευτεί την μεταγλώττιση (και τους προαιρετικούς σχολιασμούς) για να κάνει τον κώδικα σας πολύ πιο γρήγορο από όταν ερμηνεύεται. Εάν είστε σίγουροι για τις δεξιότητές σας στον προγραμματισμό C, μπορείτε επίσης ναwrite a C extension module μόνοι σας.
Δείτε επίσης
Η σελίδα wiki που είναι αφιερωμένη σεσυμβουλές απόδοσης.
Ποιος είναι ο πιο αποτελεσματικός τρόπος για να συνδέσετε πολλές συμβολοσειρές μεταξύ τους;¶
Τα αντικείμεναstr
καιbytes
είναι αμετάβλητα, επομένως η σύνδεση πολλών συμβολοσειρών μεταξύ τους είναι αναποτελεσματική καθώς κάθε συνένωση δημιουργεί ένα νέο αντικείμενο. Στη γενική περίπτωση, το συνολικό κόστος χρόνου εκτέλεσης είναι τετραγωνικό στο συνολικό μήκος συμβολοσειράς.
Για να συγκεντρώσετε πολλά αντικείμεναstr
, το προτεινόμενο ιδίωμα είναι να τα τοποθετήσετε σε μια λίστα και να καλέσετε το στοstr.join()
τέλος:
chunks=[]forsinmy_strings:chunks.append(s)result=''.join(chunks)
(ένα άλλο λογικά αποτελεσματικό ιδίωμα είναι να χρησιμοποιήσετε τοio.StringIO
)
Για τη συγκέντρωση πολλών αντικειμένωνbytes
, το συνιστώμενο ιδίωμα είναι η επέκταση ενός αντικειμένουbytearray
χρησιμοποιώντας επιτόπια συνένωση (ο τελεστής+=
):
result=bytearray()forbinmy_bytes_objects:result+=b
Ακολουθίες (Πλειάδες/Λίστες)¶
Πως μπορώ να κάνω μετατροπή μεταξύ πλειάδων και λιστών;¶
Ο κατασκευαστής τύπουtuple(seq)
μετατρέπει οποιαδήποτε ακολουθία (στην πραγματικότητα οποιοδήποτε iterable) σε πλειάδα με τα ίδια στοιχεία στην ίδια σειρά.
Για παράδειγμα, τοtuple([1,2,3])
αποδίδει(1,2,3)
και τοtuple('abc')
αποδίδει('a','b','c')
. Εάν το όρισμα είναι πλειάδα, δεν δημιουργεί αντίγραφο αλλά επιστρέφει το ίδιο αντικείμενο, επομένως είναι φτηνό να καλέσετε τοtuple()
όταν δεν είστε σίγουροι ότι ένα αντικείμενο είναι ήδη πλειάδα.
Ο κατασκευαστής τύπωνlist(seq)
μετατρέπει οποιαδήποτε ακολουθία ή iterable σε μια λίστα με τα ίδια στοιχεία στην ίδια σειρά. Για παράδειγμα, τοlist((1,2,3))
αποδίδει[1,2,3]
καιlist('abc')
αποδίδει['a','b','c']
. Αν το όρισμα είναι λίστα, δημιουργεί απλώς ένα αντίγραφο όπως θα έκανε τοseq[:]
.
Τι είναι αρνητικός δείκτης;¶
Οι ακολουθίες Python indexed με θετικούς αριθμούς και αρνητικούς αριθμούς. Για θετικούς αριθμούς το 0 είναι ο πρώτος δείκτης 1 είναι ο δεύτερος δείκτης και ούτω καθεξής. Για αρνητικούς δείκτες το -1 είναι ο τελευταίος δείκτης και το -2 είναι ο προτελευταίος (δίπλα στο τελευταίο) δείκτης και ούτω καθεξής. Σκεφτείτε τοseq[-n]
ως το ίδιο με τοseq[len(seq)-n]
.
Η χρήση αρνητικών δεικτών μπορεί ναι είναι πολύ βολική. Για παράδειγμαS[:-1]
είναι όλη η συμβολοσειρά εκτός από τον τελευταίο χαρακτήρα της, ο οποίος είναι χρήσιμος για την αφαίρεση της νέας γραμμής που ακολουθεί μια συμβολοσειρά.
Πώς μπορώ να επαναλάβω μια ακολουθία με αντίστροφη σειρά;¶
Χρησιμοποιείστε την ενσωματωμένη συνάρτησηreversed()
:
forxinreversed(sequence):...# do something with x ...
Αυτό δεν θα επηρεάσει την αρχική σας ακολουθία, αλλά δημιουργήστε ένα νέο αντίγραφο με αντίστροφη σειρά για επανάληψη.
Πως αφαιρείτε διπλότυπα από μια λίστα;¶
Δείτε το Python Cookbook για μια μακρά συζήτηση σχετικά με πολλούς τρόπους για να το κάνετε αυτό:
Εάν δεν σας πειράζει να αναδιατάξετε τη λίστα, ταξινομήστε την και μετά σαρώστε από το τέλος της λίστας, διαγράφοντας τα διπλότυπα καθώς προχωράτε:
ifmylist:mylist.sort()last=mylist[-1]foriinrange(len(mylist)-2,-1,-1):iflast==mylist[i]:delmylist[i]else:last=mylist[i]
Εάν όλα τα στοιχεία της λίστας μπορούν να χρησιμοποιηθούν ως κλειδιά συνόλου (δηλαδή είναι όλα ταhashable) αυτό είναι συχνά πιο γρήγορο:
mylist=list(set(mylist))
Αυτό μετατρέπει τη λίστα σε ένα σύνολο, αφαιρώντας έτσι τα διπλότυπα και στη συνέχεια ξανά σε λίστα.
Πως αφαιρείτε πολλαπλά στοιχεία από μία λίστα¶
Όπως και με την κατάργηση των διπλότυπων, το ρητό iterating αντίστροφα με μια συνθήκη διαγραφής είναι μια πιθανότητα. Ωστόσο, είναι ευκολότερο και πιο γρήγορο να χρησιμοποιήσετε την αντικατάσταση τμημάτων με ένα έμμεσο ή ρητώς προς τα εμπρός iteration. Ακολουθούν τρεις παραλλαγές:
mylist[:]=filter(keep_function,mylist)mylist[:]=(xforxinmylistifkeep_condition)mylist[:]=[xforxinmylistifkeep_condition]
Το comprehension της λίστας μπορεί να είναι ταχύτερο.
Πως μπορείτε να φτιάξετε έναν πίνακα στην Python;¶
Χρησιμοποιήστε μια λίστα:
["αυτή",1,"είναι","μια","λίστα"]
Οι λίστες είναι ισοδύναμες με τους πίνακες της C ή Pascal στην χρονική τους πολυπλοκότητα∙ η κύρια διαφορά είναι ότι μια λίστα Python μπορεί να περιέχει αντικείμενα πολλών διαφορετικών τύπων.
Το modulearray
παρέχει επίσης μεθόδους για τη δημιουργία πινάκων σταθερών τύπων με συμπαγείς αναπαραστάσεις, αλλά είναι πιο αργές στην ευρετηρίαση από τις λίστες. Σημειώστε επίσης ότι τοNumPy και άλλο πακέτα τρίτων, ορίζουν δομές τύπους array με διάφορα χαρακτηριστικά επίσης.
Για να λάβετε συνδεδεμένες λίστες τύπου Lisp, μπορείτε να εξομοιώσετεcons κελιά χρησιμοποιώντας πλειάδες:
lisp_list=("όπως",("αυτό",("το παράδειγμα",None)))
Εάν είναι επιθυμητή η μεταβλητότητα, μπορείτε να χρησιμοποιήσετε λίστες αντί για πλειάδες. Εδώ το ανάλογο ενός Lispcar είναιlisp_list[0]
και το ανάλογο τουcdr είναιlisp_list[1]
. Μόνο κάντε το αν είστε βέβαιοι ότι πραγματικά χρειάζεται, γιατί είναι συνήθως πιο αργό και από τη χρήση λιστών Python.
Πως φτιάχνω μια πολυδιάστατη λίστα;¶
Μάλλον προσπαθήσατε να φτιάξετε έναν πολυδιάστατο πίνακα σαν αυτόν:
>>>A=[[None]*2]*3
Αυτό φαίνεται σωστό αν το εκτυπώσετε:
>>>A[[None, None], [None, None], [None, None]]
Αλλά όταν εκχωρείτε μια τιμή, εμφανίζεται σε πολλά σημεία:
>>>A[0][0]=5>>>A[[5, None], [5, None], [5, None]]
Ο λόγος είναι ότι η αναπαραγωγή μιας λίστα με*
δεν δημιουργεί αντίγραφα, δημιουργεί μόνο αναφορές στα υπάρχοντα αντικείμενα. Το*3
δημιουργεί μια λίστα που περιέχει 3 αναφορές στην ίδια λίστα μήκους δύο. Οι αλλαγές σε μία σειρά θα εμφανίζονται σε όλες τις σειρές, κάτι που σχεδόν σίγουρα δεν είναι αυτό που θέλετε.
Η προτεινόμενη προσέγγιση είναι να δημιουργήσετε πρώτα μια λίστα με το επιθυμητό μήκος και στη συνέχεια να συμπληρώσετε κάθε στοιχείο με μια νέα λίστα:
A=[None]*3foriinrange(3):A[i]=[None]*2
Αυτό δημιουργεί μια λίστα που περιέχει 3 διαφορετικές λίστες με μήκος δύο. Μπορείτε επίσης να χρησιμοποιήσετε ένα comprehension λίστας:
w,h=2,3A=[[None]*wforiinrange(h)]
Εναλλακτικά, μπορείτε να χρησιμοποιήσετε μια επέκταση που παρέχει έναν τύπο δεδομένων μήτρας (matrix)∙ ΤοNumPy είναι το πιο γνωστό.
Πώς μπορώ να εφαρμόσω μια μέθοδο ή μια συνάρτηση σε μια ακολουθία αντικειμένων;¶
Για να καλέστε μια μέθοδο ή μια συνάρτηση και να συγκεντρώσετε τις επιστρεφόμενες τιμές είναι μια λίστα, έναlist comprehension είναι μια κομψή λύση:
result=[obj.method()forobjinmylist]result=[function(obj)forobjinmylist]
Για να εκτελέσετε απλώς τη μέθοδο ή τη συνάρτηση χωρίς να αποθηκεύσετε τις επιστρεφόμενες τιμές, αρκεί ένας απλός βρόχοςfor
:
forobjinmylist:obj.method()forobjinmylist:function(obj)
Γιατί το a_tuple[i] += [“item”] δημιουργεί μια εξαίρεση όταν λειτουργεί η προσθήκη;¶
Αυτό οφείλεται σε έναν συνδυασμό του γεγονότος ότι οι επαυξημένοι τελεστές εκχώρησης είναι τελεστέςεκχώρησης και της διαφοράς μεταξύ μεταβλητών και αμετάβλητων αντικειμένων στην Python.
Αυτή η συζήτηση ισχύει γενικά όταν οι επαυξημένοι τελεστές εκχώρησης εφαρμόζονται σε στοιχεία μιας πλειάδας που δείχνουν σε μεταβλητά αντικείμενα, αλλά θα χρησιμοποιήσουμεlist
και+=
ως υπόδειγμά μας.
Εάν γράψετε:
>>>a_tuple=(1,2)>>>a_tuple[0]+=1Traceback (most recent call last):...TypeError:'tuple' object does not support item assignment
Ο λόγος για την εξαίρεση θα πρέπει να είναι αμέσως σαφής: το1
προστίθεται στο αντικείμενοa_tuple[0]
δείχνει στο (1
), παράγοντας το αντικείμενο αποτελέσματος,2
, αλλά όταν προσπαθούμε να αντιστοιχίσουμε το αποτέλεσμα του υπολογισμού,2
, στο στοιχείο0
της πλειάδας, λαμβάνουμε ένα σφάλμα επειδή δεν μπορούμε να αλλάξουμε αυτό που δείχνει ένα στοιχείο μιας πλειάδας.
Κάτω από τα καλύμματα, αυτό που κάνει αυτή η επαυξημένη δήλωση ανάθεσης είναι περίπου το εξής:
>>>result=a_tuple[0]+1>>>a_tuple[0]=resultTraceback (most recent call last):...TypeError:'tuple' object does not support item assignment
Είναι το τμήμα ανάθεσης της λειτουργίας που παράγει το σφάλμα, αφού μια πλειάδα είναι αμετάβλητη.
Όταν γράφετε κάτι σαν:
>>>a_tuple=(['foo'],'bar')>>>a_tuple[0]+=['item']Traceback (most recent call last):...TypeError:'tuple' object does not support item assignment
Η εξαίρεση είναι λίγο πιο εκπληκτική, και ακόμη πιο εκπληκτικό είναι το γεγονός ότι παρόλο που υπήρχε ένα σφάλμα, το παράρτημα λειτούργησε:
>>>a_tuple[0]['foo', 'item']
Για να δείτε γιατί συμβαίνει αυτό, πρέπει να γνωρίζετε ότι (α) εάν ένα αντικείμενο υλοποιεί μια μαγική μέθοδο__iadd__()
, που καλείται όταν εκτελείται η επαυξημένη ανάθεση+=
και η τιμή επιστροφής είναι αυτή που χρησιμοποιείται στη δήλωση εκχώρησης∙ και (β) για λίστες,__iadd__()
ισοδυναμεί με την κλήση τουextend()
στη λίστα και επιστρέφει τη λίστα. Για αυτό λέμε ότι για λίστες+=
είναι μια «συντομογραφία» γιαlist.extend()
:
>>>a_list=[]>>>a_list+=[1]>>>a_list[1]
Αυτό ισοδυναμεί με:
>>>result=a_list.__iadd__([1])>>>a_list=result
Το αντικείμενο στο οποίο υποδεικνύεται από το a_list έχει μεταλλαχθεί και ο δείκτης στο μεταλλαγμένο αντικείμενο έχει εκχωρηθεί πίσω στοa_list
. Το τελικό αποτέλεσμα της ανάθεσης είναι ένα no-op, καθώς είναι ένας δείκτης στο ίδιο αντικείμενο που τοa_list
έδειχνε προηγουμένως, αλλά η ανάθεση εξακολουθεί να γίνεται.
Έτσι, στο παράδειγμά μας, αυτό που συμβαίνει είναι ισοδύναμο με:
>>>result=a_tuple[0].__iadd__(['item'])>>>a_tuple[0]=resultTraceback (most recent call last):...TypeError:'tuple' object does not support item assignment
Το__iadd__()
πετυχαίνει, και έτσι η λίστα επεκτείνεται, αλλά παρόλο που τοαποτέλεσμα
δείχνει στο ίδιο αντικείμενο που δείχνει ήδη τοa_tuple[0]
, αυτή η τελική ανάθεση εξακολουθεί να έχει ως αποτέλεσμα ένα λάθος, γιατί οι πλειάδες είναι αμετάβλητες.
Θέλω να κάνω μια περίπλοκη ταξινόμηση: μπορείτε να κάνετε ένα Schwartzian Transform στην Python;¶
Η τεχνική, που αποδίδεται στον Randal Schwartz της κοινότητας Perl, ταξινομεί τα στοιχεία μιας λίστας με βάση μια μέτρηση που αντιστοιχίζει κάθε στοιχείο στην « τιμή ταξινόμησης» του. Στην Python, χρησιμοποιήστε το όρισμαkey
για τη μέθοδοlist.sort()
:
Isorted=L[:]Isorted.sort(key=lambdas:int(s[10:15]))
Πως μπορώ να ταξινομήσω μια λίστα με βάση τις τιμές από μια άλλη λίστα;¶
Συγχωνεύστε τα σε έναν iterator πλειάδων, ταξινομήστε τη λίστα που προκύπτει και, στην συνέχεια επιλέξτε το στοιχείο που θέλετε.
>>>list1=["τι","κάνω","ταξινόμηση","με"]>>>list2=["κάτι","διαφορετικό","του","ταξινομώ"]>>>pairs=zip(list1,list2)>>>pairs=sorted(pairs)>>>pairs[("Εγώ", 'διαφορετικά'), ('από', 'ταξινομώ'), ('ταξινόμηση', 'σε'), ('τι', 'κάτι')]>>>result=[x[1]forxinpairs]>>>result['διαφορετικά', 'ταξινομώ', 'με', 'κάτι']
Αντικείμενα¶
Τι είναι μια κλάση;¶
Μια κλάση είναι ο συγκεκριμένος τύπος αντικειμένου που δημιουργείται με την εκτέλεση μιας δήλωσης κλάσης. Τα αντικείμενα κλάσης χρησιμοποιούνται ως πρότυπα για τη δημιουργία αντικειμένων παρουσίας, τα οποία ενσωματώνουν τόσο τα δεδομένα (χαρακτηριστικά) όσο και τον κώδικα (μεθόδους) ειδικά για έναν τύπο δεδομένων.
Μια κλάση μπορεί να βασίζεται σε μία ή περισσότερες άλλες κλάσεις, που ονομάζονται βασικές κλάσεις της. Στη συνέχεια κληρονομεί τα χαρακτηριστικά και τις μεθόδους των βασικών κλάσεων. Αυτό επιτρέπει σε ένα μοντέλο αντικειμένου να βελτιωθεί διαδοχικά με κληρονομικότητα. Μπορεί να έχετε μια γενική κλάσηMailbox
που παρέχει βασικές μεθόδους πρόσβασης για ένα γραμματοκιβώτιο και υποκλάσεις όπωςMboxMailbox
,MaildirMailbox
,OutlookMailbox
που χειρίζονται διάφορες συγκεκριμένες μορφές γραμματοκιβωτίου.
Τι είναι μια μέθοδος;¶
Μια μέθοδος είναι μια συνάρτηση σε κάποιο αντικείμενοx
που συνήθως καλείτε ωςx.name(ορίσματα...)
. Οι μέθοδοι ορίζονται ως συναρτήσεις εντός του ορισμού κλάσης:
classC:defmeth(self,arg):returnarg*2+self.attribute
Τι είναι το self;¶
Το self είναι απλώς ένα συμβατικό όνομα για το πρώτο όρισμα μιας μεθόδου. Μια μέθοδος που ορίζεται ωςmeth(self,a,b,c)
πρέπει να ονομάζεταιx.meth(a,b,c)
για κάποιο παράδειγμαx
της κλάσης στην οποία εμφανίζεται ο ορισμός∙ η καλούμενη μέθοδος θα ονομάζεταιmeth(x,a,b,c)
.
Βλ. επίσηςWhy must “self” be used explicitly in method definitions and calls?.
Πώς μπορώ να ελέγξω εάν ένα αντικείμενο είναι μια οντότητα μιας δεδομένης κλάσης ή μιας υποκλάσης της;¶
Χρησιμοποιήστε την ενσωματωμένη συνάρτησηisinstance(obj,cls)
. Μπορείτε να ελέγξετε εάν ένα αντικείμενο είναι μια παρουσία οποιασδήποτε από έναν αριθμό κλάσεων παρέχοντας μια πλειάδα αντί για μια μεμονωμένη κλάση, π.χ.isinstance(obj,(class1,class2,...))
, και μπορεί επίσης να ελέγξει εάν ένα αντικείμενο είναι ένας από τους ενσωματωμένους τύπους της Python, π.χ.isinstance(obj,str)
ήisinstance(obj,(int,float,complex))
.
Λάβετε υπόψη ότι τοisinstance()
ελέγχει επίσης για εικονική κληρονομικότητα από μιαabstract base class. Έτσι, η δοκιμή θα επιστρέψειTrue
για μια εγγεγραμμένη κλάση ακόμα κι αν δεν έχει κληρονομήσει άμεσα ή έμμεσα από αυτό. Για να ελέγξετε μια «αληθινή κληρονομικότητα», σαρώστε τοMRO της κλάσης:
fromcollections.abcimportMappingclassP:passclassC(P):passMapping.register(P)
>>>c=C()>>>isinstance(c,C)# directTrue>>>isinstance(c,P)# indirectTrue>>>isinstance(c,Mapping)# virtualTrue# Actual inheritance chain>>>type(c).__mro__(<class 'C'>, <class 'P'>, <class 'object'>)# Test for "true inheritance">>>Mappingintype(c).__mro__
Λάβετε υπόψη ότι τα περισσότερα προγράμματα δεν χρησιμοποιούν τοisinstance()
σε κλάσεις που ορίζονται από τη χρήστη πολύ συχνά. Εάν αναπτύσσετε μόνοι σας τις κλάσεις, ένα πιο σωστό αντικειμενοστρεφής στυλ είναι να ορίζετε μεθόδους στις κλάσεις που ενσωματώνουν μια συγκεκριμένη συμπεριφορά, αντί να ελέγχετε την κλάση του αντικειμένου και να κάνετε κάτι διαφορετικό με βάση την κλάση που είναι, για παράδειγμα, εάν έχετε μια συνάρτηση που κάνει κάτι:
defsearch(obj):ifisinstance(obj,Mailbox):...# code to search a mailboxelifisinstance(obj,Document):...# code to search a documentelif...
Μια καλύτερη προσέγγιση είναι να ορίσετε μια μέθοδοsearch()
σε όλες τις κλάσεις και απλώς να την καλέσετε:
classMailbox:defsearch(self):...# code to search a mailboxclassDocument:defsearch(self):...# code to search a documentobj.search()
Τι είναι το delegation;¶
Το delegation είναι μια αντικειμενοστραφής τεχνική (ονομάζεται επίσης μοτίβο σχεδίασης). Ας υποθέσουμε ότι έχετε ένα αντικείμενοx
και θέλετε να αλλάξετε τη συμπεριφορά μιας μόνο από τις μεθόδους του. Μπορείτε να δημιουργήσετε μια νέα κλάση που παρέχει μια νέα υλοποίηση της μεθόδου που σας ενδιαφέρει να αλλάξετε και εκχωρεί όλες τις άλλες μεθόδους στην αντίστοιχη μέθοδο τουx
.
Οι προγραμματιστές Python μπορούν εύκολα να υλοποιήσουν την ανάθεση. Για παράδειγμα, η ακόλουθη κλάση υλοποιεί μια κλάση που συμπεριφέρεται σαν αρχείο αλλά μετατρέπει όλα τα γραπτά δεδομένα σε κεφαλαία:
classUpperOut:def__init__(self,outfile):self._outfile=outfiledefwrite(self,s):self._outfile.write(s.upper())def__getattr__(self,name):returngetattr(self._outfile,name)
Εδώ η κλάσηUpperOut
επαναπροσδιορίζει τη μέθοδοwrite()
για να μετατρέψει τη συμβολοσειρά ορίσματος σε κεφαλαία πριν καλέσει την υποκείμενη μέθοδοself._outfile.write()
. Όλες οι άλλες μέθοδοι εκχωρούνται στο υποκείμενο αντικείμενοself._outfile
. Το delegation ολοκληρώνεται μέσω της μεθόδου__getattr__()
. Συμβουλευτείτε τοthe language reference για περισσότερες πληροφορίες σχετικά με τον έλεγχο της πρόσβασης.
Λάβετε υπόψη ότι για πιο γενικές περιπτώσεις η ανάθεση μπορεί να γίνει πιο δύσκολη. Όταν τα χαρακτηριστικά πρέπει να οριστούν καθώς και να ανακτηθούν, η κλάση πρέπει να ορίσει μια μέθοδο__setattr__()
επίσης, και πρέπει να το κάνει προσεκτικά. Η βασική υλοποίηση του__setattr__()
είναι περίπου ισοδύναμο με το εξής:
classX:...def__setattr__(self,name,value):self.__dict__[name]=value...
Πολλές υλοποιήσεις__setattr__()
καλούν την :object.__setattr__()
για να θέσουν μια μεταβλητή στον εαυτό τους χωρίς να προκαλούν άπειρη αναδρομή.
classX:def__setattr__(self,name,value):# Custom logic here...object.__setattr__(self,name,value)
Εναλλακτικά, είναι δυνατό να ορίσετε χαρακτηριστικά εισάγοντας καταχωρήσεις στοself.__dict__
απευθείας.
Πώς μπορώ να καλέσω μια μέθοδο που ορίζεται σε μια βασική κλάση από μια παράγωγη κλάση που την επεκτείνει;¶
Χρησιμοποιήστε την ενσωματωμένη συνάρτησηsuper()
:
classDerived(Base):defmeth(self):super().meth()# calls Base.meth
Στο παράδειγμα, τοsuper()
θα προσδιορίσει αυτόματα το στιγμιότυπο από το οποίο κλήθηκε (η τιμήself
), αναζητήστε τηmethod resolution order (MRO) μεtype(self).__mro__
, και επιστρέψτε το επόμενο στη σειρά μετά τοDerived
στο MRO:Base
.
Πως μπορώ να οργανώσω τον κώδικα μου προκειμένου να διευκολύνω την αλλαγή της βασικής κλάσης;¶
Θα μπορούσατε να αντιστοιχίσετε τη βασική κλάση σε ένα ψευδώνυμο και να προκύψει το ψευδώνυμο. Στην συνέχεια, το μόνο που πρέπει να αλλάξετε είναι η τιμή που έχει εκχωρηθεί ψευδώνυμο. Παρεμπιπτόντως, αυτό το κόλπο είναι επίσης χρήσιμο εάν θέλετε να αποφασίσετε δυναμικά (π.χ. ανάλογα με την διαθεσιμότητα των πόρων) ποια βασική κλάση να χρησιμοποιήσετε Παράδειγμα:
classBase:...BaseAlias=BaseclassDerived(BaseAlias):...
Πως δημιουργώ δεδομένα στατικής κλάσης και μεθόδους στατικής κλάσης;¶
Τόσο τα στατιστικά δεδομένα όσο και οι στατικές μέθοδοι (με την έννοια της C++ ή της Java) υποστηρίζονται στην Python.
Για στατικά δεδομένα, απλώς ορίστε ένα χαρακτηριστικό κλάσης. Για να εκχωρήσετε μια νέα τιμή στο χαρακτηριστικό, πρέπει να χρησιμοποιήσετε ρητά το όνομα κλάσης στην εκχώρηση:
classC:count=0# number of times C.__init__ calleddef__init__(self):C.count=C.count+1defgetcount(self):returnC.count# or return self.count
Τοc.count
αναφέρεται επίσης στοC.count
για οποιοδήποτεc
, έτσι ώστε να ισχύει τοisinstance(c,C)
, εκτός εάν παρακαμφθεί από το ίδιο τοc
ή από κάποια κλάση στη διαδρομή αναζήτησης της βασικής κλάσης από τοc.__class__
πίσω στοC
.
Προσοχή: σε μια μέθοδο του C, μια ανάθεση όπωςself.count=42
δημιουργεί μια νέα και άσχετη παρουσία με το όνομα «count» στο δικό του dict τουself
. Επανασύνδεση μιας κλάσης-στατικής όνομα δεδομένων πρέπει πάντα να προσδιορίζει την κλάση είτε βρίσκεται μέσα σε μια μέθοδο είτε όχι:
C.count=314
Οι στατικές μέθοδοι είναι δυνατές:
classC:@staticmethoddefstatic(arg1,arg2,arg3):# No 'self' parameter!...
Ωστόσο, ένας πολύ πιο απλός τρόπος για να λάβετε το αποτέλεσμα μιας στατικής μεθόδου είναι μέσω μιας απλής συνάρτησης σε επίπεδο μονάδας:
defgetcount():returnC.count
Εάν ο κώδικας σας είναι δομημένος έτσι ώστε να ορίζει μία κλάση (ή στενά συνδεδεμένη ιεραρχίας κλάσεων) ανά module, αυτό παρέχει την επιθυμητή ενθυλάκωση.
Πως μπορώ να υπερφορτώσω κατασκευαστές (ή μεθόδους) στην Python;¶
Αυτή η απάντηση ισχύει στην πραγματικότητα για όλες τις μεθόδους, αλλά η ερώτηση συνήθως εμφανίζεται πρώτη στο πλαίσιο των κατασκευαστών.
Στην C++ θα γράφατε
classC{C(){cout<<"No arguments\n";}C(inti){cout<<"Argument is "<<i<<"\n";}}
Στην Python πρέπει να γράψετε έναν μοναδικό κατασκευαστή που να πιάνει όλες τις περιπτώσεις χρησιμοποιώντας προεπιλεγμένα ορίσματα. Για παράδειγμα:
classC:def__init__(self,i=None):ifiisNone:print("No arguments")else:print("Argument is",i)
Αυτό δεν είναι εντελώς ισοδύναμο, αλλά αρκετά κοντά στην πράξη.
Θα μπορούσατε επίσης να δοκιμάσετε μια λίστα ορισμάτων μεταβλητού μήκους, π.χ.
def__init__(self,*args):...
Η ίδια προσέγγιση λειτουργεί για όλους τους ορισμούς μεθόδων.
Προσπαθώ να χρησιμοποιήσω __spam και λαμβάνω ένα σφάλμα σχετικά με το _SomeClassName__spam.¶
Τα ονόματα μεταβλητών με διπλή υπογράμμιση στην αρχή είναι «mangled» για να παρέχουν έναν απλό αλλά αποτελεσματικό τρόπο ορισμού των ιδιωτικών μεταβλητών κλάσης. Οποιοδήποτε αναγνωριστικό της φόρμας__spam
(τουλάχιστον δύο προπορευόμενες κάτω παύλες, το πολύ ένα τέλος υπογράμμισης) αντικαθίσταται μέσω κειμένου το_classname__spam
, όπου τοclassname
είναι το τρέχον όνομα κλάσης με απογυμνωμένες τυχόν προηγούμενες παύλες.
Το αναγνωριστικό μπορεί να χρησιμοποιηθεί αμετάβλητο εντός της κλάσης, αλλά για πρόσβαση σε αυτό εκτός της κλάσης, πρέπει να χρησιμοποιηθεί το παραμορφωμένο όνομα:
classA:def__one(self):return1deftwo(self):return2*self.__one()classB(A):defthree(self):return3*self._A__one()four=4*A()._A__one()
Συγκεκριμένα, αυτό δεν εγγυάται το απόρρητο καθώς ένας εξωτερικός χρήστης μπορεί ακόμα να έχει σκόπιμα πρόσβαση στο ιδιωτικό χαρακτηριστικό∙ πολλοί προγραμματιστές Python δεν μπαίνουν ποτέ στον κόπο να χρησιμοποιήσουν ονόματα μεταβλητών.
Δείτε επίσης
Οιπροδιαφραφές παραποίησης ιδιωτικού ονόματος για λεπτομέρειες και ειδικές περιπτώσεις.”
Η κλάση μου ορίζει __del__ αλλά δεν καλείται όταν διαγράφω το αντικείμενο.¶
Υπάρχουν διάφοροι πιθανοί λόγοι για αυτό.
Η πρότασηdel
δεν καλεί απαραιτήτως το__del__()
– απλώς μειώνει τον αριθμό αναφοράς του αντικειμένου, και αν αυτό φτάσει στο μηδέν καλείται το__del__()
.
Εάν οι δομές δεδομένων σας περιέχουν κυκλικούς συνδέσμους (π.χ. ένα δέντρο όπου κάθε παιδί έχει μια αναφορά γονέα και κάθε γονέας έχει μια λίστα παιδιών), οι μετρήσεις δεν θα επανέλθουν ποτέ στο μηδέν. Κάθε τόσο η Python εκτελεί έναν αλγόριθμο για να ανιχνεύσει τέτοιους κύκλους, αλλά ο συλλέκτης σκουπιδιών μπορεί να εκτελεστεί κάποια στιγμή μετά την εξαφάνιση της τελευταίας αναφοράς στη δομή δεδομένων σας, επομένως η μέθοδος__del__()
μπορεί να κληθεί σε μια άβολη και τυχαία στιγμή. Αυτό δεν είναι βολικό εάν προσπαθείτε να αναπαράξετε ένα πρόβλημα. Ακόμη χειρότερα, η σειρά με την οποία εκτελούνται μέθοδοι__del__()
του αντικειμένου είναι αυθαίρετη. Μπορείτε να εκτελέσετε τοgc.collect()
για να αναγκάσετε μια συλλογή, αλλάυπάρχουν παθολογικές περιπτώσεις όπου τα αντικείμενα δεν θα συλλεχθούν ποτέ.
Παρά τον συλλέκτη κύκλου, εξακολουθεί να είναι καλή ιδέα να ορίσετε μια ρητή μέθοδοclose()
σε αντικείμενα που θα καλούνται κάθε φορά που τελειώνετε με αυτά. Η μέθοδοςclose()
μπορεί στη συνέχει να αφαιρέσει χαρακτηριστικά που αναφέρονται σε υποαντικείμενα. Μην καλείτε το__del__()
απευθείας –__del__()
θα πρέπει να καλείτε τοclose()
και τοclose()
θα πρέπει να βεβαιωθεί ότι μπορεί να κληθεί περισσότερες από μία φορές για το ίδιο αντικείμενο.
Ένα άλλος τρόπος για να αποφύγετε τις κυκλικές αναφορές είναι να χρησιμοποιήσετε το moduleweakref
, το οποίο σας επιτρέπει να αυξάνετε τον αριθμό των αναφορών τους. Οι δομές δεδομένων δέντρων, για παράδειγμα, θα πρέπει να χρησιμοποιούν αδύναμες αναφορές για τις αναφορές γονέων και αδελφών ( αν τα χρειαστούν!).
Τέλος, εάν η μέθοδος__del__()
εγείρει μια εξαίρεση, εκτυπώνεται ένα προειδοποιητικό μήνυμα στη διεύθυνσηsys.stderr
.
Πως μπορώ να λάβω μια λίστα με όλες τις οντότητες μιας δεδομένης κλάσης;¶
Η Python δεν παρακολουθεί όλες τις παρουσίες μιας κλάσης (ή ενός ενσωματωμένου τύπου). Μπορείτε να προγραμματίσετε τον κατασκευαστή της κλάσης να παρακολουθεί όλες τις οντότητες διατηρώντας μια λίστα αδύναμων αναφορών σε κάθε παρουσία.
Γιατί το αποτέλεσμα τουid()
φαίνεται να μην είναι μοναδικό;¶
Το ενσωματωμένοid()
επιστρέφει έναν ακέραιο που είναι εγγυημένο ότι είναι μοναδικός κατά τη διάρκεια ζωής του αντικειμένου. Εφόσον στο CPython, αυτή είναι διεύθυνση μνήμης του αντικειμένου, συμβαίνει συχνά ότι μετά τη διαγραφή ενός αντικειμένου από τη μνήμη, το επόμενο πρόσφατα δημιουργημένο αντικείμενο εκχωρείται στην ίδια θέση στη μνήμη. Αυτό φαίνεται από αυτό το παράδειγμα:
>>>id(1000)13901272>>>id(2000)13901272
Τα δύο αναγνωριστικά ανήκουν σε διαφορετικά ακέραια αντικείμενα που δημιουργούνται πριν και διαγράφονται αμέσως μετά την εκτέλεση της κλήσηςid()
. Για να βεβαιωθείτε ότι τα αντικείμενα των οποίων το αναγνωριστικό θέλετε να εξετάσετε είναι ακόμα ζωντανά, δημιουργήστε μια άλλη αναφορά στο αντικείμενο:
>>>a=1000;b=2000>>>id(a)13901272>>>id(b)13891296
Πότε μπορώ να βασιστώ σε δοκιμές ταυτότητας με τον τελεστήis;¶
Ο τελεστήςis
ελέγχει την ταυτότητα του αντικειμένου. Η δοκιμήaisb
ισοδυναμεί μεid(a)==id(b)
.
Η πιο σημαντική ιδιότητα ενός τεστ ταυτότητας είναι ότι ένα αντικείμενο είναι πάντα πανομοιότυπο με τον εαυτό του, τοaisa
επιστρέφει πάνταTrue
. Τα τεστ ταυτότητας είναι συνήθως ταχύτερα από τα τεστ ισότητας. Και σε αντίθεση με τα τεστ ισότητας, τα τεστ ταυτότητας είναι εγγυημένα ότι θα επιστρέψουν ένα booleanTrue
ήFalse
.
Ωστόσο, τα τεστ ταυτότητας μπορούν μόνο να αντικαταστήσουν τα τεστ ισότητας όταν είναι εξασφαλισμένη η ταυτότητα αντικειμένου. Γενικά, υπάρχουν τρεις περιπτώσεις όπου η ταυτότητα είναι εγγυημένη:
Οι εκχωρήσεις δημιουργούν νέα ονόματα αλλά δεν αλλάζουν την ταυτότητα αντικειμένου. Μετά την ανάθεση
new=old
, είναι εγγυημένο ότι τοnewisold
.Η τοποθέτηση ενός αντικειμένου σε ένα κοντέινερ που αποθηκεύει αναφορές αντικειμένων δεν αλλάζει την ταυτότητα αντικειμένου. Μετά την ανάθεση λίστας
s[0]=x
, είναι εγγυημένο ότι τοs[0]isx
.Εάν ένα αντικείμενο είναι singleton, σημαίνει ότι μόνο ένα στιγμιότυπο αυτού του αντικειμένου μπορεί να υπάρχει. Μετά τις εκχωρήσεις
a=None
καιb=None
, είναι εγγυημένο ότι τοaisb
επειδή τοNone
είναι singleton.
Στις περισσότερες άλλες περιπτώσεις, τα τεστ ταυτότητας δεν ενδείκνυνται και προτιμώνται τα τεστ ισότητας. Ειδικότερα, τα τεστ ταυτότητας δεν θα πρέπει να χρησιμοποιούνται για τον έλεγχο σταθερών όπωςint
καιstr
που δεν είναι εγγυημένα singletons:
>>>a=1000>>>b=500>>>c=b+500>>>aiscFalse>>>a='Python'>>>b='Py'>>>c=b+'thon'>>>aiscFalse
Ομοίως, τα νέα στιγμιότυπα μεταβλητών κοντέινερ δεν είναι ποτέ πανομοιότυπα:
>>>a=[]>>>b=[]>>>aisbFalse
Στον τυπικό κώδικα βιβλιοθήκης, θα δείτε πολλά κοινά μοτίβα για τη σωστή χρήση των δοκιμών ταυτότητας:
Όπως προτείνεται από τοPEP 8, ένας έλεγχος ταυτότητας είναι ο προτιμώμενος τρόπος για να ελέγξετε το
None
. Αυτό είναι σαν απλά αγγλικά στον κώδικα και αποφεύγεται η σύγχυση με άλλα αντικείμενα που μπορεί να έχουν τιμές boolean που αξιολογούνται ως ψευδείς.Ο εντοπισμός προαιρετικών ορισμάτων μπορεί να είναι δύσκολος όταν το
None
είναι μια έγκυρη τιμή εισόδου. Σε αυτές τις περιπτώσεις, μπορείτε να δημιουργήσετε ένα αντικείμενο μεμονωμένου φρουρού που είναι εγγυημένο ότι θα διαφέρει από άλλα αντικείμενα. Για παράδειγμα, δείτε πως μπορείτε να εφαρμόσετε μια μέθοδο που συμπεριφέρεται σανdict.pop()
:_sentinel=object()defpop(self,key,default=_sentinel):ifkeyinself:value=self[key]delself[key]returnvalueifdefaultis_sentinel:raiseKeyError(key)returndefault
Οι υλοποιήσεις κοντέινερ μερικές φορές χρειάζεται να αυξήσουν τα τεστ ισότητας με δοκιμές ταυτότητας. Αυτό αποτρέπει τη σύγχυση του κώδικα από αντικείμενα όπως το
float('NaN')
που είναι ίσα με τα ίδια.
Για παράδειγμα, εδώ είναι η υλοποίηση τουcollections.abc.Sequence.__contains__()
:
def__contains__(self,value):forvinself:ifvisvalueorv==value:returnTruereturnFalse
Πώς μπορεί μια υποκλάση να ελέγξει ποια δεδομένα αποθηκεύονται σε μια αμετάβλητη παρουσία;¶
Κατά την υποκλάση ενός αμετάβλητου τύπου, παρακάμψετε τη μέθοδο__new__()
αντί για τη μέθοδο__init__()
. Η τελευταία εκτελείται μόνο αφού δημιουργηθεί μια παρουσία, η οποία είναι πολύ αργά για να αλλάζει δεδομένα σε μια αμετάβλητη περίπτωση.
Όλες αυτές οι αμετάβλητες κλάσεις έχουν διαφορετική υπογραφή από τη μητρική τους κλάση:
fromdatetimeimportdateclassFirstOfMonthDate(date):"Always choose the first day of the month"def__new__(cls,year,month,day):returnsuper().__new__(cls,year,month,1)classNamedInt(int):"Allow text names for some numbers"xlat={'zero':0,'one':1,'ten':10}def__new__(cls,value):value=cls.xlat.get(value,value)returnsuper().__new__(cls,value)classTitleStr(str):"Convert str to name suitable for a URL path"def__new__(cls,s):s=s.lower().replace(' ','-')s=''.join([cforcinsifc.isalnum()orc=='-'])returnsuper().__new__(cls,s)
Οι κλάσεις μπορούν να χρησιμοποιηθούν έτσι:
>>>FirstOfMonthDate(2012,2,14)FirstOfMonthDate(2012, 2, 1)>>>NamedInt('ten')10>>>NamedInt(20)20>>>TitleStr('Blog: Why Python Rocks')'blog-why-python-rocks'
Πώς μπορώ να αποθηκεύσω τις κλήσεις μεθόδου στην κρυφή μνήμη;¶
Τα δύο βασικά εργαλεία για τις μεθόδους αποθήκευσης στην προσωρινή μνήμη είναι ταfunctools.cached_property()
καιfunctools.lru_cache()
. Το πρώτο αποθηκεύει τα αποτελέσματα σε επίπεδο παρουσίας και το δεύτερο σε επίπεδο κλάσης.
Η προσέγγισηcached_property λειτουργεί μόνο με μεθόδους που δεν λαμβάνουν ορίσματα. Δεν δημιουργεί αναφορά στο στιγμιότυπο. Το αποτέλεσμα της cached μεθόδους θα διατηρηθεί μόνο όσο το στιγμιότυπο είναι ζωντανό.
Το πλεονέκτημα είναι ότι όταν ένα στιγμιότυπο δεν χρησιμοποιείται πλέον, το αποτέλεσμα της αποθηκευμένης μεθόδου θα απελευθερωθεί αμέσως. Το μειονέκτημα είναι ότι εάν συσσωρευτούν στιγμιότυπα, θα είναι και τα αποτελέσματα της συσσωρευμένης μεθόδου. Μπορούν να αναπτυχθούν χωρίς περιορισμούς.
Η προσέγγισηlru_cache λειτουργεί με μεθόδους που έχουν ορίσματαhashable. Δημιουργεί μια αναφορά στο στιγμιότυπο, εκτός εάν καταβληθούν ειδικές προσπάθειες για να περάσει σε αδύναμες αναφορές.
Το πλεονέκτημα του αλγορίθμου που χρησιμοποιήθηκε λιγότερο πρόσφατα είναι ότι η κρυφή μνήμη οριοθετείται από το καθορισμένοmaxsize. Το μειονέκτημα είναι ότι τα στιγμιότυπα διατηρούνται ζωντανά έως ότου παλαιώσουν από την κρυφή μνήμη ή μέχρι να διαγραφεί η κρυφή μνήμη.
Αυτό το παράδειγμα δείχνει τις διάφορες τεχνικές:
classWeather:"Lookup weather information on a government website"def__init__(self,station_id):self._station_id=station_id# The _station_id is private and immutabledefcurrent_temperature(self):"Latest hourly observation"# Do not cache this because old results# can be out of date.@cached_propertydeflocation(self):"Return the longitude/latitude coordinates of the station"# Result only depends on the station_id@lru_cache(maxsize=20)defhistoric_rainfall(self,date,units='mm'):"Rainfall on a given date"# Depends on the station_id, date, and units.
Το παραπάνω παράδειγμα προϋποθέτει ότι τοstation_id δεν αλλάζει ποτέ. Εάν τα σχετικά χαρακτηριστικά παρουσίας είναι μεταβλητά, η προσέγγισηcached_property δεν μπορεί να λειτουργήσει επειδή δεν μπορεί να εντοπίσει αλλαγές στα χαρακτηριστικά.
Για να λειτουργήσει η προσέγγισηlru_cache όταν τοstation_id είναι μεταβλητό, η κλάση πρέπει να ορίσει τις μεθόδους__eq__()
και__hash__()
ώστε η κρυφή μνήμη να μπορεί να εντοπίσει σχετικές ενημερώσεις χαρακτηριστικών:
classWeather:"Example with a mutable station identifier"def__init__(self,station_id):self.station_id=station_iddefchange_station(self,station_id):self.station_id=station_iddef__eq__(self,other):returnself.station_id==other.station_iddef__hash__(self):returnhash(self.station_id)@lru_cache(maxsize=20)defhistoric_rainfall(self,date,units='cm'):'Rainfall on a given date'# Depends on the station_id, date, and units.
Modules¶
Πως δημιουργώ ένα .pyc αρχείο;¶
Όταν ένα module εισάγεται για πρώτη φορά (ή όταν το αρχείο προέλευσης έχει αλλάξει από τη δημιουργία του τρέχοντος μεταγλωττισμένου αρχείου), ένα αρχείο.pyc
που παρέχει τον μεταγλωττισμένο κώδικα θα πρέπει να δημιουργηθεί σε έναν υποκατάλογο__pycache__
ο κατάλογος που περιέχει το.py
. Το αρχείο.pyc
θα έχει ένα όνομα αρχείου που ξεκινά με το ίδιο όνομα με το αρχείο.py
και τελειώνει σε.pyc
, με ένα μεσαίο στοιχείο που εξαρτάται από το συγκεκριμένο δυαδικό αρχείοpython
που το δημιούργησε. (Βλ.PEP 3147 για λεπτομέρειες.)
Ένας λόγος για τον οποίο ενδέχεται να μην δημιουργηθεί ένα αρχείο.pyc
είναι ένα πρόβλημα δικαιωμάτων στον κατάλογο που περιέχει το αρχείο προέλευσης, που σημαίνει ότι δεν μπορεί να δημιουργηθεί ο υποκατάλογος__pycache__
. Αυτό μπορεί να συμβεί, για παράδειγμα, εάν αναπτυχθεί ως ένας χρήστης αλλά εκτελείται ως άλλος, όπως εάν δοκιμάζετε με έναν διακομιστή ιστού.
Εκτός και αν έχει οριστεί η μεταβλητή περιβάλλοντοςPYTHONDONTWRITEBYTECODE
, η δημιουργία ενός αρχείου .pyc είναι αυτόματη εάν εισάγετε ένα module και η Python έχει τη δυνατότητα (δικαιώματα, ελεύθερος χώρος, κ.λπ…) να δημιουργήσει ένα__pycache__
υποκατάλογο και γράψτε το μεταγλωττισμένο module σε αυτόν τον υποκατάλογο.
Η εκτέλεση της Python σε ένα σενάριο ανώτατου επιπέδου δεν θεωρείται εισαγωγή και δεν θα δημιουργηθεί.pyc
. Για παράδειγμα, εάν έχετε ένα module ανωτάτου επιπέδουfoo.py
που εισάγει ένα άλλο modulexyz.py
, όταν εκτελείτε τοfoo
(πληκτρολογώνταςpythonfoo.py
ως εντολή κελύφους), θα δημιουργηθεί ένα.pyc
για τοxyz
επειδή τοxyz
έχει εισαχθεί, αλλά δεν θα δημιουργηθεί αρχείο.pyc
για τοfoo
καθώς τοfoo.py
δεν εισάγεται.
Εάν χρειάζεται να δημιουργήσετε ένα αρχείο.pyc
για τοfoo
– δηλαδή, να δημιουργήσετε ένα αρχείο.pyc
για ένα module που δεν έχει εισαχθεί – μπορείτε, χρησιμοποιώντας τα modulespy_compile
καιcompileall
.
Το modulepy_compile
μπορεί να μεταγλωττίσει χειροκίνητα οποιαδήποτε module. Ένας τρόπος είναι να χρησιμοποιήσετε τη συνάρτησηcompile()
σε αυτήν την ενότητα διαδραστικά:
>>>importpy_compile>>>py_compile.compile('foo.py')
Αυτό θα γράψει το.pyc
σε έναν υποκατάλογο__pycache__
στην ίδια θέση με τοfoo.py
(ή μπορείτε να το παρακάμψετε με την προαιρετική παράμετροcfile
).
Μπορείτε επίσης να μεταγλωττίσετε αυτόματα όλα τα αρχεία σε έναν κατάλογο ή καταλόγους χρησιμοποιώντας το modulecompileall
. Μπορείτε να κάνετε από το shell prompt εκτελώντας τοcompileall.py
και παρέχοντας τη διαδρομή ενός καταλόγου που περιέχει αρχεία Python για μεταγλώττιση:
python-mcompileall.
Πως μπορώ να βρω το όνομα του τρέχοντος module;¶
Ένα module μπορεί να βρει το δικό του όνομα module κοιτάζοντας την προκαθορισμένη καθολική μεταβλητή__name__
. Εάν αυτή έχει την τιμή__main__
, το πρόγραμμα εκτελείται ως σενάριο. Πολλά modules που χρησιμοποιούνται συνήθως με την εισαγωγή τους παρέχουν επίσης μια διεπαφή γραμμής εντολών ή έναν αυτοέλεγχο και εκτελέστε αυτόν τον κώδικα μόνο αφού ελέγξετε το__name__
:
defmain():print('Running test...')...if__name__=='__main__':main()
Πως μπορώ να έχω modules που εισάγουν αμοιβαία το ένα το άλλο;¶
Υποθέστε ότι έχετε τα ακόλουθα modules:
foo.py
:
frombarimportbar_varfoo_var=1
bar.py
:
fromfooimportfoo_varbar_var=2
Το πρόβλημα είναι ότι ο διερμηνέας θα εκτελέσει τα ακόλουθα βήματα:
main εισάγει
foo
Δημιουργούνται κενά καθολικά για το
foo
Το
foo
μεταγλωττίζεται και ξεκινά η εκτέλεσηfoo
εισάγειbar
Δημιουργούνται κενά καθολικά για
bar
Το
bar
μεταγλωττίζεται και αρχίζει να εκτελείταιΤο
bar
εισάγει τοfoo
(το οποίο είναι απαγορευτικό, καθώς υπάρχει ήδη ένα module με το όνομαfoo
)Ο μηχανισμός εισαγωγής προσπαθεί να διαβάσει το
foo_var
από τα παγκόσμιαfoo
, για να ορίσει τοbar.foo_var=foo.foo_var
Το τελευταίο βήμα αποτυγχάνει, επειδή η Python δεν έχει τελειώσει ακόμα με την ερμηνεία τουfoo
και το global λεξικό συμβόλων για τοfoo
είναι ακόμα κενό.
Το ίδιο συμβαίνει όταν χρησιμοποιείτε τοimportfoo
και, στη συνέχεια, προσπαθείτε να αποκτήσετε πρόσβαση στοfoo.foo_var
σε καθολικό κώδικα.
Υπάρχουν (τουλάχιστον) τρεις πιθανοί τρόποι αντιμετώπισης αυτού του προβλήματος.
Ο Guido van Rossum συνιστά την αποφυγή όλων των χρήσεων τουfrom<module>import...
και την τοποθέτηση όλου του κώδικα μέσα σε συναρτήσεις. Τα initializations καθολικών μεταβλητών και μεταβλητών κλάσης θα πρέπει να χρησιμοποιηθούν μόνο σταθερές ή ενσωματωμένες συναρτήσεις. Αυτό σημαίνει ότι ένα εισαγόμενο module αναφέρεται ως<module>.<name>
.
Ο Jim Roskind προτείνει να εκτελέσετε τα βήματα με την ακόλουθη σειρά σε κάθε module:
εξαγωγές (globals, συναρτήσεις, και κλάσεις που δεν χρειάζονται εισαγόμενες βασικές κλάσεις)
δηλώσεις
import
ενεργός κώδικας (συμπεριλαμβανομένων των καθολικών που αρχικοποιούνται από εισαγόμενες τιμές).
Ο Van Rossum δεν αρέσει πολύ αυτή η προσέγγιση επειδή οι εισαγωγές εμφανίζονται σε ένα περίεργο μέρος, αλλά λειτουργεί.
Ο Matthias Urlichs συνιστά την αναδιάρθρωση του κώδικά σας έτσι ώστε η αναδρομική εισαγωγή να μην είναι απαραίτητη εξαρχής.
Αυτές οι λύσεις δεν αλληλοαποκλείονται.
__import__(“x.y.z”) επιστρέφει <module “x”>∙ πως μπορώ να πάρω το z?¶
Σκεφτείτε να χρησιμοποιήσετε τη συνάρτηση ευκολίαςimport_module()
από τοimportlib
αντί:
z=importlib.import_module('x.y.z')
Όταν επεξεργάζομαι ένα module που έχει εισαχθεί και την επανεισάγω, οι αλλαγές δεν εμφανίζονται. Γιατί συμβαίνει αυτό;¶
Για λόγους αποτελεσματικότητας καθώς και συνέπειας, η Python διαβάζει το αρχείο της ενότητας μόνο την πρώτη φορά που εισάγεται μια λειτουργική μονάδα. Εάν δεν το έκανε, σε ένα πρόγραμμα που αποτελείται από πολλές ενότητες όπου η καθεμία εισάγει το ίδιο βασικό module, το βασικό module θα αναλυθεί και θα αναλυθεί ξανά πολλές φορές. Για να αναγκάσετε τη εκ νέου ανάγνωση μιας αλλαγμένης ενότητας , κάντε το εξής:
importimportlibimportmodnameimportlib.reload(modname)
Προειδοποίηση: αυτή η τεχνική δεν είναι 100% ασφαλής. Ειδικότερα, modules που περιέχουν δηλώσεις όπως
frommodnameimportsome_objects
θα συνεχίσει να λειτουργεί με την παλιά έκδοση των εισαγόμενων αντικειμένων. Εάν η λειτουργική μονάδα περιέχει ορισμούς κλάσεων, οι υπάρχουσες παρουσίες κλάσεωνδεν θα ενημερωθούν για να χρησιμοποιούν τον ορισμό της νέας κλάσης. Αυτό μπορεί να οδηγήσει στην ακόλουθη παράδοξη συμπεριφορά:
>>>importimportlib>>>importcls>>>c=cls.C()# Create an instance of C>>>importlib.reload(cls)<module 'cls' from 'cls.py'>>>>isinstance(c,cls.C)# isinstance is false?!?False
Η φύση του προβλήματος καθίσταται σαφής εάν εκτυπώσετε την «ταυτότητα» των αντικειμένων κλάσης:
>>>hex(id(c.__class__))'0x7352a0'>>>hex(id(cls.C))'0x4198d0'