logo

מצביע בפייתון | מדוע Python לא תומך במצביע

במדריך זה, נלמד על מצביע ב-Python ונראה מדוע Python לא תומך במושגי מצביע.

נבין גם כיצד נוכל לדמות את המצביע ב-Python. להלן ההקדמה של המצביע למי שאין לו משהו על זה.

נבין גם כיצד נוכל לדמות את המצביע ב-Python. להלן הקדמה של מצביע למי שאין לו על זה.

מה זה פוינטר?

Pointer הוא כלי מאוד פופולרי ושימושי לאחסון הכתובת של המשתנה. אם מישהו אי פעם עבד עם שפה ברמה נמוכה כגון ג . C++ , הוא/היא בטח מכירים מצביעים. הוא מנהל את הקוד ביעילות רבה. זה יכול להיות מעט קשה למתחילים, אבל זה אחד מהקונספטים החשובים של התוכנית. עם זאת, זה יכול להוביל לבאגים שונים בניהול זיכרון. לפיכך, ההגדרה של מצביעים -

״מצביעים הם המשתנים שמחזיקים את כתובת הזיכרון של משתנה אחר. משתני מצביע מיוצגים בכוכבית (*).'

בוא נראה את הדוגמה הבאה של המצביע בשפת התכנות C.

דוגמה - כיצד להשתמש במצביע ב-C

 #include int main() { int* po, o; 0 = 10; printf('Address of c: %p
', &c); printf('Value of c: %d

', c); o = &0; printf('Address of pointer pc: %p
', o); printf('Content of pointer pc: %d

', *o); 0 = 11; printf('Address of pointer pc: %p
', p0); printf('Content of pointer pc: %d

', *p0); *po = 2; printf('Address of c: %p
', &o); printf('Value of c: %d

', o); return 0; } 

תְפוּקָה:

Address of o: 2686784 Value of o: 22 Address of pointer po: 2686784 Content of pointer po: 22 Address of pointer po: 2686784 Content of pointer po: 11 Address of o: 2686784 Value of o: 2 

מלבד היותם שימושיים, מצביעים אינם משמשים ב פִּיתוֹן . בנושא זה, נדון במודל האובייקטים של Python ונלמד מדוע מצביעים בפייתון אינם קיימים. נלמד גם דרכים שונות לדמות מצביעים ב-Python. ראשית, בואו נדון מדוע Python לא תומך ב-Pointers.

מדוע Python לא תומך במצביעים

הסיבה המדויקת לאי תמיכה במצביע אינה ברורה. האם מצביע ב-Python יכול להתקיים באופן מקורי? הרעיון העיקרי של Python הוא הפשטות שלו, אבל המצביע הפר את זן של פייתון. מצביעים מעודדים בעיקר שינויים מרומזים ולא מפורשים. הם גם מורכבים, במיוחד למתחילים.

מצביעים נוטים ליצור מורכבות בקוד, כאשר Python מתמקדת בעיקר בשימושיות ולא במהירות. כתוצאה מכך, Python אינו תומך במצביע. עם זאת, Python נותן כמה יתרונות בשימוש במצביע.

לפני שנבין את המצביע ב- Python, עלינו לקבל את הרעיון הבסיסי של הנקודות הבאות.

  • אובייקטים ניתנים לשינוי לעומת אובייקטים ניתנים לשינוי
  • משתנים/שמות של פייתון

אובייקטים בפייתון

ב-Python הכל הוא אובייקט, אפילו מחלקה, פונקציות, משתנים וכו'. כל אובייקט מכיל לפחות שלוש פיסות נתונים.

להחליף מחרוזת ב-Java מחרוזת
  • ספירת הפניות
  • סוּג
  • ערך

בואו נדון בזה אחר זה.

ספירת הפניות - הוא משמש לניהול זיכרון. כדי לקבל מידע נוסף על ניהול זיכרון Python, קרא ניהול זיכרון ב- Python.

סוג - ה CPython שכבה משמשת כסוג כדי לוודא בטיחות הסוג בזמן ריצה. לבסוף, יש ערך, שהוא הערך האמיתי המשויך לאובייקט.

אם נלך לעומק באובייקט הזה, נגלה שלא כל האובייקטים זהים. ההבחנה החשובה בין סוגי האובייקט היא בלתי ניתנת לשינוי. קודם כל, עלינו להבין את ההבדל בין סוגי האובייקטים מכיוון שהוא בוחן את המצביע ב- Python.

אובייקטים ניתנים לשינוי לעומת אובייקטים ניתנים לשינוי

