Ο κανονικός τρόπος επιστροφής πολλαπλών τιμών σε γλώσσες που το υποστηρίζουν είναι συχνά η tupling.
Σκεφτείτε αυτό το τετριμμένο παράδειγμα:
def f(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return (y0, y1, y2)
Ωστόσο, αυτό γίνεται γρήγορα προβληματικό καθώς αυξάνεται ο αριθμός των τιμών που επιστρέφονται. Τι γίνεται αν θέλετε να επιστρέψετε τέσσερις ή πέντε τιμές; Σίγουρα, θα μπορούσατε να συνεχίσετε να τις πλειοδοτείτε, αλλά γίνεται εύκολο να ξεχάσετε ποια τιμή βρίσκεται πού. Είναι επίσης μάλλον άσχημο να τις ξεπακετάρετε όπου θέλετε να τις λάβετε.
Το επόμενο λογικό βήμα φαίνεται να είναι η εισαγωγή κάποιου είδους 'σημείωσης εγγραφής'. Στην Python, ο προφανής τρόπος για να γίνει αυτό είναι μέσω ενός dict
.
Σκεφτείτε το εξής:
def g(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return {'y0': y0, 'y1': y1 ,'y2': y2}
(Για να είμαστε σαφείς, τα y0, y1 και y2 εννοούνται απλώς ως αφηρημένα αναγνωριστικά. Όπως επισημάνθηκε, στην πράξη θα χρησιμοποιούσατε σημαίνοντα αναγνωριστικά).
Τώρα, έχουμε έναν μηχανισμό με τον οποίο μπορούμε να προβάλλουμε ένα συγκεκριμένο μέλος του επιστρεφόμενου αντικειμένου. Για παράδειγμα,
result['y0']
Ωστόσο, υπάρχει και μια άλλη επιλογή. Θα μπορούσαμε αντ' αυτού να επιστρέψουμε μια εξειδικευμένη δομή. Το διατύπωσα αυτό στο πλαίσιο της Python, αλλά είμαι σίγουρος ότι ισχύει και για άλλες γλώσσες. Πράγματι, αν δουλεύατε σε C, αυτή θα μπορούσε κάλλιστα να είναι η μόνη σας επιλογή. Ορίστε:
class ReturnValue:
def __init__(self, y0, y1, y2):
self.y0 = y0
self.y1 = y1
self.y2 = y2
def g(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return ReturnValue(y0, y1, y2)
Στην Python οι δύο προηγούμενες είναι ίσως πολύ παρόμοιες από την άποψη του υδραυλικού εξοπλισμού - τελικά { y0, y1, y2 }
καταλήγουν να είναι απλά καταχωρήσεις στην εσωτερική __dict__
της ReturnValue
.
Υπάρχει όμως ένα επιπλέον χαρακτηριστικό που παρέχει η Python για μικροσκοπικά αντικείμενα, το χαρακτηριστικό __slots__
. Η κλάση θα μπορούσε να εκφραστεί ως εξής:
class ReturnValue(object):
__slots__ = ["y0", "y1", "y2"]
def __init__(self, y0, y1, y2):
self.y0 = y0
self.y1 = y1
self.y2 = y2
Από το Εγχειρίδιο αναφοράς της Python:
Η δήλωση
__slots__
παίρνει μια ακολουθία από μεταβλητές παραδείγματος και κρατά ακριβώς αρκετό χώρο σε κάθε παράδειγμα για να κρατήσει μια τιμή για κάθε μεταβλητή. Ο χώρος εξοικονομείται επειδή η__dict__
δεν δημιουργείται για κάθε περίπτωση.
Χρησιμοποιώντας τις νέες κλάσεις δεδομένων της Python 3.7'επιστρέφετε μια κλάση με αυτόματα προστιθέμενες ειδικές μεθόδους, τυποποίηση και άλλα χρήσιμα εργαλεία:
@dataclass
class Returnvalue:
y0: int
y1: float
y3: int
def total_cost(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return ReturnValue(y0, y1, y2)
Μια άλλη πρόταση που είχα παραβλέψει προέρχεται από τον Bill the Lizard:
def h(x):
result = [x + 1]
result.append(x * 3)
result.append(y0 ** y3)
return result
Αυτή είναι η λιγότερο αγαπημένη μου μέθοδος όμως. Υποθέτω ότι είμαι στιγματισμένος από την έκθεση στη Haskell, αλλά η ιδέα των λιστών μικτού τύπου πάντα μου φαινόταν άβολη. Στο συγκεκριμένο παράδειγμα η λίστα δεν είναι μικτού τύπου, αλλά θα μπορούσε να είναι.
Μια λίστα που χρησιμοποιείται με αυτόν τον τρόπο πραγματικά δεν κερδίζει τίποτα σε σχέση με την πλειάδα από όσο μπορώ να πω. Η μόνη πραγματική διαφορά μεταξύ λιστών και πλειάδων στην Python είναι ότι οι λίστες είναι μεταβλητές, ενώ οι πλειάδες όχι.
Προσωπικά τείνω να μεταφέρω τις συμβάσεις από τον λειτουργικό προγραμματισμό: χρησιμοποιήστε λίστες για οποιοδήποτε αριθμό στοιχείων του ίδιου τύπου, και πλειάδες για ένα σταθερό αριθμό στοιχείων προκαθορισμένων τύπων.
Μετά το μακροσκελές προοίμιο, έρχεται η αναπόφευκτη ερώτηση. Ποια μέθοδος (πιστεύετε ότι) είναι η καλύτερη;
Συνήθως επιλέγω τη διαδρομή του λεξικού, επειδή περιλαμβάνει λιγότερη δουλειά εγκατάστασης. Από την άποψη των τύπων, ωστόσο, ίσως είναι καλύτερα να ακολουθήσετε τη διαδρομή της κλάσης, καθώς αυτό μπορεί να σας βοηθήσει να αποφύγετε τη σύγχυση σε σχέση με το τι αντιπροσωπεύει ένα λεξικό.
Από την άλλη πλευρά, υπάρχουν κάποιοι στην κοινότητα της Python που πιστεύουν ότι οι υπονοούμενες διασυνδέσεις πρέπει να προτιμώνται από τις ρητές διασυνδέσεις, οπότε ο τύπος του αντικειμένου πραγματικά δεν έχει σημασία, αφού ουσιαστικά βασίζεστε στη σύμβαση ότι το ίδιο χαρακτηριστικό θα έχει πάντα την ίδια σημασία.
Έτσι, πώς επιστρέφετε -εσύ- πολλαπλές τιμές στην Python;
Προτιμώ:
def g(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return {'y0':y0, 'y1':y1 ,'y2':y2 }
Φαίνεται ότι όλα τα υπόλοιπα είναι απλώς επιπλέον κώδικας για να γίνει το ίδιο πράγμα.
Γενικά, η "εξειδικευμένη δομή" στην πραγματικότητα ΕΙΝΑΙ μια λογική τρέχουσα κατάσταση ενός αντικειμένου, με τις δικές της μεθόδους.
class Some3SpaceThing(object):
def __init__(self,x):
self.g(x)
def g(self,x):
self.y0 = x + 1
self.y1 = x * 3
self.y2 = y0 ** y3
r = Some3SpaceThing( x )
r.y0
r.y1
r.y2
Μου αρέσει να βρίσκω ονόματα για ανώνυμες δομές όπου είναι δυνατόν. Τα ουσιαστικά ονόματα κάνουν τα πράγματα πιο ξεκάθαρα.
Σε γλώσσες όπως η Python, θα χρησιμοποιούσα συνήθως ένα λεξικό, καθώς συνεπάγεται λιγότερη επιβάρυνση από τη δημιουργία μιας νέας κλάσης.
Ωστόσο, αν διαπιστώνω ότι επιστρέφω συνεχώς το ίδιο σύνολο μεταβλητών, τότε αυτό πιθανώς συνεπάγεται μια νέα κλάση που θα την υπολογίσω.