Εάν έχετε προγραμματίσει για Python (αντικειμενοστραφής προγραμματισμός) για κάποιο χρονικό διάστημα, τότε σίγουρα έχετε συναντήσει μεθόδους που έχουν self
ως την πρώτη τους παράμετρο.
Ας προσπαθήσουμε πρώτα να καταλάβουμε τι είναι αυτή η επαναλαμβανόμενη παράμετρος.
Τι είναι ο εαυτός στο Python;
Σε αντικειμενοστρεφή προγραμματισμό, όποτε ορίζουμε μεθόδους για μια τάξη, χρησιμοποιούμε self
ως την πρώτη παράμετρο σε κάθε περίπτωση. Ας δούμε τον ορισμό μιας τάξης που ονομάζεται Cat
.
class Cat: def __init__(self, name, age): self.name = name self.age = age def info(self): print(f"I am a cat. My name is (self.name). I am (self.age) years old.") def make_sound(self): print("Meow")
Σε αυτήν την περίπτωση όλες οι μέθοδοι, συμπεριλαμβανομένων __init__
, έχουν την πρώτη παράμετρο ως self
.
Γνωρίζουμε ότι η τάξη είναι ένα σχεδιάγραμμα για τα αντικείμενα. Αυτό το σχεδιάγραμμα μπορεί να χρησιμοποιηθεί για τη δημιουργία πολλαπλών αριθμών αντικειμένων. Ας δημιουργήσουμε δύο διαφορετικά αντικείμενα από την παραπάνω τάξη.
cat1 = Cat('Andy', 2) cat2 = Cat('Phoebe', 3)
Η self
λέξη-κλειδί χρησιμοποιείται για να αντιπροσωπεύει μια παρουσία (αντικείμενο) της συγκεκριμένης κλάσης. Σε αυτήν την περίπτωση, τα δύο Cat
αντικείμενα cat1
και cat2
έχουν τα δικά τους name
και τα age
χαρακτηριστικά τους. Εάν δεν υπήρχε αυτόματο επιχείρημα, η ίδια τάξη δεν θα μπορούσε να κρατήσει τις πληροφορίες και για τα δύο αυτά αντικείμενα.
Ωστόσο, δεδομένου ότι η τάξη είναι απλώς ένα σχεδιάγραμμα, self
επιτρέπει την πρόσβαση στα χαρακτηριστικά και τις μεθόδους κάθε αντικειμένου στο python. Αυτό επιτρέπει σε κάθε αντικείμενο να έχει τα δικά του χαρακτηριστικά και μεθόδους. Έτσι, ακόμα και πολύ πριν από τη δημιουργία αυτών των αντικειμένων, αναφερόμαστε στα αντικείμενα ως self
προσδιορίζοντας την κλάση.
Γιατί ο εαυτός ορίζεται ρητά κάθε φορά;
Ακόμα και όταν καταλαβαίνουμε τη χρήση του self
, μπορεί να φαίνεται περίεργο, ειδικά για προγραμματιστές που προέρχονται από άλλες γλώσσες, που self
περνά ως παράμετρος ρητά κάθε φορά που ορίζουμε μια μέθοδο. Καθώς πηγαίνει το Zen of Python , "Το ρητό είναι καλύτερο από το σιωπηρό ".
Λοιπόν, γιατί πρέπει να το κάνουμε αυτό; Ας πάρουμε ένα απλό παράδειγμα για να ξεκινήσουμε. Έχουμε μια Point
τάξη που καθορίζει μια μέθοδο distance
για τον υπολογισμό της απόστασης από την προέλευση.
class Point(object): def __init__(self,x = 0,y = 0): self.x = x self.y = y def distance(self): """Find distance from origin""" return (self.x**2 + self.y**2) ** 0.5
Ας δημιουργήσουμε τώρα αυτήν την τάξη και βρούμε την απόσταση.
>>> p1 = Point(6,8) >>> p1.distance() 10.0
Στο παραπάνω παράδειγμα, __init__()
ορίζονται τρεις παράμετροι, αλλά μόλις περάσαμε δύο (6 και 8). Παρομοίως distance()
απαιτεί ένα, αλλά περάστηκαν μηδενικά ορίσματα. Γιατί η Python δεν διαμαρτύρεται για αυτήν την αναντιστοιχία αριθμού επιχειρήματος;
Τι συμβαίνει εσωτερικά;
Point.distance
και p1.distance
στο παραπάνω παράδειγμα είναι διαφορετικά και δεν είναι ακριβώς τα ίδια.
>>> type(Point.distance) >>> type(p1.distance)
Μπορούμε να δούμε ότι το πρώτο είναι μια συνάρτηση και το δεύτερο είναι μια μέθοδος. Ένα ιδιαίτερο πράγμα σχετικά με τις μεθόδους (σε Python) είναι ότι το ίδιο το αντικείμενο μεταφέρεται ως το πρώτο όρισμα στην αντίστοιχη συνάρτηση
Στην περίπτωση του παραπάνω παραδείγματος, η μέθοδος κλήσης p1.distance()
είναι στην πραγματικότητα ισοδύναμη με Point.distance(p1)
.
Γενικά, όταν καλούμε μια μέθοδο με κάποια ορίσματα, η αντίστοιχη συνάρτηση κλάσης καλείται τοποθετώντας το αντικείμενο της μεθόδου πριν από το πρώτο όρισμα. Έτσι, οτιδήποτε άλλο obj.meth(args)
γίνεται Class.meth(obj, args)
. Η διαδικασία κλήσης είναι αυτόματη ενώ η διαδικασία λήψης δεν είναι (ρητή).
Αυτός είναι ο λόγος για τον οποίο η πρώτη παράμετρος μιας συνάρτησης στην κλάση πρέπει να είναι το ίδιο το αντικείμενο. Η σύνταξη αυτής της παραμέτρου ως self
απλή σύμβαση. Δεν είναι λέξη-κλειδί και δεν έχει ιδιαίτερη σημασία στο Python. Θα μπορούσαμε να χρησιμοποιήσουμε άλλα ονόματα (όπως this
), αλλά είναι ιδιαίτερα αποθαρρυντικό. Η χρήση ονομάτων διαφορετικών από εκείνη την self
περιφρονούν οι περισσότεροι προγραμματιστές και υποβαθμίζει την αναγνωσιμότητα του κώδικα ( μετράται η αναγνωσιμότητα ).
Ο εαυτός μπορεί να αποφευχθεί
Μέχρι τώρα είστε σαφείς ότι το ίδιο το αντικείμενο (παρουσία) μεταδίδεται αυτόματα ως το πρώτο όρισμα. Αυτή η σιωπηρή συμπεριφορά μπορεί να αποφευχθεί κατά τη δημιουργία μιας στατικής μεθόδου. Εξετάστε το ακόλουθο απλό παράδειγμα:
class A(object): @staticmethod def stat_meth(): print("Look no self was passed")
Εδώ, @staticmethod
είναι ένας διακοσμητής λειτουργιών που κάνει stat_meth()
στατικό. Ας δημιουργήσουμε αυτήν την τάξη και καλέστε τη μέθοδο.
>>> a = A() >>> a.stat_meth() Look no self was passed
Από το παραπάνω παράδειγμα, μπορούμε να δούμε ότι η σιωπηρή συμπεριφορά της διέλευσης του αντικειμένου ως το πρώτο επιχείρημα αποφεύχθηκε κατά τη χρήση μιας στατικής μεθόδου. Συνολικά, οι στατικές μέθοδοι συμπεριφέρονται όπως οι απλές παλιές συναρτήσεις (Δεδομένου ότι όλα τα αντικείμενα μιας κλάσης μοιράζονται στατικές μεθόδους).
>>> type(A.stat_meth) >>> type(a.stat_meth)
Ο εαυτός είναι εδώ για να μείνει
Το ρητό self
δεν είναι μοναδικό για την Python. Αυτή η ιδέα δανείστηκε από το Modula-3 . Ακολουθεί μια περίπτωση χρήσης όπου γίνεται χρήσιμη.
Δεν υπάρχει ρητή μεταβλητή δήλωση στο Python. Αναδύονται στην πρώτη ανάθεση. Η χρήση του self
καθιστά ευκολότερη τη διάκριση μεταξύ χαρακτηριστικών παρουσίας (και μεθόδων) από τοπικές μεταβλητές.
Στο πρώτο παράδειγμα, το self.x είναι ένα χαρακτηριστικό παρουσία, ενώ το x είναι μια τοπική μεταβλητή. Δεν είναι τα ίδια και βρίσκονται σε διαφορετικούς χώρους ονομάτων.
Πολλοί πρότειναν να γίνουν μια λέξη-κλειδί στο Python, όπως this
στο C ++ και το Java. Αυτό θα εξαλείψει την περιττή χρήση του ρητού self
από την τυπική λίστα παραμέτρων σε μεθόδους.
Ενώ αυτή η ιδέα φαίνεται πολλά υποσχόμενη, δεν πρόκειται να συμβεί. Τουλάχιστον όχι στο εγγύς μέλλον. Ο κύριος λόγος είναι η συμβατότητα προς τα πίσω. Εδώ είναι ένα blog από τον ίδιο τον δημιουργό του Python που εξηγεί γιατί πρέπει να μείνει ο ρητός εαυτός.
Το __init __ () δεν είναι κατασκευαστής
Ένα σημαντικό συμπέρασμα που μπορεί να εξαχθεί από τις μέχρι τώρα πληροφορίες είναι ότι η __init__()
μέθοδος δεν είναι κατασκευαστής. Πολλοί αφελείς προγραμματιστές Python μπερδεύονται με αυτό αφού __init__()
καλείται όταν δημιουργούμε ένα αντικείμενο.
A closer inspection will reveal that the first parameter in __init__()
is the object itself (object already exists). The function __init__()
is called immediately after the object is created and is used to initialize it.
Technically speaking, a constructor is a method which creates the object itself. In Python, this method is __new__()
. A common signature of this method is:
__new__(cls, *args, **kwargs)
When __new__()
is called, the class itself is passed as the first argument automatically(cls
).
Again, like self, cls is just a naming convention. Furthermore, *args and **kwargs are used to take an arbitrary number of arguments during method calls in Python.
Some important things to remember when implementing __new__()
are:
__new__()
is always called before__init__()
.- First argument is the class itself which is passed implicitly.
- Always return a valid object from
__new__()
. Not mandatory, but its main use is to create and return an object.
Let's take a look at an example:
class Point(object): def __new__(cls,*args,**kwargs): print("From new") print(cls) print(args) print(kwargs) # create our object and return it obj = super().__new__(cls) return obj def __init__(self,x = 0,y = 0): print("From init") self.x = x self.y = y
Now, let's now instantiate it.
>>> p2 = Point(3,4) From new (3, 4) () From init
This example illustrates that __new__()
is called before __init__()
. We can also see that the parameter cls in __new__()
is the class itself (Point
). Finally, the object is created by calling the __new__()
method on object base class.
In Python, object
is the base class from which all other classes are derived. In the above example, we have done this using super().
Use __new__ or __init__?
You might have seen __init__()
very often but the use of __new__()
is rare. This is because most of the time you don't need to override it. Generally, __init__()
is used to initialize a newly created object while __new__()
is used to control the way an object is created.
We can also use __new__()
to initialize attributes of an object, but logically it should be inside __init__()
.
One practical use of __new__()
, however, could be to restrict the number of objects created from a class.
Suppose we wanted a class SqPoint
for creating instances to represent the four vertices of a square. We can inherit from our previous class Point
(the second example in this article) and use __new__()
to implement this restriction. Here is an example to restrict a class to have only four instances.
class SqPoint(Point): MAX_Inst = 4 Inst_created = 0 def __new__(cls,*args,**kwargs): if (cls.Inst_created>= cls.MAX_Inst): raise ValueError("Cannot create more objects") cls.Inst_created += 1 return super().__new__(cls)
Ένα δείγμα εκτέλεσης:
>>> p1 = SqPoint(0,0) >>> p2 = SqPoint(1,0) >>> p3 = SqPoint(1,1) >>> p4 = SqPoint(0,1) >>> >>> p5 = SqPoint(2,2) Traceback (most recent call last):… ValueError: Cannot create more objects