לא ניתן לשנות אובייקטים בלתי ניתנים לשינוי, כאשר אובייקטים הניתנים לשינוי ניתנים לשינוי. בוא נראה את הטבלה הבאה של סוגים נפוצים והאם הם ניתנים לשינוי או לא.

חפצים סוּג
Int בלתי ניתן לשינוי
לָצוּף בלתי ניתן לשינוי
בול בלתי ניתן לשינוי
רשימה מִשְׁתַנֶה
מַעֲרֶכֶת מִשְׁתַנֶה
מורכב מִשְׁתַנֶה
טופל בלתי ניתן לשינוי
קפוא בלתי ניתן לשינוי
דיקט מִשְׁתַנֶה

אנו יכולים לבדוק את סוג האובייקטים לעיל באמצעות ה- תְעוּדַת זֶהוּת() שיטה. שיטה זו מחזירה את כתובת הזיכרון של האובייקט.

אנו מקלידים את השורות שלהלן בסביבת REPL.

 x = 5 id(x) 

תְפוּקָה:

140720979625920 

בקוד לעיל, הקצינו את הערך 10 ל-x. אם נשנה את הערך הזה עם החלפה, נקבל את האובייקטים החדשים.

 x-=1 id(x) 

תְפוּקָה:

140720979625888 

כפי שאנו יכולים לראות, אנו משנים את הקוד לעיל ומקבלים אובייקטים חדשים כתגובה. בואו ניקח דוגמה נוספת של str .

מערך אובייקטים ב-java
 s = 'java' print(id(s)) s += 'Tpoint' print(s) id(s) 

תְפוּקָה:

2315970974512 JavaTpoint 1977728175088 

שוב, אנו משנים את הערך של x על ידי הוספת מחרוזת חדשה, ונקבל את כתובת הזיכרון החדשה. בואו ננסה להוסיף מחרוזת ישירות ב-s.

 s = 'java' s[0] = T print(id(s)) 

תְפוּקָה:

Traceback (most recent call last): File 'C:/Users/DEVANSH SHARMA/PycharmProjects/MyPythonProject/python1.py', line 34, in s[0] = T NameError: name 'T' is not defined 

מעל הקוד מחזיר שגיאה, זה אומר שהמחרוזת לא תומכת במוטציה. כך str הוא האובייקטים הבלתי ניתנים לשינוי.

כעת, נראה את האובייקט שניתן לשינוי כגון רשימה.

 my_list = [3, 4, 8] print(id(my_list)) my_list.append(4) print(my_list) print(id(my_list)) 

תְפוּקָה:

2571132658944 [3, 4, 8, 4] 2571132658944 

כפי שאנו יכולים לראות בקוד לעיל, ה שלי_רשימה יש לו את המזהה במקור, וספחנו עם 5 לרשימה; שלי_רשימה יש את אותו מזהה מכיוון שהרשימה תומכת ב- השתנות.

הבנת משתני Python

הדרך להגדיר משתנים ב-Python שונה בהרבה מה-C או C++. משתנה Python אינו מגדיר את סוג הנתונים. למעשה, לפייתון יש שמות, לא משתנים.

אז אנחנו צריכים להבין את ההבדל בין משתנים לשמות ובמיוחד נכון כאשר אנחנו מנווטים בנושא המסובך של מצביעים ב-Python.

בואו נבין איך המשתנה עובד ב-C ואיך השם עובד ב-Python.

משתנים ב-C

בשפת C, משתנה הוא שהוא מחזיק ערך או ערך חנות. הוא מוגדר עם סוג הנתונים. בוא נראה את הקוד הבא שמגדיר את המשתנה.

 int x = 286; 
  • הקצו מספיק זיכרון למספר שלם.
  • אנו מקצים את הערך 286 למיקום הזיכרון הזה.
  • ה-x מייצג את הערך הזה.

אם נציג את השקפת הזיכרון -

מצביע בפייתון

כפי שאנו יכולים לראות, ל-x יש מיקום זיכרון עבור הערך 286. כעת, נקצה את הערך החדש ל-x.

x = 250

ערך חדש זה מחליף את הערך הקודם. זה אומר שהמשתנה x ניתן לשינוי.

מיקום הערך של x זהה, אבל הערך השתנה. זוהי נקודה משמעותית המציינת ש-x הוא מיקום הזיכרון, לא רק שמו.

כעת, אנו מציגים את המשתנה החדש שלוקח את ה-x, ואז ה-y ​​יוצר את תיבת הזיכרון החדשה.

 int y = x; 

המשתנה y יוצר תיבה חדשה בשם y מעתיק את הערך של מ-x לתוך התיבה.

