FAQ Προγραμματισμού¶
Περιεχόμενα
-
-
Γιατί λαμβάνω ένα UnboundLocalError όταν η μεταβλητή έχει μια τιμή;
Ποιοι είναι οι κανόνες για τις τοπικές και τις καθολικές μεταβλητές στην Python;
Ποιες είναι οι «βέλτιστες πρακτικές» για τη χρήση import σε ένα module;
Γιατί μοιράζονται οι προεπιλεγμένες τιμές μεταξύ των αντικειμένων;
Γιατί η αλλαγή της λίστας “y” αλλάζει επίσης και τη λίστα “x”;
Πως μπορώ να γράψω μια συνάρτηση με παραμέτρους εξόδου (κλήση με αναφορά);
Πως δημιουργείτε μια συνάρτηση υψηλότερης τάξης στην Python;
Πως μπορώ να βρω τις μεθόδους ή τα χαρακτηριστικά ενός αντικειμένου;
Πως μπορεί ο κώδικας μου να ανακαλύψει το όνομα ενός αντικειμένου;
Τι σημαίνει η κάθετος(/) στη λίστα παραμέτρων μιας συνάρτησης;
-
Πώς προσδιορίζω δεκαεξαδικούς ή οκταδικούς ακέραιους αριθμούς;
Πώς μπορώ να πάρω το literal χαρακτηριστικό int αντί για το SyntaxError;
Πως μπορώ να χρησιμοποιήσω συμβολοσειρές για να καλέσω συναρτήσεις/μεθόδους;
Is there an equivalent to Perl’s chomp() for removing trailing newlines from strings?
What does “UnicodeDecodeError” or “UnicodeEncodeError” error mean?
-
How do I call a method defined in a base class from a derived class that overrides it?
Πως μπορώ να οργανώσω τον κώδικα μου προκειμένου να διευκολύνω την αλλαγή της βασικής κλάσης;
Πως δημιουργώ δεδομένα στατικής κλάσης και μεθόδους στατικής κλάσης;
Πως μπορώ να υπερφορτώσω κατασκευαστές (ή μεθόδους) στην Python;
Προσπαθώ να χρησιμοποιήσω __spam και λαμβάνω ένα σφάλμα σχετικά με το _SomeClassName__spam.
Η κλάση μου ορίζει __del__ αλλά δεν καλείται όταν διαγράφω το αντικείμενο.
Πως μπορώ να λάβω μια λίστα με όλες τις οντότητες μιας δεδομένης κλάσης;
Γιατί το αποτέλεσμα του
id()
φαίνεται να μην είναι μοναδικό;Πότε μπορώ να βασιστώ σε δοκιμές ταυτότητας με τον τελεστή is;
Πώς μπορεί μια υποκλάση να ελέγξει ποια δεδομένα αποθηκεύονται σε μια αμετάβλητη παρουσία;
Γενικές Ερωτήσεις¶
Υπάρχει πρόγραμμα εντοπισμού σφαλμάτων σε επίπεδο πηγαίου κώδικα με σημεία διακοπής , με ένα βήμα κλπ.;¶
Ναι.
Πολλοί εντοπιστές σφαλμάτων για την Python περιγράφονται παρακάτω και η ενσωματωμένη συνάρτηση breakpoint()
σάς επιτρέπει να μεταβείτε σε οποιοδήποτε από αυτά.
Το pdb module είναι ένα απλό αλλά επαρκές πρόγραμμα εντοπισμού σφαλμάτων σε λειτουργία κονσόλας για την Python. Είναι μέρος της τυπικής βιβλιοθήκης Python και είναι documented in the Library Reference Manual
. Μπορείτε επίσης να γράψετε το δικό σας πρόγραμμα σφαλμάτων χρησιμοποιώντας τον κώδικα για το pdb ως παράδειγμα.
The IDLE interactive development environment, which is part of the standard Python distribution (normally available as Tools/scripts/idle), includes a graphical debugger.
Το PythonWin είναι ένα Python IDE που περιλαμβάνει ένα πρόγραμμα εντοπισμού σφαλμάτων GUI που βασίζεται σε pdb. Το πρόγραμμα εντοπισμού σφαλμάτων PythonWin χρωματίζει τα σημεία διακοπής και έχει αρκετά ωραία χαρακτηριστικά, όπως τον εντοπισμό σφαλμάτων σε προγράμματα που δεν είναι PythonWin. Το PythonWin είναι διαθέσιμο ως μέρος του pywin32 έργου και ως μέρος της διανομής ActivePython .
Eric is an IDE built on PyQt and the Scintilla editing component.
Το trepan3k είναι ένα πρόγραμμα εντοπισμού σφαλμάτων παρόμοιο με το gdb.
Το Visual Studio Code είναι ένας IDE με εργαλεία εντοπισμού σφαλμάτων που ενσωματώνεται με λογισμικό ελέγχου έκδοσης.
Υπάρχει ένας αριθμός εμπορικών Python IDEs που περιλαμβάνουν γραφικούς εντοπισμούς σφαλμάτων. Αυτά περιλαμβάνουν:
Υπάρχουν εργαλεία που βοηθούν στην εύρεση σφαλμάτων ή στην εκτέλεση στατικής ανάλυσης;¶
Ναι.
Pylint and Pyflakes do basic checking that will help you catch bugs sooner.
Static type checkers such as Mypy, Pyre, and Pytype can check type hints in Python source code.
Πως μπορώ να δημιουργήσω ένα stand-alone binary από ένα Python script;¶
Δεν χρειάζεστε την δυνατότητα μεταγλώττισης κώδικα Python σε C, εάν το μόνο που θέλετε είναι ένα stand-alone πρόγραμμα που οι χρήστες μπορούν να κατεβάσουν και να εκτελέσουν χωρίς να χρειάζεται να εγκαταστήσουν πρώτα την διανομή της Python. Υπάρχει μια σειρά από εργαλεία που καθορίζουν το σύνολο των modules που απαιτούνται από ένα πρόγραμμα και συνδέουν αυτά τα modules μαζί με ένα δυαδικό αρχείο Python για να παραχθεί ένα μόνο εκτελέσιμο.
One is to use the freeze tool, which is included in the Python source tree as
Tools/freeze
. It converts Python byte code to C arrays; with a C compiler you can
embed all your modules into a new program, which is then linked with the
standard 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 only)
Υπάρχουν πρότυπα κωδικοποίησης ή οδηγός στυλ για προγράμματα Python;¶
Ναι. Το στυλ κωδικοποίησης που απαιτείται για τα τυπικά modules βιβλιοθήκης τεκμηριώνεται ως PEP 8.
Βασική Γλώσσα¶
Γιατί λαμβάνω ένα UnboundLocalError όταν η μεταβλητή έχει μια τιμή;¶
It can be a surprise to get the UnboundLocalError in previously working code when it is modified by adding an assignment statement somewhere in the body of a function.
Αυτό ο κώδικας:
>>> x = 10
>>> def bar():
... print(x)
>>> bar()
10
δουλεύει, αλλά αυτός ο κώδικας:
>>> x = 10
>>> def foo():
... print(x)
... x += 1
results in an UnboundLocalError:
>>> foo()
Traceback (most recent call last):
...
UnboundLocalError: local variable 'x' referenced before assignment
Αυτό συμβαίνει επειδή όταν κάνετε μια ανάθεση σε μια μεταβλητή σε ένα εύρος, αυτή η μεταβλητή γίνεται τοπική σε αυτό το εύρος και σκιάζει οποιαδήποτε μεταβλητή με παρόμοιο όνομα στο εξωτερικό εύρος. Εφόσον η τελευταία πρόταση στο foo εκχωρεί μια νέα τιμή στο x
, ο μεταγλωττιστής την αναγνωρίζει ως τοπική μεταβλητή. Κατά συνέπεια όταν η προηγούμενη print(x)
επιχειρεί να εκτυπώσει την uninitialized τοπική μεταβλητή και προκύπτει σφάλμα.
Στο παραπάνω παράδειγμα, μπορείτε να αποκτήσετε πρόσβαση στη μεταβλητή εξωτερικού εύρους δηλώνοντας την ως καθολική:
>>> x = 10
>>> def foobar():
... global x
... print(x)
... x += 1
>>> foobar()
10
Αυτή η ρητή δήλωση απαιτείται για να σας υπενθυμίσει ότι (σε αντίθεση με την επιφανειακά ανάλογη κατάσταση με τις μεταβλητές κλάσης και στιγμιότυπου) στην πραγματικότητα τροποποιείτε την τιμή της μεταβλητής στο εξωτερικό πεδίο:
>>> print(x)
11
Μπορείτε να κάνετε κάτι παρόμοιο σε ένα ένθετο πεδίο χρησιμοποιώντας τη λέξη κλειδί nonlocal
:
>>> def foo():
... x = 10
... def bar():
... nonlocal x
... print(x)
... x += 1
... bar()
... print(x)
>>> foo()
10
11
Ποιοι είναι οι κανόνες για τις τοπικές και τις καθολικές μεταβλητές στην Python;¶
Στην Python, οι μεταβλητές που αναφέρονται μόνο μέσα σε μια συνάρτηση είναι έμμεσα καθολικές. Εάν σε μια μεταβλητή εκχωρηθεί μια τιμή οπουδήποτε μέσα στο σώμα της συνάρτησης, θεωρείται ότι είναι τοπική, εκτός αν δηλωθεί ρητά ως καθολική.
Αν και είναι λίγο έκπληξη στην αρχή, αυτό το εξηγεί μια στιγμή. Από τη μία πλευρά η απαίτηση global
για εκχωρημένες μεταβλητές παρέχει μια γραμμή έναντι ανεπιθύμητων παρενεργειών. Από την άλλη, εάν απαιτείται global
για όλες τις καθολικές αναφορές, θα χρησιμοποιούσατε global
όλη την ώρα. Θα έπρεπε να δηλώσετε ως καθολική κάθε αναφορά σε μια ενσωματωμένη λειτουργία ή σε ένα στοιχείο ενός εισαγόμενου module. Αυτή η ακαταστασία θα νικήσει την χρησιμότητα της δήλωσης global
για τον εντοπισμό παρενεργειών.
Γιατί τα lambdas που ορίζονται σε έναν βρόχο με διαφορετικές τιμές επιστρέφουν όλα το ίδιο αποτέλεσμα;¶
Ας υποθέσουμε ότι χρησιμοποιείτε έναν βρόχο for για να ορίσετε μερικά διαφορετικά lambdas (ή ακόμα και απλές συναρτήσεις), π.χ.:
>>> squares = []
>>> for x in range(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 = []
>>> for x in range(5):
... squares.append(lambda n=x: n**2)
Εδώ, το n=x
δημιουργεί μια νέα μεταβλητή n
τοπική στο lambda και υπολογίζεται όταν το lambda ορίζεται έτσι ώστε να έχει την ίδια τιμή που είχε το x
σε εκείνο το σημείο βρόχου. Αυτό σημαίνει ότι η τιμή του n
θα είναι 0
στο πρώτο lambda, 1
στο δεύτερο , 2
στο τρίτο και ούτω καθεξής. Επομένως κάθε lambda θα επιστρέψει τώρα το σωστό αποτέλεσμα:
>>> squares[2]()
4
>>> squares[4]()
16
Σημειώστε ότι αυτή η συμπεριφορά δεν είναι ιδιόμορφη για το lambdas, αλλά ισχύει και για κανονικές λειτουργίες.
Ποιες είναι οι «βέλτιστες πρακτικές» για τη χρήση import σε ένα module;¶
Γενικά, μην χρησιμοποιείτε from modulename import *
. Κάτι τέτοιο δημιουργεί μια ακαταστασία στο importer’s namespace, και καθιστά πιο δύσκολο για τα linters να εντοπίσουν απροσδιόριστα ονόματα.
Εισαγωγή modules στην κορυφή ενός αρχείου. Με αυτόν τον τρόπο καθιστά σαφές ποια άλλα modules απαιτεί ο κώδικας σας και αποφεύγονται ερωτήσεις σχετικά με το αν το όνομα της μονάδας είναι εντός πεδίου. Χρησιμοποιώντας ένα import ανά γραμμή καθιστά εύκολη την προσθήκη και τη διαγραφή module imports, αλλά χρησιμοποιώντας πολλαπλά imports ανά γραμμής καταναλώνεται λιγότερος χώρος στην οθόνη.
Είναι καλή πρακτική εάν εισάγετε module με την ακόλουθη σειρά:
standard library modules – e.g.
sys
,os
,getopt
,re
third-party library modules (anything installed in Python’s site-packages directory) – e.g. mx.DateTime, ZODB, PIL.Image, etc.
locally-developed 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
.
Πώς μπορώ να μεταβιβάσω προαιρετικές παραμέτρους ή παραμέτρους λέξεων-κλειδιών από τη μια συνάρτηση στην άλλη;¶
Συλλέξτε τα ορίσματα χρησιμοποιώντας τους specifiers *
και **
στη λίστα παραμέτρων της συνάρτησης∙ Αυτό σας δίνει τα ορίσματα θέσης ως πλειάδα και τα ορίσματα λέξεων-κλειδιών ως λεξικό. Στη συνέχεια, μπορείτε να μεταβιβάσετε αυτά τα ορίσματα κατά την κλήση άλλης συνάρτησης με τη χρήση των *
και **
:
def f(x, *args, **kwargs):
...
kwargs['width'] = '14.3c'
...
g(x, *args, **kwargs)
Ποια είναι η διαφορά μεταξύ ορισμάτων και παραμέτρων;¶
Parameters are defined by the names that appear in a function definition, whereas arguments are the values actually passed to a function when calling it. Parameters define what types of arguments a function can accept. For example, given the function definition:
def func(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, που σημαίνει ότι μπορείτε να αλλάξετε το περιεχόμενό τους.
After the call to append()
, the content of the mutable object has
changed from []
to [10]
. Since both the variables refer to the same
object, using either name accesses the modified value [10]
.
Αν αντιστοιχίσουμε ένα αμετάβλητο αντικείμενο σε x
:
>>> x = 5 # ints are immutable
>>> y = x
>>> x = x + 1 # 5 can't be mutated, we are creating a new object here
>>> x
6
>>> y
5
μπορούμε να δούμε ότι σε αυτήν την περίπτωση x
και y
δεν είναι πλέον ίσα. Αυτό συμβαίνει επειδή οι ακέραιοι αριθμοί είναι immutable, και όταν κάνουμε x = x + 1
δεν μεταλλάσσεται το int 5
αυξάνοντας την τιμή του∙ αντίθετα, δημιουργούμε ένα νέο αντικείμενο (το int 6
) και το εκχωρούμε στο x
(δηλαδή, αλλάζοντας το αντικείμενο στο οποίο αναφέρεται το x
). Μετά από αυτήν την ανάθεση έχουμε δύο αντικείμενα (τα ints 6
and 5
) και δύο μεταβλητές που αναφέρονται σε αυτά (x
τώρα αναφέρεται σε 6
αλλά y
ακόμα αναφέρεται στο 5
).
Some operations (for example y.append(10)
and y.sort()
) mutate the
object, whereas superficially similar operations (for example y = y + [10]
and sorted(y)
) create a new object. In general in Python (and in all cases
in the standard library) a method that mutates an object will return None
to help avoid getting the two types of operations confused. So if you
mistakenly write y.sort()
thinking it will give you a sorted copy of y
,
you’ll instead end up with None
, which will likely cause your program to
generate an easily diagnosed error.
Ωστόσο, υπάρχει μία κατηγορία λειτουργιών όπου η ίδια πράξη έχει μερικές φορές διαφορετικές συμπεριφορές με διαφορετικούς τύπους: οι επαυξημένοι τελεστές ανάθεσης. Για παράδειγμα, +=
μεταλλάσσει τις λίστες αλλά όχι τις πλειάδες ή τους 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. Εφόσον η εκχώρηση δημιουργεί απλώς αναφορές σε αντικείμενα, δεν υπάρχει ψευδώνυμο μεταξύ ενός ονόματος ορίσματος σε αυτό που καλεί και σε αυτό που καλείται, και επομένως δεν υπάρχει κλήση προς αναφορά από μόνη της. Μπορείτε να επιτύχετε το επιθυμητό αποτέλεσμα με διάφορους τρόπους.
Επιστρέφοντας μια πλειάδα των αποτελεσμάτων:
>>> def func1(a, b): ... a = 'new-value' # a and b are local names ... b = b + 1 # assigned to new objects ... return a, b # return new values ... >>> x, y = 'old-value', 99 >>> func1(x, y) ('new-value', 100)
Αυτή είναι σχεδόν πάντα η πιο ξεκάθαρη λύση.
Χρησιμοποιώντας καθολικές μεταβλητές. Αυτό δεν είναι ασφαλές για νήμα και δεν συνίσταται.
Περνώντας ένα μεταβλητό (με δυνατότητα αλλαγής επί τόπου) αντικείμενο:
>>> def func2(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]
Περνώντας σε ένα λεξικό που μεταλλάσσεται:
>>> def func3(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}
Ή ομαδοποιήστε τιμές σε μια παρουσία κλάσης:
>>> class Namespace: ... def __init__(self, /, **args): ... for key, value in args.items(): ... setattr(self, key, value) ... >>> def func4(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
. Χρησιμοποιώντας ένθετα πεδία:
def linear(a, b):
def result(x):
return a * x + b
return result
Ή χρησιμοποιώντας ένα callable αντικείμενο:
class linear:
def __init__(self, a, b):
self.a, self.b = a, b
def __call__(self, x):
return self.a * x + self.b
Και στις δύο περιπτώσεις:
taxes = linear(0.3, 2)
δίνει ένα callable αντικείμενο όπου taxes(10e6) == 0.3 * 10e6 + 2
.
Η προσέγγιση του callable αντικειμένου έχει το μειονέκτημα ότι είναι λίγο πιο αργή και οδηγεί σε ελαφρώς μεγαλύτερο κώδικα. Ωστόσο, σημειώστε ότι μια συλλογή από callables μπορεί να μοιραστεί την υπογραφή τους μέσω κληρονομικότητας:
class exponential(linear):
# __init__ inherited
def __call__(self, x):
return self.a * (x ** self.b)
Το αντικείμενο μπορεί να ενθυλακώσει την κατάσταση για πολλές μεθόδους:
class counter:
value = 0
def set(self, x):
self.value = x
def up(self):
self.value = self.value + 1
def down(self):
self.value = self.value - 1
count = 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[:]
Πως μπορώ να βρω τις μεθόδους ή τα χαρακτηριστικά ενός αντικειμένου;¶
For an instance x of a user-defined class, dir(x)
returns an alphabetized
list of the names containing the instance attributes and methods and attributes
defined by its class.
Πως μπορεί ο κώδικας μου να ανακαλύψει το όνομα ενός αντικειμένου;¶
Γενικά μιλώντας, δεν μπορεί, επειδή τα αντικείμενα δεν έχουνε πραγματικά ονόματα. Ουσιαστικά, η εκχώρηση δεσμεύει πάντα ένα όνομα σε μια τιμή∙ το ίδιο ισχύει για τις δηλώσεις def
και class
, αλλά σε αυτή την περίπτωση η τιμή είναι callable. Λάβετε υπόψη τον ακόλουθο κώδικα:
>>> class A:
... pass
...
>>> B = A
>>> a = B()
>>> b = a
>>> print(b)
<__main__.A object at 0x16D07CC>
>>> print(a)
<__main__.A object at 0x16D07CC>
Arguably the class has a name: even though it is bound to two names and invoked through the name B the created instance is still reported as an instance of class A. However, it is impossible to say whether the instance’s name is a or b, since both names are bound to the same value.
Γενικά μιλώντας, δεν θα πρέπει να είναι απαραίτητο ο κώδικας σας να «γνωρίζει τα ονόματα» συγκεκριμένων τιμών. Αν δεν γράφετε εσκεμμένα ενδοσκοπικά (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, 25
small = x if x < y else y
Πριν εισαχθεί αυτή η σύνταξη στην Python 2.5, ένα κοινό ιδίωμα ήταν η χρήση λογικών τελεστών:
[expression] and [on_true] or [on_false]
Ωστόσο, αυτό το ιδίωμα δεν είναι ασφαλές, καθώς μπορεί να δώσει λανθασμένα αποτελέσματα όταν το on_true έχει ψευδή δυαδική τιμή. Επομένως, είναι πάντα καλύτερο να χρησιμοποιήσετε τη φόρμα ... if ... else ...
.
Είναι δυνατόν να γράψουμε ασαφή one-liners στην Python;¶
Yes. Usually this is done by nesting lambda
within
lambda
. See the following three examples, due to Ulf Bartelt:
from functools import reduce
# Primes < 1000
print(list(filter(None,map(lambda y:y*reduce(lambda x,y:x*y!=0,
map(lambda x,y=y:y%x,range(2,int(pow(y,0.5)+1))),1),range(2,1000)))))
# First 10 Fibonacci numbers
print(list(map(lambda x,f=lambda x,f:(f(x-1,f)+f(x-2,f)) if x>1 else 1:
f(x,f), range(10))))
# Mandelbrot set
print((lambda Ru,Ro,Iu,Io,IM,Sx,Sy:reduce(lambda x,y:x+y,map(lambda y,
Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,Sy=Sy,L=lambda yc,Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,i=IM,
Sx=Sx,Sy=Sy:reduce(lambda x,y:x+y,map(lambda x,xc=Ru,yc=yc,Ru=Ru,Ro=Ro,
i=i,Sx=Sx,F=lambda xc,yc,x,y,k,f=lambda xc,yc,x,y,k,f:(k<=0)or (x*x+y*y
>=4.0) or 1+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
Μην το δοκιμάσετε στο σπίτι, παιδιά!
Τι σημαίνει η κάθετος(/) στη λίστα παραμέτρων μιας συνάρτησης;¶
A slash in the argument list of a function denotes that the parameters prior to
it are positional-only. Positional-only parameters are the ones without an
externally-usable name. Upon calling a function that accepts positional-only
parameters, arguments are mapped to parameters based solely on their position.
For example, divmod()
is a function that accepts positional-only
parameters. Its documentation looks like this:
>>> 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>", line 1, in <module>
TypeError: divmod() takes no keyword arguments
Αριθμοί και συμβολοσειρές¶
Πώς προσδιορίζω δεκαεξαδικούς ή οκταδικούς ακέραιους αριθμούς;¶
Για να καθορίσετε έναν οκταδικό ψηφίο, προηγείται την οκταδικής τιμής με ένα μηδέν, ¨και μετά ένα πεζό ή κεφαλαίο «o». Για παράδειγμα, για να ορίσετε τη μεταβλητή «a» στην οκταδική τιμή «10» (8 σε δεκαδικό), πληκτρολογήστε:
>>> a = 0o10
>>> a
8
Το δεκαεξαδικό είναι εξίσου εύκολο. Απλώς προηγείται του δεκαεξαδικού αριθμού με ένα μηδέν, και μετά ένα πεζό ή κεφαλαίο «x». Τα δεκαεξαδικά ψηφία μπορούν να καθοριστούν με πεζά ή κεφαλαία. Για παράδειγμα, στον διερμηνέα Python:
>>> a = 0xa5
>>> a
165
>>> b = 0XB2
>>> b
178
Γιατί το -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;¶
Trying to lookup an int
literal attribute in the normal manner gives
a syntax error because the period is seen as a decimal point:
>>> 1.__class__
File "<stdin>", line 1
1.__class__
^
SyntaxError: invalid decimal literal
Η λύση είναι να διαχωριστεί το literal από την τελεία είτε με κενό είτε με παρένθεση.
>>> 1 .__class__
<class 'int'>
>>> (1).__class__
<class 'int'>
Πως μετατρέπω μια συμβολοσειρά σε έναν αριθμό;¶
For integers, use the built-in int()
type constructor, e.g. int('144')
== 144
. Similarly, float()
converts to floating-point,
e.g. 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”).
Πως μετατρέπω έναν αριθμό σε συμβολοσειρά;¶
To convert, e.g., the number 144 to the string “144”, use the built-in type
constructor str()
. If you want a hexadecimal or octal representation, use
the built-in functions hex()
or oct()
. For fancy formatting, see
the Formatted string literals and Format String Syntax sections,
e.g. "{:04d}".format(144)
yields
'0144'
and "{:.3f}".format(1.0/3.0)
yields '0.333'
.
Πώς μπορώ να τροποποιήσω μια συμβολοσειρά στη θέση της;¶
Δεν μπορείτε, γιατί οι συμβολοσειρές είναι αμετάβλητες. Στις περισσότερες περιπτώσεις θα πρέπει απλώς να δημιουργήσετε μια νέα συμβολοσειρά από τα διάφορα μέρη από τα οποία θέλετε να τη συναρμολογήσετε. Ωστόσο, εάν χρειάζεστε ένα αντικείμενο με δυνατότητα τροποποίησης δεδομένων unicode, δοκιμάστε να χρησιμοποιήσετε ένα αντικείμενο io.StringIO
ή το module array
:
>>> import io
>>> s = "Hello, world"
>>> sio = io.StringIO(s)
>>> sio.getvalue()
'Hello, world'
>>> sio.seek(7)
7
>>> sio.write("there!")
6
>>> sio.getvalue()
'Hello, there!'
>>> import array
>>> a = array.array('u', s)
>>> print(a)
array('u', 'Hello, world')
>>> a[0] = 'y'
>>> print(a)
array('u', 'yello, world')
>>> a.tounicode()
'yello, world'
Πως μπορώ να χρησιμοποιήσω συμβολοσειρές για να καλέσω συναρτήσεις/μεθόδους;¶
Υπάρχουν διάφορες τεχνικές.
Το καλύτερο είναι να χρησιμοποιήσετε ένα λεξικό που αντιστοιχίζει συμβολοσειρές σε συναρτήσεις. Το κύριο πλεονέκτημα αυτής της τεχνικής είναι ότι οι συμβολοσειρές δεν χρειάζεται να ταιριάζουν με τα ονόματα των συναρτήσεων. Αυτή είναι επίσης η κύρια τεχνική που χρησιμοποιείται για την εξομοίωση μιας κατασκευής πεζών-κεφαλαίων:
def a(): pass def b(): pass dispatch = {'go': a, 'stop': b} # Note lack of parens for funcs dispatch[get_input()]() # Note trailing parens to call function
Χρησιμοποιείστε την ενσωματωμένη συνάρτηση
getattr()
:import foo getattr(foo, 'bar')()
Σημειώστε ότι το
getattr()
λειτουργεί σε οποιοδήποτε αντικείμενο, συμπεριλαμβανομένων κλάσεων, οντοτήτων κλάσεων, modules, και ούτω καθεξής.Αυτό χρησιμοποιείται σε πολλά σημεία της τυπικής βιβλιοθήκης, όπως αυτό:
class Foo: def do_foo(self): ... def do_bar(self): ... f = getattr(foo_instance, 'do_' + opname) f()
Χρησιμοποιήστε το
locals()
για να επιλύσετε το όνομα της συνάρτησης:def myFunc(): print("hello") fname = "myFunc" f = locals()[fname] f()
Is there an equivalent to Perl’s chomp() for removing trailing newlines from strings?¶
Μπορείτε να χρησιμοποιήσετε το 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()
λειτουργεί καλά.
Is there a scanf() or sscanf() equivalent?¶
Όχι ως τέτοιο.
For simple input parsing, the easiest approach is usually to split the line into
whitespace-delimited words using the split()
method of string objects
and then convert decimal strings to numeric values using int()
or
float()
. split()
supports an optional «sep» parameter which is useful
if the line uses something other than whitespace as a separator.
For more complicated input parsing, regular expressions are more powerful
than C’s sscanf()
and better suited for the task.
What does “UnicodeDecodeError” or “UnicodeEncodeError” error mean?¶
Βλ το Unicode HOWTO.
Απόδοση¶
Το πρόγραμμα μου είναι πολύ αργό. Πως μπορώ να το επιταχύνω;¶
Αυτό είναι δύσκολο, γενικά. Πρώτον, εδώ είναι μια λίστα με πράγματα που πρέπει να θυμάστε πριν βουτήξετε περαιτέρω:
Τα χαρακτηριστικά απόδοσης διαφέρουν μεταξύ των υλοποιήσεων 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 HOW TO για παραδείγματα μέτριος προηγμένης χρήσης).Οι αφαιρέσεις τείνουν να δημιουργούν έμμεσες κατευθύνσεις και αναγκάζουν τον διερμηνέα να εργαστεί περισσότερο. Εάν τα επίπεδα της έμμεσης κατεύθυνσης υπερτερούν του όγκου της χρήσιμης εργασίας που γίνεται, το πρόγραμμα σας θα είναι πιο αργό. Θα πρέπει να αποφύγετε την υπερβολική αφαίρεση, ειδικά με τη μορφή μικροσκοπικών συναρτήσεων ή μεθόδων (που είναι επίσης συχνά επιζήμια για την αναγνωσιμότητα).
If you have reached the limit of what pure Python can allow, there are tools to take you further away. For example, Cython can compile a slightly modified version of Python code into a C extension, and can be used on many different platforms. Cython can take advantage of compilation (and optional type annotations) to make your code significantly faster than when interpreted. If you are confident in your C programming skills, you can also write a C extension module yourself.
Δείτε επίσης
Η σελίδα wiki που είναι αφιερωμένη σε συμβουλές απόδοσης.
Ποιος είναι ο πιο αποτελεσματικός τρόπος για να συνδέσετε πολλές συμβολοσειρές μεταξύ τους;¶
Τα αντικείμενα str
και bytes
είναι αμετάβλητα, επομένως η σύνδεση πολλών συμβολοσειρών μεταξύ τους είναι αναποτελεσματική καθώς κάθε συνένωση δημιουργεί ένα νέο αντικείμενο. Στη γενική περίπτωση, το συνολικό κόστος χρόνου εκτέλεσης είναι τετραγωνικό στο συνολικό μήκος συμβολοσειράς.
Για να συγκεντρώσετε πολλά αντικείμενα str
, το προτεινόμενο ιδίωμα είναι να τα τοποθετήσετε σε μια λίστα και να καλέσετε το στο str.join()
τέλος:
chunks = []
for s in my_strings:
chunks.append(s)
result = ''.join(chunks)
(ένα άλλο λογικά αποτελεσματικό ιδίωμα είναι να χρησιμοποιήσετε το io.StringIO
)
Για τη συγκέντρωση πολλών αντικειμένων bytes
, το συνιστώμενο ιδίωμα είναι η επέκταση ενός αντικειμένου bytearray
χρησιμοποιώντας επιτόπια συνένωση (ο τελεστής +=
):
result = bytearray()
for b in my_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()
:
for x in reversed(sequence):
... # do something with x ...
Αυτό δεν θα επηρεάσει την αρχική σας ακολουθία, αλλά δημιουργήστε ένα νέο αντίγραφο με αντίστροφη σειρά για επανάληψη.
Πως αφαιρείτε διπλότυπα από μια λίστα;¶
Δείτε το Python Cookbook για μια μακρά συζήτηση σχετικά με πολλούς τρόπους για να το κάνετε αυτό:
Εάν δεν σας πειράζει να αναδιατάξετε τη λίστα, ταξινομήστε την και μετά σαρώστε από το τέλος της λίστας, διαγράφοντας τα διπλότυπα καθώς προχωράτε:
if mylist:
mylist.sort()
last = mylist[-1]
for i in range(len(mylist)-2, -1, -1):
if last == mylist[i]:
del mylist[i]
else:
last = mylist[i]
Εάν όλα τα στοιχεία της λίστας μπορούν να χρησιμοποιηθούν ως κλειδιά συνόλου (δηλαδή είναι όλα τα hashable) αυτό είναι συχνά πιο γρήγορο:
mylist = list(set(mylist))
Αυτό μετατρέπει τη λίστα σε ένα σύνολο, αφαιρώντας έτσι τα διπλότυπα και στη συνέχεια ξανά σε λίστα.
Πως αφαιρείτε πολλαπλά στοιχεία από μία λίστα¶
Όπως και με την κατάργηση των διπλότυπων, το ρητό iterating αντίστροφα με μια συνθήκη διαγραφής είναι μια πιθανότητα. Ωστόσο, είναι ευκολότερο και πιο γρήγορο να χρησιμοποιήσετε την αντικατάσταση τμημάτων με ένα έμμεσο ή ρητώς προς τα εμπρός iteration. Ακολουθούν τρεις παραλλαγές:
mylist[:] = filter(keep_function, mylist)
mylist[:] = (x for x in mylist if keep_condition)
mylist[:] = [x for x in mylist if keep_condition]
Το comprehension της λίστας μπορεί να είναι ταχύτερο.
Πως μπορείτε να φτιάξετε έναν πίνακα στην Python;¶
Χρησιμοποιήστε μια λίστα:
["this", 1, "is", "an", "array"]
Οι λίστες είναι ισοδύναμες με τους πίνακες της C ή Pascal στην χρονική τους πολυπλοκότητα∙ η κύρια διαφορά είναι ότι μια λίστα Python μπορεί να περιέχει αντικείμενα πολλών διαφορετικών τύπων.
The array
module also provides methods for creating arrays of fixed types
with compact representations, but they are slower to index than lists. Also
note that NumPy and other third party packages define array-like structures with
various characteristics as well.
To get Lisp-style linked lists, you can emulate cons cells using tuples:
lisp_list = ("like", ("this", ("example", None) ) )
If mutability is desired, you could use lists instead of tuples. Here the
analogue of lisp car is lisp_list[0]
and the analogue of cdr is
lisp_list[1]
. Only do this if you’re sure you really need to, because it’s
usually a lot slower than using Python lists.
Πως φτιάχνω μια πολυδιάστατη λίστα;¶
Μάλλον προσπαθήσατε να φτιάξετε έναν πολυδιάστατο πίνακα σαν αυτόν:
>>> 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] * 3
for i in range(3):
A[i] = [None] * 2
Αυτό δημιουργεί μια λίστα που περιέχει 3 διαφορετικές λίστες με μήκος δύο. Μπορείτε επίσης να χρησιμοποιήσετε ένα comprehension λίστας:
w, h = 2, 3
A = [[None] * w for i in range(h)]
Or, you can use an extension that provides a matrix datatype; NumPy is the best known.
How do I apply a method to a sequence of objects?¶
Use a list comprehension:
result = [obj.method() for obj in mylist]
Γιατί το a_tuple[i] += [“item”] δημιουργεί μια εξαίρεση όταν λειτουργεί η προσθήκη;¶
Αυτό οφείλεται σε έναν συνδυασμό του γεγονότος ότι οι επαυξημένοι τελεστές εκχώρησης είναι τελεστές εκχώρησης και της διαφοράς μεταξύ μεταβλητών και αμετάβλητων αντικειμένων στην Python.
Αυτή η συζήτηση ισχύει γενικά όταν οι επαυξημένοι τελεστές εκχώρησης εφαρμόζονται σε στοιχεία μιας πλειάδας που δείχνουν σε μεταβλητά αντικείμενα, αλλά θα χρησιμοποιήσουμε list
και +=
ως υπόδειγμά μας.
Εάν γράψετε:
>>> a_tuple = (1, 2)
>>> a_tuple[0] += 1
Traceback (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] = result
Traceback (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']
To see why this happens, you need to know that (a) if an object implements an
__iadd__
magic method, it gets called when the +=
augmented assignment
is executed, and its return value is what gets used in the assignment statement;
and (b) for lists, __iadd__
is equivalent to calling extend
on the list
and returning the list. That’s why we say that for lists, +=
is a
«shorthand» for 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] = result
Traceback (most recent call last):
...
TypeError: 'tuple' object does not support item assignment
The __iadd__
succeeds, and thus the list is extended, but even though
result
points to the same object that a_tuple[0]
already points to,
that final assignment still results in an error, because tuples are immutable.
Θέλω να κάνω μια περίπλοκη ταξινόμηση: μπορείτε να κάνετε ένα Schwartzian Transform στην Python;¶
Η τεχνική, που αποδίδεται στον Randal Schwartz της κοινότητας Perl, ταξινομεί τα στοιχεία μιας λίστας με βάση μια μέτρηση που αντιστοιχίζει κάθε στοιχείο στην » τιμή ταξινόμησης» του. Στην Python, χρησιμοποιήστε το όρισμα key
για τη μέθοδο list.sort()
:
Isorted = L[:]
Isorted.sort(key=lambda s: int(s[10:15]))
Πως μπορώ να ταξινομήσω μια λίστα με βάση τις τιμές από μια άλλη λίστα;¶
Συγχωνεύστε τα σε έναν iterator πλειάδων, ταξινομήστε τη λίστα που προκύπτει και, στην συνέχεια επιλέξτε το στοιχείο που θέλετε.
>>> list1 = ["what", "I'm", "sorting", "by"]
>>> list2 = ["something", "else", "to", "sort"]
>>> pairs = zip(list1, list2)
>>> pairs = sorted(pairs)
>>> pairs
[("I'm", 'else'), ('by', 'sort'), ('sorting', 'to'), ('what', 'something')]
>>> result = [x[1] for x in pairs]
>>> result
['else', 'sort', 'to', 'something']
Αντικείμενα¶
Τι είναι μια κλάση;¶
Μια κλάση είναι ο συγκεκριμένος τύπος αντικειμένου που δημιουργείται με την εκτέλεση μιας δήλωσης κλάσης. Τα αντικείμενα κλάσης χρησιμοποιούνται ως πρότυπα για τη δημιουργία αντικειμένων παρουσίας, τα οποία ενσωματώνουν τόσο τα δεδομένα (χαρακτηριστικά) όσο και τον κώδικα (μεθόδους) ειδικά για έναν τύπο δεδομένων.
Μια κλάση μπορεί να βασίζεται σε μία ή περισσότερες άλλες κλάσεις, που ονομάζονται βασικές κλάσεις της. Στη συνέχεια κληρονομεί τα χαρακτηριστικά και τις μεθόδους των βασικών κλάσεων. Αυτό επιτρέπει σε ένα μοντέλο αντικειμένου να βελτιωθεί διαδοχικά με κληρονομικότητα. Μπορεί να έχετε μια γενική κλάση Mailbox
που παρέχει βασικές μεθόδους πρόσβασης για ένα γραμματοκιβώτιο και υποκλάσεις όπως MboxMailbox
, MaildirMailbox
, OutlookMailbox
που χειρίζονται διάφορες συγκεκριμένες μορφές γραμματοκιβωτίου.
Τι είναι μια μέθοδος;¶
Μια μέθοδος είναι μια συνάρτηση σε κάποιο αντικείμενο x
που συνήθως καλείτε ως x.name(ορίσματα...)
. Οι μέθοδοι ορίζονται ως συναρτήσεις εντός του ορισμού κλάσης:
class C:
def meth(self, arg):
return arg * 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?.
Πώς μπορώ να ελέγξω εάν ένα αντικείμενο είναι μια οντότητα μιας δεδομένης κλάσης ή μιας υποκλάσης της;¶
Use the built-in function isinstance(obj, cls)
. You can check if an object
is an instance of any of a number of classes by providing a tuple instead of a
single class, e.g. isinstance(obj, (class1, class2, ...))
, and can also
check whether an object is one of Python’s built-in types, e.g.
isinstance(obj, str)
or isinstance(obj, (int, float, complex))
.
Λάβετε υπόψη ότι το isinstance()
ελέγχει επίσης για εικονική κληρονομικότητα από μια abstract base class. Έτσι, η δοκιμή θα επιστρέψει True
για μια εγγεγραμμένη κλάση ακόμα κι αν δεν έχει κληρονομήσει άμεσα ή έμμεσα από αυτό. Για να ελέγξετε μια «αληθινή κληρονομικότητα», σαρώστε το MRO της κλάσης:
from collections.abc import Mapping
class P:
pass
class C(P):
pass
Mapping.register(P)
>>> c = C()
>>> isinstance(c, C) # direct
True
>>> isinstance(c, P) # indirect
True
>>> isinstance(c, Mapping) # virtual
True
# Actual inheritance chain
>>> type(c).__mro__
(<class 'C'>, <class 'P'>, <class 'object'>)
# Test for "true inheritance"
>>> Mapping in type(c).__mro__
False
Λάβετε υπόψη ότι τα περισσότερα προγράμματα δεν χρησιμοποιούν το isinstance()
σε κλάσεις που ορίζονται από τη χρήστη πολύ συχνά. Εάν αναπτύσσετε μόνοι σας τις κλάσεις, ένα πιο σωστό αντικειμενοστρεφής στυλ είναι να ορίζετε μεθόδους στις κλάσεις που ενσωματώνουν μια συγκεκριμένη συμπεριφορά, αντί να ελέγχετε την κλάση του αντικειμένου και να κάνετε κάτι διαφορετικό με βάση την κλάση που είναι, για παράδειγμα, εάν έχετε μια συνάρτηση που κάνει κάτι:
def search(obj):
if isinstance(obj, Mailbox):
... # code to search a mailbox
elif isinstance(obj, Document):
... # code to search a document
elif ...
Μια καλύτερη προσέγγιση είναι να ορίσετε μια μέθοδο search()
σε όλες τις κλάσεις και απλώς να την καλέσετε:
class Mailbox:
def search(self):
... # code to search a mailbox
class Document:
def search(self):
... # code to search a document
obj.search()
Τι είναι το delegation;¶
Το delegation είναι μια αντικειμενοστραφής τεχνική (ονομάζεται επίσης μοτίβο σχεδίασης). Ας υποθέσουμε ότι έχετε ένα αντικείμενο x
και θέλετε να αλλάξετε τη συμπεριφορά μιας μόνο από τις μεθόδους του. Μπορείτε να δημιουργήσετε μια νέα κλάση που παρέχει μια νέα υλοποίηση της μεθόδου που σας ενδιαφέρει να αλλάξετε και εκχωρεί όλες τις άλλες μεθόδους στην αντίστοιχη μέθοδο του x
.
Οι προγραμματιστές Python μπορούν εύκολα να υλοποιήσουν την ανάθεση. Για παράδειγμα, η ακόλουθη κλάση υλοποιεί μια κλάση που συμπεριφέρεται σαν αρχείο αλλά μετατρέπει όλα τα γραπτά δεδομένα σε κεφαλαία:
class UpperOut:
def __init__(self, outfile):
self._outfile = outfile
def write(self, s):
self._outfile.write(s.upper())
def __getattr__(self, name):
return getattr(self._outfile, name)
Here the UpperOut
class redefines the write()
method to convert the
argument string to uppercase before calling the underlying
self._outfile.write()
method. All other methods are delegated to the
underlying self._outfile
object. The delegation is accomplished via the
__getattr__
method; consult the language reference
for more information about controlling attribute access.
Note that for more general cases delegation can get trickier. When attributes
must be set as well as retrieved, the class must define a __setattr__()
method too, and it must do so carefully. The basic implementation of
__setattr__()
is roughly equivalent to the following:
class X:
...
def __setattr__(self, name, value):
self.__dict__[name] = value
...
Most __setattr__()
implementations must modify self.__dict__
to store
local state for self without causing an infinite recursion.
How do I call a method defined in a base class from a derived class that overrides it?¶
Χρησιμοποιήστε την ενσωματωμένη συνάρτηση super()
:
class Derived(Base):
def meth(self):
super(Derived, self).meth()
For version prior to 3.0, you may be using classic classes: For a class
definition such as class Derived(Base): ...
you can call method meth()
defined in Base
(or one of Base
’s base classes) as Base.meth(self,
arguments...)
. Here, Base.meth
is an unbound method, so you need to
provide the self
argument.
Πως μπορώ να οργανώσω τον κώδικα μου προκειμένου να διευκολύνω την αλλαγή της βασικής κλάσης;¶
Θα μπορούσατε να αντιστοιχίσετε τη βασική κλάση σε ένα ψευδώνυμο και να προκύψει το ψευδώνυμο. Στην συνέχεια, το μόνο που πρέπει να αλλάξετε είναι η τιμή που έχει εκχωρηθεί ψευδώνυμο. Παρεμπιπτόντως, αυτό το κόλπο είναι επίσης χρήσιμο εάν θέλετε να αποφασίσετε δυναμικά (π.χ. ανάλογα με την διαθεσιμότητα των πόρων) ποια βασική κλάση να χρησιμοποιήσετε Παράδειγμα:
class Base:
...
BaseAlias = Base
class Derived(BaseAlias):
...
Πως δημιουργώ δεδομένα στατικής κλάσης και μεθόδους στατικής κλάσης;¶
Τόσο τα στατιστικά δεδομένα όσο και οι στατικές μέθοδοι (με την έννοια της C++ ή της Java) υποστηρίζονται στην Python.
Για στατικά δεδομένα, απλώς ορίστε ένα χαρακτηριστικό κλάσης. Για να εκχωρήσετε μια νέα τιμή στο χαρακτηριστικό, πρέπει να χρησιμοποιήσετε ρητά το όνομα κλάσης στην εκχώρηση:
class C:
count = 0 # number of times C.__init__ called
def __init__(self):
C.count = C.count + 1
def getcount(self):
return C.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
Οι στατικές μέθοδοι είναι δυνατές:
class C:
@staticmethod
def static(arg1, arg2, arg3):
# No 'self' parameter!
...
Ωστόσο, ένας πολύ πιο απλός τρόπος για να λάβετε το αποτέλεσμα μιας στατικής μεθόδου είναι μέσω μιας απλής συνάρτησης σε επίπεδο μονάδας:
def getcount():
return C.count
Εάν ο κώδικας σας είναι δομημένος έτσι ώστε να ορίζει μία κλάση (ή στενά συνδεδεμένη ιεραρχίας κλάσεων) ανά module, αυτό παρέχει την επιθυμητή ενθυλάκωση.
Πως μπορώ να υπερφορτώσω κατασκευαστές (ή μεθόδους) στην Python;¶
Αυτή η απάντηση ισχύει στην πραγματικότητα για όλες τις μεθόδους, αλλά η ερώτηση συνήθως εμφανίζεται πρώτη στο πλαίσιο των κατασκευαστών.
Στην C++ θα γράφατε
class C {
C() { cout << "No arguments\n"; }
C(int i) { cout << "Argument is " << i << "\n"; }
}
Στην Python πρέπει να γράψετε έναν μοναδικό κατασκευαστή που να πιάνει όλες τις περιπτώσεις χρησιμοποιώντας προεπιλεγμένα ορίσματα. Για παράδειγμα:
class C:
def __init__(self, i=None):
if i is None:
print("No arguments")
else:
print("Argument is", i)
Αυτό δεν είναι εντελώς ισοδύναμο, αλλά αρκετά κοντά στην πράξη.
Θα μπορούσατε επίσης να δοκιμάσετε μια λίστα ορισμάτων μεταβλητού μήκους, π.χ.
def __init__(self, *args):
...
Η ίδια προσέγγιση λειτουργεί για όλους τους ορισμούς μεθόδων.
Προσπαθώ να χρησιμοποιήσω __spam και λαμβάνω ένα σφάλμα σχετικά με το _SomeClassName__spam.¶
Τα ονόματα μεταβλητών με διπλή υπογράμμιση στην αρχή είναι «mangled» για να παρέχουν έναν απλό αλλά αποτελεσματικό τρόπο ορισμού των ιδιωτικών μεταβλητών κλάσης. Οποιοδήποτε αναγνωριστικό της φόρμας __spam
(τουλάχιστον δύο προπορευόμενες κάτω παύλες, το πολύ ένα τέλος υπογράμμισης) αντικαθίσταται μέσω κειμένου το _classname__spam
, όπου το classname
είναι το τρέχον όνομα κλάσης με απογυμνωμένες τυχόν προηγούμενες παύλες.
This doesn’t guarantee privacy: an outside user can still deliberately access
the «_classname__spam» attribute, and private values are visible in the object’s
__dict__
. Many Python programmers never bother to use private variable
names at all.
Η κλάση μου ορίζει __del__ αλλά δεν καλείται όταν διαγράφω το αντικείμενο.¶
Υπάρχουν διάφοροι πιθανοί λόγοι για αυτό.
The del statement does not necessarily call __del__()
– it simply
decrements the object’s reference count, and if this reaches zero
__del__()
is called.
If your data structures contain circular links (e.g. a tree where each child has
a parent reference and each parent has a list of children) the reference counts
will never go back to zero. Once in a while Python runs an algorithm to detect
such cycles, but the garbage collector might run some time after the last
reference to your data structure vanishes, so your __del__()
method may be
called at an inconvenient and random time. This is inconvenient if you’re trying
to reproduce a problem. Worse, the order in which object’s __del__()
methods are executed is arbitrary. You can run gc.collect()
to force a
collection, but there are pathological cases where objects will never be
collected.
Despite the cycle collector, it’s still a good idea to define an explicit
close()
method on objects to be called whenever you’re done with them. The
close()
method can then remove attributes that refer to subobjects. Don’t
call __del__()
directly – __del__()
should call close()
and
close()
should make sure that it can be called more than once for the same
object.
Ένα άλλος τρόπος για να αποφύγετε τις κυκλικές αναφορές είναι να χρησιμοποιήσετε το module weakref
, το οποίο σας επιτρέπει να αυξάνετε τον αριθμό των αναφορών τους. Οι δομές δεδομένων δέντρων, για παράδειγμα, θα πρέπει να χρησιμοποιούν αδύναμες αναφορές για τις αναφορές γονέων και αδελφών ( αν τα χρειαστούν!).
Finally, if your __del__()
method raises an exception, a warning message
is printed to 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
ελέγχει την ταυτότητα του αντικειμένου. Η δοκιμή a is b
ισοδυναμεί με id(a) == id(b)
.
Η πιο σημαντική ιδιότητα ενός τεστ ταυτότητας είναι ότι ένα αντικείμενο είναι πάντα πανομοιότυπο με τον εαυτό του, το a is a
επιστρέφει πάντα True
. Τα τεστ ταυτότητας είναι συνήθως ταχύτερα από τα τεστ ισότητας. Και σε αντίθεση με τα τεστ ισότητας, τα τεστ ταυτότητας είναι εγγυημένα ότι θα επιστρέψουν ένα boolean True
ή False
.
Ωστόσο, τα τεστ ταυτότητας μπορούν μόνο να αντικαταστήσουν τα τεστ ισότητας όταν είναι εξασφαλισμένη η ταυτότητα αντικειμένου. Γενικά, υπάρχουν τρεις περιπτώσεις όπου η ταυτότητα είναι εγγυημένη:
1) Assignments create new names but do not change object identity. After the
assignment new = old
, it is guaranteed that new is old
.
2) Putting an object in a container that stores object references does not
change object identity. After the list assignment s[0] = x
, it is
guaranteed that s[0] is x
.
3) If an object is a singleton, it means that only one instance of that object
can exist. After the assignments a = None
and b = None
, it is
guaranteed that a is b
because None
is a singleton.
Στις περισσότερες άλλες περιπτώσεις, τα τεστ ταυτότητας δεν ενδείκνυνται και προτιμώνται τα τεστ ισότητας. Ειδικότερα, τα τεστ ταυτότητας δεν θα πρέπει να χρησιμοποιούνται για τον έλεγχο σταθερών όπως int
και str
που δεν είναι εγγυημένα singletons:
>>> a = 1000
>>> b = 500
>>> c = b + 500
>>> a is c
False
>>> a = 'Python'
>>> b = 'Py'
>>> c = b + 'thon'
>>> a is c
False
Ομοίως, τα νέα στιγμιότυπα μεταβλητών κοντέινερ δεν είναι ποτέ πανομοιότυπα:
>>> a = []
>>> b = []
>>> a is b
False
Στον τυπικό κώδικα βιβλιοθήκης, θα δείτε πολλά κοινά μοτίβα για τη σωστή χρήση των δοκιμών ταυτότητας:
1) As recommended by PEP 8, an identity test is the preferred way to check
for None
. This reads like plain English in code and avoids confusion with
other objects that may have boolean values that evaluate to false.
2) Detecting optional arguments can be tricky when None
is a valid input
value. In those situations, you can create a singleton sentinel object
guaranteed to be distinct from other objects. For example, here is how
to implement a method that behaves like dict.pop()
:
_sentinel = object()
def pop(self, key, default=_sentinel):
if key in self:
value = self[key]
del self[key]
return value
if default is _sentinel:
raise KeyError(key)
return default
3) Container implementations sometimes need to augment equality tests with
identity tests. This prevents the code from being confused by objects such as
float('NaN')
that are not equal to themselves.
For example, here is the implementation of
collections.abc.Sequence.__contains__()
:
def __contains__(self, value):
for v in self:
if v is value or v == value:
return True
return False
Πώς μπορεί μια υποκλάση να ελέγξει ποια δεδομένα αποθηκεύονται σε μια αμετάβλητη παρουσία;¶
When subclassing an immutable type, override the __new__()
method
instead of the __init__()
method. The latter only runs after an
instance is created, which is too late to alter data in an immutable
instance.
Όλες αυτές οι αμετάβλητες κλάσεις έχουν διαφορετική υπογραφή από τη μητρική τους κλάση:
from datetime import date
class FirstOfMonthDate(date):
"Always choose the first day of the month"
def __new__(cls, year, month, day):
return super().__new__(cls, year, month, 1)
class NamedInt(int):
"Allow text names for some numbers"
xlat = {'zero': 0, 'one': 1, 'ten': 10}
def __new__(cls, value):
value = cls.xlat.get(value, value)
return super().__new__(cls, value)
class TitleStr(str):
"Convert str to name suitable for a URL path"
def __new__(cls, s):
s = s.lower().replace(' ', '-')
s = ''.join([c for c in s if c.isalnum() or c == '-'])
return super().__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'
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
που εισάγει ένα άλλο module xyz.py
, όταν εκτελείτε το foo
(πληκτρολογώντας python foo.py
ως εντολή κελύφους), θα δημιουργηθεί ένα .pyc
για το xyz
επειδή το xyz
έχει εισαχθεί, αλλά δεν θα δημιουργηθεί αρχείο .pyc
για το foo
καθώς το foo.py
δεν εισάγεται.
Εάν χρειάζεται να δημιουργήσετε ένα αρχείο .pyc
για το foo
– δηλαδή, να δημιουργήσετε ένα αρχείο .pyc
για ένα module που δεν έχει εισαχθεί – μπορείτε, χρησιμοποιώντας τα modules py_compile
και compileall
.
Το module py_compile
μπορεί να μεταγλωττίσει χειροκίνητα οποιαδήποτε module. Ένας τρόπος είναι να χρησιμοποιήσετε τη συνάρτηση compile()
σε αυτήν την ενότητα διαδραστικά:
>>> import py_compile
>>> py_compile.compile('foo.py')
Αυτό θα γράψει το .pyc
σε έναν υποκατάλογο __pycache__
στην ίδια θέση με το foo.py
(ή μπορείτε να το παρακάμψετε με την προαιρετική παράμετρο cfile
).
Μπορείτε επίσης να μεταγλωττίσετε αυτόματα όλα τα αρχεία σε έναν κατάλογο ή καταλόγους χρησιμοποιώντας το module compileall
. Μπορείτε να κάνετε από το shell prompt εκτελώντας το compileall.py
και παρέχοντας τη διαδρομή ενός καταλόγου που περιέχει αρχεία Python για μεταγλώττιση:
python -m compileall .
Πως μπορώ να βρω το όνομα του τρέχοντος module;¶
Ένα module μπορεί να βρει το δικό του όνομα module κοιτάζοντας την προκαθορισμένη καθολική μεταβλητή __name__
. Εάν αυτή έχει την τιμή __main__
, το πρόγραμμα εκτελείται ως σενάριο. Πολλά modules που χρησιμοποιούνται συνήθως με την εισαγωγή τους παρέχουν επίσης μια διεπαφή γραμμής εντολών ή έναν αυτοέλεγχο και εκτελέστε αυτόν τον κώδικα μόνο αφού ελέγξετε το __name__
:
def main():
print('Running test...')
...
if __name__ == '__main__':
main()
Πως μπορώ να έχω modules που εισάγουν αμοιβαία το ένα το άλλο;¶
Υποθέστε ότι έχετε τα ακόλουθα modules:
foo.py:
from bar import bar_var
foo_var = 1
bar.py:
from foo import foo_var
bar_var = 2
Το πρόβλημα είναι ότι ο διερμηνέας θα εκτελέσει τα ακόλουθα βήματα:
main imports foo
Empty globals for foo are created
foo is compiled and starts executing
foo imports bar
Empty globals for bar are created
bar is compiled and starts executing
bar imports foo (which is a no-op since there already is a module named foo)
bar.foo_var = foo.foo_var
Το τελευταίο βήμα αποτυγχάνει, επειδή η Python δεν έχει τελειώσει ακόμα με την ερμηνεία του foo
και το global λεξικό συμβόλων για το foo
είναι ακόμα κενό.
Το ίδιο συμβαίνει όταν χρησιμοποιείτε το import foo
και, στη συνέχεια, προσπαθείτε να αποκτήσετε πρόσβαση στο 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 θα αναλυθεί και θα αναλυθεί ξανά πολλές φορές. Για να αναγκάσετε τη εκ νέου ανάγνωση μιας αλλαγμένης ενότητας , κάντε το εξής:
import importlib
import modname
importlib.reload(modname)
Προειδοποίηση: αυτή η τεχνική δεν είναι 100% ασφαλής. Ειδικότερα, modules που περιέχουν δηλώσεις όπως
from modname import some_objects
θα συνεχίσει να λειτουργεί με την παλιά έκδοση των εισαγόμενων αντικειμένων. Εάν η λειτουργική μονάδα περιέχει ορισμούς κλάσεων, οι υπάρχουσες παρουσίες κλάσεων δεν θα ενημερωθούν για να χρησιμοποιούν τον ορισμό της νέας κλάσης. Αυτό μπορεί να οδηγήσει στην ακόλουθη παράδοξη συμπεριφορά:
>>> import importlib
>>> import cls
>>> 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'