Συνήθεις Συντακτικές Δομές Python στην Επιστήμη Δεδομένων (Προχωρημένο Επίπεδο)
Αυτές τις μέρες διαβάζω το βιβλίο Data Science from Scratch (Διεύθυνση PDF), ένα εξαιρετικό και ευκολοδιάβαστο εισαγωγικό σύγγραμμα στην επιστήμη δεδομένων. Ένα κεφάλαιο του βιβλίου παρουσιάζει τη βασική σύνταξη της Python, καθώς και πιο προχωρημένες συντακτικές δομές που χρησιμοποιούνται συχνά στην επιστήμη δεδομένων. Βρήκα την παρουσίαση πολύ καλή, σύντομη και σαφή, γι’ αυτό και τη μεταφράζω εδώ για να την έχω ως σημείωση. Συνήθεις Συντακτικές Δομές Python στην Επιστήμη Δεδομένων (Βασικό Επίπεδο) Συνήθεις Συντακτικές Δομές Python στην Επιστήμη Δεδομένων (Προχωρημένο Επίπεδο)
Αυτό το κεφάλαιο επικεντρώνεται σε προχωρημένες συντακτικές δομές και λειτουργίες της Python (βασισμένες στην Python 2.7) που είναι εξαιρετικά χρήσιμες στην επεξεργασία δεδομένων.
Ταξινόμηση (Sorting)
Αν θέλετε να ταξινομήσετε μια λίστα στην Python, μπορείτε να χρησιμοποιήσετε τη μέθοδο sort της λίστας. Αν δεν θέλετε να τροποποιήσετε την αρχική λίστα, μπορείτε να χρησιμοποιήσετε τη συνάρτηση sorted, η οποία επιστρέφει μια νέα, ταξινομημένη λίστα:
x = [4,1,2,3]
y = sorted(x) # y = [1,2,3,4], το x παραμένει αμετάβλητο
x.sort() # Το x είναι τώρα = [1,2,3,4]
# Οι μέθοδοι sort και sorted ταξινομούν τις λίστες από προεπιλογή σε αύξουσα σειρά.
Αν θέλετε να ταξινομήσετε σε φθίνουσα σειρά, μπορείτε να ορίσετε την παράμετρο reverse = True.
Μπορείτε επίσης να ορίσετε προσαρμοσμένες συναρτήσεις ταξινόμησης, ώστε η λίστα να ταξινομείται με βάση ένα συγκεκριμένο κλειδί (key):
# Ταξινόμηση κατά απόλυτη τιμή, από το μεγαλύτερο στο μικρότερο
x = sorted([-4,1,-2,3], key=abs, reverse=True) # is [-4,3,-2,1]
# Ταξινόμηση κατά συχνότητα εμφάνισης λέξεων, από το μεγαλύτερο στο μικρότερο
wc = sorted(word_counts.items(),
key=lambda (word, count): count,
reverse=True)
Κατανοήσεις Λιστών (List Comprehensions)
Συχνά θα συναντήσετε περιπτώσεις όπου θέλετε να εξάγετε συγκεκριμένα στοιχεία από μια λίστα για να δημιουργήσετε μια νέα, ή να αλλάξετε τις τιμές ορισμένων στοιχείων, ή και τα δύο. Η ιδιωματική προσέγγιση στην Python για αυτό είναι οι Κατανοήσεις Λιστών (List Comprehensions):
even_numbers = [x for x in range(5) if x % 2 == 0] # [0, 2, 4]
squares = [x * x for x in range(5)] # [0, 1, 4, 9, 16]
even_squares = [x * x for x in even_numbers] # [0, 4, 16]
Παρόμοια, μπορείτε να μετατρέψετε λίστες σε λεξικά ή σύνολα:
square_dict = { x : x * x for x in range(5) } # { 0:0, 1:1, 2:4, 3:9, 4:16 }
square_set = { x * x for x in [1, -1] } # { 1 }
Αν δεν χρειάζεται να χρησιμοποιήσετε τα στοιχεία της λίστας, μπορείτε να χρησιμοποιήσετε την κάτω παύλα (_) ως μεταβλητή:
zeroes = [0 for _ in even_numbers] # Έχει το ίδιο μήκος με τη λίστα even_numbers
Οι κατανοήσεις λιστών υποστηρίζουν πολλαπλούς βρόχους for:
pairs = [(x, y)
for x in range(10)
for y in range(10)] # Συνολικά 100 ζεύγη: (0,0) (0,1) ... (9,8), (9,9)
Οι επόμενοι βρόχοι for μπορούν να χρησιμοποιήσουν τα αποτελέσματα των προηγούμενων βρόχων for:
increasing_pairs = [(x, y) # Περιλαμβάνει μόνο ζεύγη όπου x < y
for x in range(10) # range(lo, hi) equals
for y in range(x + 1, 10)] # [lo, lo + 1, ..., hi - 1]
Θα χρησιμοποιούμε συχνά κατανοήσεις λιστών στο μέλλον.
Γεννήτριες και Επαναλήπτες (Generators and Iterators)
Ένα πρόβλημα με τις λίστες είναι ότι μπορεί εύκολα να γίνουν τεράστιες. Για παράδειγμα, το range(1000000) θα δημιουργήσει μια λίστα με ένα εκατομμύριο στοιχεία. Αν έπρεπε να επεξεργαστείτε όλα τα δεδομένα ταυτόχρονα, αυτό θα απαιτούσε υπερβολικό χρόνο (ή θα εξαντλούσε τη μνήμη). Στην πράξη, μπορεί να χρειάζεστε μόνο τα πρώτα λίγα στοιχεία, καθιστώντας τις υπόλοιπες πράξεις περιττές.
Οι γεννήτριες, από την άλλη πλευρά, σας επιτρέπουν να επαναλάβετε μόνο τα δεδομένα που χρειάζεστε. Μπορείτε να δημιουργήσετε μια γεννήτρια χρησιμοποιώντας συναρτήσεις και την έκφραση yield:
def lazy_range(n):
"""μια 'τεμπέλικη' έκδοση του range"""
i = 0
while i < n:
yield i
i += 1
Σημείωση του Μεταφραστή:
Οι γεννήτριες είναι επίσης ένας ειδικός τύπος επαναλήπτη (iterator). Η λέξη-κλειδί yield είναι το κλειδί για την υλοποίηση της επανάληψης σε μια γεννήτρια. Λειτουργεί ως σημείο παύσης και συνέχισης της εκτέλεσης της γεννήτριας, επιτρέποντας την ανάθεση τιμών στην έκφραση yield ή την επιστροφή της τιμής της. Οποιαδήποτε συνάρτηση περιέχει μια εντολή yield ονομάζεται γεννήτρια. Όταν η εκτέλεση εξέρχεται από μια γεννήτρια, αυτή αποθηκεύει την τρέχουσα κατάσταση εκτέλεσής της και την επαναφέρει στην επόμενη κλήση για να παράγει την επόμενη επαναληπτική τιμή. Η χρήση λιστών για επανάληψη μπορεί να καταλάβει μεγάλο χώρο στη μνήμη, ενώ η χρήση γεννητριών καταλαμβάνει περίπου τον χώρο μιας μόνο διεύθυνσης, επιτυγχάνοντας έτσι εξοικονόμηση μνήμης.
Ο παρακάτω βρόχος θα καταναλώνει μία-μία τις τιμές από το yield μέχρι να εξαντληθούν:
for i in lazy_range(10):
do_something_with(i)
(Στην πραγματικότητα, η Python διαθέτει μια ενσωματωμένη συνάρτηση που επιτυγχάνει το ίδιο αποτέλεσμα με το παραπάνω lazy_range, ονομάζεται xrange στην Python 2 και range στην Python 3, η οποία είναι ‘lazy’.) Αυτό σημαίνει ότι μπορείτε να δημιουργήσετε μια άπειρη ακολουθία:
def natural_numbers():
"""Επιστρέφει 1, 2, 3, ..."""
n = 1
while True:
yield n
n += 1
Ωστόσο, δεν συνιστάται η χρήση τέτοιων δηλώσεων χωρίς λογική εξόδου από τον βρόχο.
ΣΥΜΒΟΥΛΗ
Ένα μειονέκτημα της επανάληψης με γεννήτριες είναι ότι μπορείτε να επαναλάβετε τα στοιχεία μόνο μία φορά από την αρχή μέχρι το τέλος. Αν θέλετε να επαναλάβετε πολλές φορές, πρέπει να δημιουργείτε μια νέα γεννήτρια κάθε φορά ή να χρησιμοποιείτε μια λίστα.
Ο δεύτερος τρόπος για να δημιουργήσετε μια γεννήτρια είναι χρησιμοποιώντας μια έκφραση κατανόησης μέσα σε παρενθέσεις:
lazy_evens_below_20 = (i for i in lazy_range(20) if i % 2 == 0)
Γνωρίζουμε ότι η μέθοδος items() ενός λεξικού επιστρέφει μια λίστα με όλα τα ζεύγη κλειδιών-τιμών του λεξικού. Ωστόσο, στις περισσότερες περιπτώσεις, χρησιμοποιούμε τη μέθοδο γεννήτριας iteritems() για να επαναλάβουμε, παράγοντας και επιστρέφοντας ένα ζεύγος κλειδιού-τιμής κάθε φορά.
Τυχαιότητα (Randomness)
Κατά τη μελέτη της επιστήμης δεδομένων, θα χρειαστεί συχνά να δημιουργούμε τυχαίους αριθμούς. Αρκεί να εισάγουμε το module random για να το χρησιμοποιήσουμε:
import random
four_uniform_randoms = [random.random() for _ in range(4)]
# [0.8444218515250481, # random.random() παράγει τυχαίους αριθμούς
# 0.7579544029403025, # Οι τυχαίοι αριθμοί κανονικοποιούνται, κυμαίνονται μεταξύ 0 και 1
# 0.420571580830845, # Αυτή η συνάρτηση είναι η πιο συχνά χρησιμοποιούμενη για τη δημιουργία τυχαίων αριθμών
# 0.25891675029296335]
Αν θέλετε αναπαραγώγιμα αποτελέσματα, μπορείτε να κάνετε το module random να παράγει ψευδοτυχαίους (δηλαδή ντετερμινιστικούς) αριθμούς βασισμένους στην εσωτερική κατάσταση που ορίζεται από το random.seed:
random.seed(10) # θέτει το seed στο 10
print random.random() # 0.57140259469
random.seed(10) # επαναφέρει το seed στο 10
print random.random() # 0.57140259469 ξανά
Μερικές φορές χρησιμοποιούμε επίσης τη συνάρτηση random.randrange για να δημιουργήσουμε έναν τυχαίο αριθμό εντός συγκεκριμένου εύρους:
random.randrange(10) # Επιλέγει τυχαία έναν αριθμό από το range(10) = [0, 1, ..., 9]
random.randrange(3, 6) # Επιλέγει τυχαία έναν αριθμό από το range(3, 6) = [3, 4, 5]
Υπάρχουν και άλλες μέθοδοι που είναι μερικές φορές πολύ χρήσιμες, όπως η random.shuffle, η οποία ανακατεύει τη σειρά των στοιχείων σε μια λίστα, δημιουργώντας μια νέα τυχαία διατεταγμένη λίστα:
up_to_ten = range(10)
random.shuffle(up_to_ten)
print up_to_ten
# [2, 5, 1, 9, 7, 3, 8, 6, 4, 0] (το αποτέλεσμά σας πιθανώς θα είναι διαφορετικό)
Αν θέλετε να επιλέξετε ένα τυχαίο στοιχείο από μια λίστα, μπορείτε να χρησιμοποιήσετε τη μέθοδος random.choice:
my_best_friend = random.choice(["Alice", "Bob", "Charlie"]) # Το αποτέλεσμά μου ήταν "Bob"
Αν θέλετε να δημιουργήσετε μια τυχαία ακολουθία χωρίς να ανακατέψετε την αρχική λίστα, μπορείτε να χρησιμοποιήσετε τη μέθοδο random.sample:
lottery_numbers = range(60)
winning_numbers = random.sample(lottery_numbers, 6) # [16, 36, 10, 6, 25, 9]
Μπορείτε να επιλέξετε πολλαπλά τυχαία δείγματα (με επανάληψη) κάνοντας πολλαπλές κλήσεις:
four_with_replacement = [random.choice(range(10))
for _ in range(4)]
# [9, 4, 4, 2]
Κανονικές Εκφράσεις (Regular Expressions)
Οι Κανονικές Εκφράσεις χρησιμοποιούνται για την αναζήτηση κειμένου. Είναι λίγο πολύπλοκες, αλλά εξαιρετικά χρήσιμες, και υπάρχουν πολλά βιβλία αφιερωμένα αποκλειστικά σε αυτές. Θα τις εξηγήσουμε λεπτομερώς όταν τις συναντήσουμε. Παρακάτω παρατίθενται μερικά παραδείγματα χρήσης Κανονικών Εκφράσεων στην Python:
import re
print all([ # Όλες οι παρακάτω δηλώσεις επιστρέφουν True, επειδή
not re.match("a", "cat"), # * το 'cat' δεν ξεκινά με 'a'
re.search("a", "cat"), # * το 'cat' περιέχει το γράμμα 'a'
not re.search("c", "dog"), # * το 'dog' δεν περιέχει το γράμμα 'c'
3 == len(re.split("[ab]", "carbs")), # * χωρίζει τη λέξη σε τρία μέρη ['c','r','s'] με βάση τα 'a' ή 'b'
"R-D-" == re.sub("[0-9]", "-", "R2D2") # * αντικαθιστά τους αριθμούς με παύλες
]) # Έξοδος True
Αντικειμενοστραφής Προγραμματισμός (Object-Oriented Programming)
Όπως πολλές γλώσσες, η Python σας επιτρέπει να ορίζετε κλάσεις που ενθυλακώνουν δεδομένα και συναρτήσεις που λειτουργούν πάνω σε αυτά. Μερικές φορές τις χρησιμοποιούμε για να κάνουμε τον κώδικά μας πιο σαφή και συνοπτικό. Ίσως ο ευκολότερος τρόπος να τις εξηγήσουμε είναι με ένα καλά σχολιασμένο παράδειγμα. Ας υποθέσουμε ότι δεν υπήρχαν ενσωματωμένα σύνολα στην Python. Θα θέλαμε ίσως να δημιουργήσουμε τη δική μας κλάση Set. Ποιες λειτουργίες θα έπρεπε να έχει αυτή η κλάση; Για παράδειγμα, δεδομένου ενός Set, θα πρέπει να μπορούμε να προσθέτουμε στοιχεία σε αυτό, να αφαιρούμε στοιχεία από αυτό και να ελέγχουμε αν περιέχει μια συγκεκριμένη τιμή. Έτσι, θα δημιουργήσουμε όλες αυτές τις λειτουργίες ως συναρτήσεις-μέλη αυτής της κλάσης. Με αυτόν τον τρόπο, μπορούμε να έχουμε πρόσβαση σε αυτές τις συναρτήσεις-μέλη χρησιμοποιώντας την τελεία μετά το αντικείμενο Set:
# Κατά σύμβαση, δίνουμε ονόματα κλάσεων σε _PascalCase_
class Set:
# Αυτές είναι συναρτήσεις-μέλη
# Κάθε συνάρτηση-μέλος έχει μια παράμετρο "self" στην αρχή (άλλη μια σύμβαση)
# Το "self" αντιστοιχεί στο συγκεκριμένο αντικείμενο Set που χρησιμοποιείται
def __init__(self, values=None):
"""Αυτή είναι η συνάρτηση δημιουργίας
Κάθε φορά που δημιουργείτε ένα νέο Set, καλείται αυτή η συνάρτηση
Μπορεί να κληθεί ως εξής
s1 = Set() # Κενό σύνολο
s2 = Set([1,2,2,3]) # Αρχικοποιεί το σύνολο με τις καθορισμένες τιμές"""
self.dict = {} # Κάθε στιγμιότυπο του Set έχει το δικό του χαρακτηριστικό dict
# Χρησιμοποιούμε αυτό το χαρακτηριστικό για να παρακολουθούμε κάθε μέλος
if values is not None:
for value in values:
self.add(value)
def __repr__(self):
"""Αυτή είναι η αναπαράσταση συμβολοσειράς του αντικειμένου Set
Μπορείτε να την δείτε πληκτρολογώντας το αντικείμενο στο παράθυρο εντολών της Python ή περνώντας το αντικείμενο στη μέθοδο str()"""
return "Set: " + str(self.dict.keys())
# Θα αναπαραστήσουμε τη συμμετοχή ως κλειδιά στο self.dict, με τις τιμές τους να είναι True
def add(self, value):
self.dict[value] = True
# Αν η παράμετρος είναι κλειδί στο λεξικό, η αντίστοιχη τιμή βρίσκεται στο Set
def contains(self, value):
return value in self.dict
def remove(self, value):
del self.dict[value]
Τότε μπορούμε να χρησιμοποιήσουμε το Set ως εξής:
s = Set([1,2,3])
s.add(4)
print s.contains(4) # True
s.remove(3)
print s.contains(3) # False
Συναρτησιακά Εργαλεία (Functional Tools)
Μερικές Συναρτήσεις (partial)
Όταν περνάμε συναρτήσεις, μερικές φορές θέλουμε να χρησιμοποιήσουμε ένα μέρος της λειτουργικότητας μιας συνάρτησης για να δημιουργήσουμε μια νέα συνάρτηση. Για ένα απλό παράδειγμα, ας υποθέσουμε ότι έχουμε μια συνάρτηση με δύο μεταβλητές:
def exp(base, power):
return base ** power
Θέλουμε να τη χρησιμοποιήσουμε για να δημιουργήσουμε μια συνάρτηση που δέχεται μία μεταβλητή και επιστρέφει το αποτέλεσμα της συνάρτησης δύναμης exp(2, power), όπου η βάση είναι 2.
Φυσικά, θα μπορούσαμε να ορίσουμε μια νέα συνάρτηση με def, αν και αυτό δεν φαίνεται πολύ έξυπνο:
def two_to_the(power):
return exp(2, power)
Μια πιο έξυπνη προσέγγιση είναι να χρησιμοποιήσουμε τη μέθοδο functools.partial:
from functools import partial
two_to_the = partial(exp, 2) # Αυτή η συνάρτηση έχει τώρα μόνο μία μεταβλητή
print two_to_the(3) # 8
Αν έχουν οριστεί ονόματα, μπορείτε επίσης να χρησιμοποιήσετε τη μέθοδο partial για να συμπληρώσετε άλλες παραμέτρους:
square_of = partial(exp, power=2)
print square_of(3) # 9
Αν προσπαθήσετε να χρησιμοποιήσετε ακατάλληλα τις παραμέτρους στο μέσο μιας συνάρτησης, το πρόγραμμα θα γίνει γρήγορα χαοτικό, οπότε προσπαθήστε να αποφύγετε αυτή τη συμπεριφορά.
Χαρτογράφηση (map)
Περιστασιακά χρησιμοποιούμε επίσης συναρτήσεις όπως map, reduce και filter ως εναλλακτικές λύσεις για τη λειτουργικότητα των κατανοήσεων λιστών:
def double(x):
return 2 * x
xs = [1, 2, 3, 4]
twice_xs = [double(x) for x in xs] # [2, 4, 6, 8]
twice_xs = map(double, xs) # Το ίδιο με παραπάνω
list_doubler = partial(map, double) # Η συνάρτηση διπλασιάζει τη λίστα
twice_xs = list_doubler(xs) # Επίσης [2, 4, 6, 8]
Η μέθοδος map μπορεί επίσης να χρησιμοποιηθεί για τη χαρτογράφηση συναρτήσεων με πολλαπλές παραμέτρους σε πολλαπλές λίστες:
def multiply(x, y): return x * y
products = map(multiply, [1, 2], [4, 5]) # [1 * 4, 2 * 5] = [4, 10]
Φιλτράρισμα (filter)
Παρόμοια, η λειτουργία του filter αντιστοιχεί στη λειτουργία if στις κατανοήσεις λιστών:
def is_even(x):
"""Επιστρέφει True αν το x είναι άρτιος, False αν είναι περιττός"""
return x % 2 == 0
x_evens = [x for x in xs if is_even(x)] # [2, 4]
x_evens = filter(is_even, xs) # Το ίδιο με παραπάνω
list_evener = partial(filter, is_even) # Αυτή η συνάρτηση υλοποιεί τη λειτουργία φιλτραρίσματος
x_evens = list_evener(xs) # Επίσης [2, 4]
Αναγωγή (reduce)
Η μέθοδος reduce συνεχώς συγχωνεύει το πρώτο και το δεύτερο στοιχείο μιας λίστας, στη συνέχεια συγχωνεύει το αποτέλεσμα με το τρίτο στοιχείο, και επαναλαμβάνει αυτή τη διαδικασία μέχρι να ληφθεί ένα μοναδικό αποτέλεσμα:
x_product = reduce(multiply, xs) # = 1 * 2 * 3 * 4 = 24
list_product = partial(reduce, multiply) # Αυτή η συνάρτηση πραγματοποιεί την αναγωγή μιας λίστας
x_product = list_product(xs) # Επίσης 24
Αρίθμηση (enumerate)
Περιστασιακά, προκύπτουν περιπτώσεις όπου κατά την επανάληψη μιας λίστας, θέλουμε να χρησιμοποιήσουμε ταυτόχρονα το στοιχείο και τον δείκτη του:
# Όχι και τόσο 'Pythonic' (όχι τόσο συνοπτικό ή κομψό)
for i in range(len(documents)):
document = documents[i]
do_something(i, document)
# Ομοίως όχι και τόσο 'Pythonic' (όχι τόσο συνοπτικό ή κομψό)
i = 0
for document in documents:
do_something(i, document)
i += 1
Ο πιο συνοπτικός τρόπος είναι να χρησιμοποιήσετε τη μέθοδο enumerate για να δημιουργήσετε ένα tuple (index, element):
for i, document in enumerate(documents):
do_something(i, document)
Παρόμοια, αν θέλετε να χρησιμοποιήσετε μόνο τον δείκτη:
for i in range(len(documents)): do_something(i) # Όχι συνοπτικό
for i, _ in enumerate(documents): do_something(i) # Συνοπτικό
Θα χρησιμοποιούμε συχνά αυτή τη μέθοδο στο μέλλον.
Συμπίεση και Αποσυμπίεση Ορισμάτων (zip και Argument Unpacking)
Συμπίεση (zip)
Συχνά συμπιέζουμε δύο ή περισσότερες λίστες. Η συμπίεση μετατρέπει ουσιαστικά πολλαπλές λίστες σε μία ενιαία λίστα με αντίστοιχα tuples:
list1 = ['a', 'b', 'c']
list2 = [1, 2, 3]
zip(list1, list2) # Δίνει [('a', 1), ('b', 2), ('c', 3)]
Αποσυμπίεση Ορισμάτων (Argument Unpacking)
Εάν οι λίστες έχουν διαφορετικό μήκος, η διαδικασία συμπίεσης θα σταματήσει στο τέλος της συντομότερης λίστας. Μπορείτε επίσης να χρησιμοποιήσετε ένα ‘περίεργο’ κόλπο ‘αποσυμπίεσης’ για να αποσυμπιέσετε μια λίστα:
pairs = [('a', 1), ('b', 2), ('c', 3)]
letters, numbers = zip(*pairs)
Εδώ, ο αστερίσκος χρησιμοποιείται για την αποσυμπίεση ορισμάτων, χρησιμοποιώντας τα στοιχεία του pairs ως μεμονωμένα ορίσματα για το zip. Η παρακάτω κλήση έχει το ίδιο αποτέλεσμα:
zip(('a', 1), ('b', 2), ('c', 3)) # Επιστρέφει [('a','b','c'), ('1','2','3')]
Η αποσυμπίεση ορισμάτων μπορεί επίσης να χρησιμοποιηθεί σε συνδυασμό με άλλες συναρτήσεις:
def add(a, b): return a + b
add(1, 2) # Επιστρέφει 3
add([1, 2]) # Θα προκαλέσει σφάλμα
add(*[1, 2]) # Επιστρέφει 3
Αν και όχι πάντα πρακτικό, είναι ένα καλό κόλπο για να κάνετε τον κώδικα πιο συνοπτικό.
Μεταβλητός Αριθμός Ορισμάτων (args και kwargs)
Ας υποθέσουμε ότι θέλουμε να δημιουργήσουμε μια συνάρτηση ανώτερης τάξης, η οποία δέχεται μια παλιά συνάρτηση ως είσοδο και επιστρέφει μια νέα συνάρτηση που είναι η παλιά συνάρτηση πολλαπλασιασμένη επί 2:
def doubler(f):
def g(x):
return 2 * f(x)
return g
Παράδειγμα εκτέλεσης:
def f1(x):
return x + 1
g = doubler(f1)
print g(3) # 8 (== ( 3 + 1) * 2)
print g(-1) # 0 (== (-1 + 1) * 2)
Ωστόσο, αυτή η μέθοδος δεν λειτουργεί καλά αν ο αριθμός των ορισμάτων που περνιούνται είναι μεγαλύτερος από ένα:
def f2(x, y):
return x + y
g = doubler(f2)
print g(1, 2) # Σφάλμα TypeError: g() takes exactly 1 argument (2 given)
Επομένως, πρέπει να ορίσουμε μια συνάρτηση που να μπορεί να δέχεται οποιονδήποτε αριθμό ορισμάτων, και στη συνέχεια να χρησιμοποιήσουμε την αποσυμπίεση ορισμάτων για να περάσουμε πολλαπλά ορίσματα. Αυτό μπορεί να φανεί λίγο μαγικό:
def magic(*args, **kwargs):
print "unnamed args:", args
print "keyword args:", kwargs
magic(1, 2, key="word", key2="word2")
# Αποτελέσματα εξόδου:
# unnamed args: (1, 2)
# keyword args: {'key2': 'word2', 'key': 'word'}
Όταν ορίζουμε μια συνάρτηση με αυτόν τον τρόπο, το args (συντομογραφία του ‘arguments’) είναι ένα tuple που περιέχει τα ανώνυμα ορίσματα, ενώ το kwargs (συντομογραφία του ‘keyword arguments’) είναι ένα λεξικό που περιέχει τα ορισμένα με όνομα ορίσματα.
Μπορούν επίσης να χρησιμοποιηθούν σε περιπτώσεις όπου τα ορίσματα που περνιούνται είναι λίστες (ή tuples) ή πίνακες:
def other_way_magic(x, y, z):
return x + y + z
x_y_list = [1, 2]
z_dict = { "z" : 3 }
print other_way_magic(*x_y_list, **z_dict) # 6
Μπορείτε να το χρησιμοποιήσετε με διάφορους ‘περίεργους’ τρόπους, αλλά εμείς θα το χρησιμοποιήσουμε μόνο για να λύσουμε το πρόβλημα της μετάδοσης μεταβλητού αριθμού ορισμάτων σε συναρτήσεις ανώτερης τάξης:
def doubler_correct(f):
"""Λειτουργεί αποτελεσματικά ανεξάρτητα από το τι είναι το f"""
def g(*args, **kwargs):
"""Ανεξάρτητα από τον αριθμό των παραμέτρων, αυτή η συνάρτηση μπορεί να τις περάσει σωστά στο f"""
return 2 * f(*args, **kwargs)
return g
g = doubler_correct(f2)
print g(1, 2) # 6
Καλώς ήρθατε στον κόσμο της επιστήμης δεδομένων!
Ντινγκ! Συγχαρητήρια, μόλις ανοίξατε την πόρτα σε έναν νέο κόσμο! Τώρα μπορείτε να διασκεδάσετε εξερευνώντας!
Σχετικά Αναγνώσματα:
Συνήθεις Συντακτικές Δομές Python στην Επιστήμη Δεδομένων (Βασικό Επίπεδο)