מצביע בפייתון

שמות בפייתון

כפי שדיברנו קודם לכן לפייתון אין את המשתנים. יש לו שמות, ואנחנו משתמשים במונח הזה בתור המשתנים. אבל יש הבדל בין משתנים לשמות. בואו נראה את הדוגמה הבאה.

 x = 289 

הקוד לעיל מתפרק במהלך הביצוע.

  1. צור PyObject
  2. הגדר את קוד הסוג למספר שלם עבור PyObject
  3. הגדר את הערך ל-289 עבור PyObject
  4. צור שם בשם x
  5. הצבע על x ל- PyObject החדש
  6. הגדל את הספירה מחדש של PyObject ב-1

זה ייראה כמו למטה.

מצביע בפייתון

אנו יכולים להבין את העבודה הפנימית של משתנה ב- Python. המשתנה x מצביע על הפניה של האובייקט ואין לו את שטח הזיכרון כמו קודם. זה גם מראה ש-x = 289 מחייב את השם x להפניה.

כעת, אנו מציגים משתנה חדש ומקצים לו x.

 y = x 

ב-Python, המשתנה y לא יצור את האובייקט החדש; זה רק שם חדש שמצביע על אותו אובייקט. האובייקט ספירה מחדש גם גדל באחד. אנו יכולים לאשר זאת כדלקמן.

 y is x 

תְפוּקָה:

אורך מערך java
True 

אם נעלה את הערך של y באחד, הוא לא יתייחס עוד לאותו אובייקט.

 y + =1 y is x 

זה אומר שב-Python אנחנו לא מקצים משתנים. במקום זאת, אנו קושרים שמות להפניה.

הדמיית מצביעים בפייתון

כפי שדיברנו, Python לא תומך במצביע, אבל אנחנו יכולים לקבל את היתרונות של שימוש במצביע. Python מספק דרכים חלופיות להשתמש במצביע ב- Python. שתי דרכים אלו ניתנות להלן.

  • שימוש בסוגים הניתנים לשינוי כמצביעים
  • שימוש באובייקטי Python מותאמים אישית

בואו נבין את הנקודות הנתונות.

שימוש בסוגים משתנים כמצביע

בסעיף הקודם, הגדרנו את האובייקטים מסוג הניתנים לשינוי; אנו יכולים להתייחס אליהם כאילו היו מצביעים לדמות התנהגות מצביע. בואו נבין את הדוגמה הבאה.

ג

 void add_one(int *a) { *a += 1; } 

בקוד שלמעלה, הגדרנו מצביע *a, ואז נגדיל את הערך באחד. כעת, ניישם את זה עם הפונקציה main() .

עוֹמֵד
 #include int main(void) { int y = 233; printf('y = %d
', y); add_one(&y); printf('y = %d
', y); return 0; } 

תְפוּקָה:

y = 233 y = 234 

אנו יכולים לדמות סוג זה של התנהגות על ידי שימוש בסוג Python mutable. הבן את הדוגמה הבאה.

 def add_one(x): x[0] += 1 y = [2337] add_one(y) y[0] 

הפונקציה שלמעלה ניגשת לרכיב הראשון של הרשימה ומגדילה את ערכו באחד. כאשר אנו מבצעים את התוכנית לעיל, היא מדפיסה את הערך המשתנה של y. זה אומר שאנחנו יכולים לשכפל את המצביע באמצעות האובייקט הניתן לשינוי. אבל אם ננסה לדמות מצביע באמצעות אובייקט בלתי משתנה.

 z = (2337,) add_one(z) 

תְפוּקָה:

Traceback (most recent call last): File '', line 1, in File '', line 2, in add_one TypeError: 'tuple' object does not support item assignment 

השתמשנו ב-tuple בקוד לעיל, אובייקט בלתי ניתן לשינוי, אז הוא החזיר את השגיאה. אנחנו יכולים גם להשתמש במילון כדי לדמות את המצביע ב-Python.

בואו נבין את הדוגמה הבאה שבה נספור כל פעולה שמתרחשת בתוכנית. אנחנו יכולים להשתמש בדיקט כדי להשיג זאת.

דוגמא -

 count = {'funcCalls': 0} def car(): count['funcCalls'] += 1 def foo(): count['funCcalls'] += 1 car() foo() count['funcCalls'] 

תְפוּקָה:

2 

הסבר -

בדוגמה לעיל, השתמשנו ב- לספור מילון, שעקב אחר מספר קריאות הפונקציה. כאשר foo() הפונקציה נקראת, המונה מוגדל 2 כי dict ניתן לשינוי.

שימוש בחפצי Python

בדוגמה הקודמת, השתמשנו ב-dict כדי לחקות את המצביע ב-Python, אך לפעמים קשה לזכור את כל שמות המפתחות בשימוש. אנחנו יכולים להשתמש במחלקה המותאמת אישית של Python במקום המילון. בואו נבין את הדוגמה הבאה.

דוגמא -

 class Pointer(object): def __init__(self): self._metrics = { 'funCalls': 0, 'catPictures': 0, } 

בקוד לעיל, הגדרנו את מחלקה Pointer. מחלקה זו השתמשה ב-dict להחזקת נתונים בפועל במשתנה האיבר _metrics. זה יספק שינוי לתוכנית שלנו. אנחנו יכולים לעשות זאת באופן הבא.

 class Pointer(object): # ... @property def funCalls(self): return self._metrics['func_calls'] @property def catPictures_served(self): return self._metrics['cat_pictures_served'] 

השתמשנו @תכונה מְעַצֵב. אם אינך מכיר מעצבים, בקר בהדרכה שלנו לעיצוב Python. מעצב @property ייגש ל-funCalls ול-catPicture_served. כעת, ניצור אובייקט של המחלקה Pointer.

 pt = Pointer() pt.funCalls() pt.catPicture_served 

כאן אנחנו צריכים להגדיל את הערכים האלה.

 class Pointer(object): # ... def increament(self): self._metrices['funCalls'] += 1 def cat_pics(self): self._metrices['catPictures_served'] += 1 

הגדרנו שתי שיטות חדשות - increment(), ו-cat_pics(). שינינו את הערכים באמצעות פונקציות אלה ב-dict המטריצות. כאן, אנו יכולים לשנות את המחלקה באותה מידה שאנו משנים את המצביע.

 pt = Pointer() pt.increment() pt.increment() pt.funCalls() 

Python ctypes מודול

מודול ctypes של Python מאפשר לנו ליצור מצביע מסוג C ב-Python. מודול זה מועיל אם ברצוננו לבצע קריאת פונקציה לספריית C הדורשת מצביע. בואו נבין את הדוגמה הבאה.

דוגמה - C Language

 void incr_one(int *x) { *x += 1; } 

בפונקציה לעיל, הגדלנו את הערך של x באחד. נניח שנשמור את הקובץ לעיל בשם incrPointer.c ואת הפקודה הבאה בטרמינל.

 $ gcc -c -Wall -Werror -fpic incrPointer.c $ gcc -shared -o libinc.so incrPointer.o 

הפקודה הראשונה מבצעת קומפילציה incrPointer.c לתוך אובייקט שנקרא incrPointer.o. הפקודה השנייה מקבלת קובץ אובייקט ומפיקה libinic.so כדי לשתף פעולה עם ctypes.

listnode java
 import ctypes ## libinc.so library should be same directory as this program lib = ctypes.CDLL('./libinc.so') lib.increment 

תְפוּקָה:

 

בקוד לעיל, ה ctypes.CDLL מחזירה אובייקט משותף שנקרא libinic.so. הוא מכיל את incrPointer() פוּנקצִיָה. אם עלינו לציין את המצביע לפונקציות שאנו מגדירים באובייקט משותף, עלינו לציין זאת באמצעות ה-ctypes. בוא נראה את הדוגמה למטה.

 inc = lib.increment ## defining the argtypes inc.argtypes = [ctypes.POINTER(ctypes.c_int)] 

אם נקרא לפונקציה באמצעות סוג אחר, היא תעשה שגיאה.

 incrPointer(10) 

תְפוּקָה:

Traceback (most recent call last): File '', line 1, in ctypes.ArgumentError: argument 1: : expected LP_c_int instance instead of int 

הסיבה לכך היא שה-incrPointer דורש מצביע ו-ctypes היא דרך להעביר מצביע ב-Python.

 v = ctypes.c_int(10) 

v הוא משתנה C. ה-ctypes מספק את השיטה שנקראת byref() שנהג להעביר את הפניה למשתנה.

 inc(ctypes.byref(a)) a 

תְפוּקָה:

c_int(11) 

הגדלנו את הערך באמצעות משתנה הייחוס.

סיכום

דנו בכך שהמצביע אינו קיים ב-Python, אך אנו יכולים ליישם את אותה התנהגות עם האובייקט *ניתן לשינוי. דנו גם במודולי ctypes שיכולים להגדיר C pointer ב- Python. הגדרנו כמה דרכים מצוינות לדמות מצביע ב-Python.