קטגוריה: Python

פיתון מול רובי, לא מה שחשבתם

הקדמה

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

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

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

ואז אני רואה שאלות (הדגמה בלבד: 1, 2, 3, 4) שונות על פיתון (כי הרי דוחפים אותה לכל שיחה בערך), ואני רואה איפה למעשה אנשים מסתבכים איתה, ואני מנסה להשוות את זה לרובי, ואני יודע שבמקומות האלו לא ממש יפלו לי שם עם רובי (וזה לא בגלל שאין מקומות ליפול בהם ברובי), והייתי רוצה להסביר גם למה.

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

שאלה ראשונה

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

a = 10.times.select{ |x| x % 2 == 0 }
a
=> [0, 2, 4, 6, 8]

זהו, זה כל מה שצריך, שורה אחת 🙂
אסביר אותה בקצרה. ברובי כל דבר הוא אובייקט, אין כזה דבר משהו שהוא "פרימיטיבי" (בהשוואה לג'אווה למשל).
מספר שלם "רגיל", הוא אובייקט מסוג Fixnum. יש לו מתודה אשר מקבלת ירושה ממחלקת האב – Integer בשם times, אשר מבצעת כמות הפעמים של המספר המבוקש איטרציה.
במידה ולא יוצרים block או proc בשביל לרוץ כמניית האיטרציה, חוזרת לנו מחלקה מסוג Enumerator אשר היא מחלקת בסיס לדברים בעלי יכולת מנייה (כדוגמת מערכים). במחלקת ה Enumerator ישנה מתודה בשם select, המאפשרת להגדיר אילו ערכים באיטרציה כלשהי יחזרו אלי כמערך, כאשר אני יצרתי כאן למעשה proc עם הוראות.
היות וברובי השורה האחרונה בהכרח תמיד חוזרת (אם אין explicit return), אז הוא יודע שכאשר יש מספר זוגי, אז אני רוצה שזה הערך אשר יחזור אלי.
עכשיו הנה משהו מעניין על רובי תוך כדי ההסבר – אין ברובי אופרטורים. כל מה שנראה כאופרטור (כדוגמת חילוק השארית ופעולת השוויון), הם למעשה מתודות, ולכן מתודה כדוגמת even?‎ זהה למה שהייתי צריך, ויתר מזה, אין צורך בסוגריים, ולכן זה יכול גם להראות כך:

a = 10.times.select{ |x| x.even? }
a
=> [0, 2, 4, 6, 8]

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

שאלה ראשונה – תת שאלה

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

a = []
10.times do |i|
  a.push(i) if i.even?
  puts a.inspect
end

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

ובנוסף, לרובי גם יש תמיכה בשני תחבירים של משפט טרינארי אשר שם נפל השואל:

i = 1
a = if i.even? then 't' else 'f' end
b = i.even? ? 't' : 'f'

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

c = unless i.even? then 'f' else 't' end

שאלה שניה

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

a = [1, 2, ['a', 'b', [0xa, 0xb] ]]
=> [1, 2, ["a", "b", [10, 11]]]
a.flatten
=> [1, 2, "a", "b", 10, 11]

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

כפי שכתבתי בתשובה לשאלה הראשונה, מערך ברובי, יורש מ Enumerable, אבל יש לו גם הרבה מתודות משל עצמו.
מתודה אחת כזו היא flatten, אשר עושה בדיוק את מה שאותו בחור ביקש.
המתודה יכולה להגיע גם בעוד צורה בשם flatten!‎ אשר התפקיד שלה הוא זהה, עם שינוי קטן אחד – היא לא מחזירה מערך חדש בלי לגעת בקיים (כפי ש flatten עושה), אלא היא משנה את המערך הקיים וחוזרת עם אובייקט המערך עצמו.

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

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

שאלה שלישית

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

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

str1 = 'yoyo'
print 'sdfsdfsfsdfsfsdf' if str1.eql?('aaaaa') || str1 == 'yopo' || str1 === 'yoyo'

ישנם כאמור עוד דרכים לביצוע השוואת מחרוזות, אבל אלו מספיקות, לא?
על רגל אחת (בלי באמת למצות את ההסבר עד הסוף שלדעתי דורש פוסט שלם רק על זה):
המתודה (בדיקה) הראשונה של eql?‎ בודקת האם האורך והתוכן זהים.
המתודה (הבדיקה) השנייה של ==, בודקת האם זה תוכן זה הוא מסוג מחרוזת, ובמידה וכן, בודקת אורך ורק אז את התוכן.
המתודה (הבדיקה) השלישית של ===, בודקת גם היא את סוג המחלקה, ובמידה ושניהם מחרוזות אז מבצעים פעולה הזהה ל==, במידה ולא, תהה בדיקה האם יש מכנה משוטף בניהם הניתן להשוואה באמצעות פעולת case.

שאלה רביעית

בשאלה הרביעית, רצו להוריד קובץ מאתר מאובטח.
הנה משהו פשוט שמוריד תמונה מגוגל בגישה מאובטחת:

require 'open-uri'

image = open('https://www.google.com/images/srpr/logo11w.png')
open('/tmp/image.png', 'wb+') { |a| a.write(image.read) }
image.close

קצת פירקתי את הדברים לקריאות.
אני טוען את הספרייה open-uri, אשר תפקידה הוא לדעת לפתוח קישורים של ftp, http ו https כאילו היו file descriptor של רובי.

אני במקרה הזה ניגש לקובץ תמונה של גוגל, ושומר אותו כקובץ בינארי בספריית /tmp/ .
שימו לב כי אינני סוגר את הקובץ תמונה שאני שומר בדיסק, והוא נסגר לבד, בזכות פעולת ה proc, אשר בעצם מתבצעת "מאחורי הקלעים" באמצעות yield. וכאשר פקודה זו מסתיימת, הפונקציה של open סוגרת את עצמה לבד.

סיכום

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

recursive tail

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

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

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

אבל יש בעיה עם רקורסיות – כל פעם כאשר קוראים לפונקציה, נוסף stack frame חדש לקריאה הזו.
כלומר יש לי מספר עותקים של הפונקציה באוויר עם זיכרון במיוחד עבורה.

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

הגישה המוכרת ביותר לכך נקראת tail recursion. או רקורסיית זנב בעברית טכנית.

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

מה הכוונה? אשתמש בקוד רובי (שהוא לדעתי קריא מאוד) לשם כך.

רקורסיה בגישה ה"רגילה" תהיה כתובה כך:

def recursive(n)
  if n <= 1
    1
   else
     n * recursive(n - 1)
   end
end

recursive(4)
=> 24

רקורסיית זנב, תראה כך:

def tail_recursive(current, n, result)
  return result if current <= n

  new_result = result * current
  tail_recursive(current + 1, n, new_result)
end

tail_recursive(1, 4, 1)
=> 24

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

אבל לא הכל ורוד בזה. קשה יותר לדבג בעיות ברקורסיית זנב, היות ולמעשה אין לנו stack frames רבים, שיהיה ניתן להבין באיזה שלב יש בעיה. ובכך אין לנו stack trace שיסייע לנו בנושא.

יותר מזה, בשפה כמו רובי (ופיתון, אשר לא מכילה רקורסיית זנב – מבחירה), קל יותר ואף עדיף דווקא להשתמש בלולאות במקרה הזה, מאשר ברקורסיה.
יותר מזה, יש מגבלות שונות לכמות הקריאות שניתן לבצע לפונקציה זו או אחרת, בעוד שקל יותר לבצע לולאות על מידע גדול יותר.

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

My first python application

It's that time of the thing again – I'm required to learn new technology due to a project requirement.
The requirement is to write something using django, but I do not know django or Python.

So I've started my first project using the Python language, just to have a feel for it.

My first project is to convert my (in)famous quote file into a database, so I choose Firebird db for the task, using Python driver named fdb. It is very robust open source RDBMS, that many people over look, and choose unreliable RDBMS known as MySQL/MariaDB instead (but why ?!).
Here is the DDL of my database, it's simple as you can see, but has so much abilities 🙂

My code is not perfect, and require a lot more optimization, and polishing, but I'm getting there using the help of my friend – Meir, who loves the idea that a Ruby dev like myself arriving into the python world 🙂

WAT I have learned about …

בשבוע האחרון יצא לי לעשות הרבה דברים שונים.

למשל לכתוב קצת קוד פיתון, ולגלות כי Tuple זו הגדרה מוזרה במקצת:

a = (1)  # an Integer
a = (1,) # a Tuple
a = 1    # an integer
a = 1,   # a Tuple

על זה נאמר WAT ?

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

case a
 when 1 : something # 1.9.3 raises syntax error
 when 1
          something # Both 1.8.7 and 1.9.3 accept it
end

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

Playing with web frameworks

Foreword

I'm researching several web frameworks in order to create a web application that I need to, that also should work as a web site on the Internet, and will have API to integrate things into other web-sites, if clients does not want to use that site.

For the task I started with two frameworks:

  1. fpWeb – aka fcl-web
  2. django

I do not want to try Rails, because I find the logic behind it very disturbing. The Rails framework rewrite the language, rather then adding libraries to use. I think that it is not the proper way to write libraries. So I decided not to try it at all, even though I really love the Ruby language itself.

fpWeb

So I started testing fpWeb, and I must say that it takes a while to get used to it and the way it does things, but once I figured out the basics, I find it really enjoyable to write a CGI (I always hate to write html/css stuff though, and that's regardless of the language for the server side) application.

fpWeb works in very different mindset then most modern web frameworks I have ever worked with so far. On one hand it appears that everything chosen for you, that is the template engine, session engine etc.. But  on the other hand, you can actually use what ever you want, just decide on something and go for it. להמשיך לקרוא

אבולוציה של שפת תכנות

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

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

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

כידוע אני מתכנת מקצועי בשפת פסקל מונחת עצמים. כלומר פיזית הרווחתי כסף (וארוויח בעתיד) מתכנות בשפה. רגע זה לא נשמע טוב אשנה את המשפט: תכנתתי בצורה מקצועית בסביבת הפיתוח Delphi והרווחתי כסף מפיתוח בסביבה הזו. וואוו אם מכבסים מילים (כלומר לא יודעים את שם השפה) אז זה נשמע טוב יותר לא ?

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

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

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

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

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

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

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

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

0.1+0.2 = ?

חידה קטנה: אם אני עושה בשפת מחשב (כולל מסדי נתונים) את החישוב הבא:

0.1+0.2

מה תהיה התוצאה ?

התשובה היא שזה תלוי.

במה זה תלוי ? ובכן זה תלוי במהדר/מפרש, וזה תלוי בסטנדרט שאותו מפרש/מהדר משתמש בו.

למעשה השפה היחידה עם 2 מהדרים שניסיתי שנתנה את התשובה 0.3 היתה פסקל על FPC (אין לי דלפי לנסות עליו).

מבחינת מסדי נתונים כל מה שניסיתי (שזה Firebird, MySQL, PosgreSQL) כולם נתנו תשובה זהה לFPC.

שאר השפות החליטו שמדובר בתוצאה של 0.30000000000000004

מהם שאר השפות ?

ובכן

C (gcc), Perl, Ruby, Python, Javascrupt (Firefox), PHP

מי צודק בתשובה שלו ? ובכן כולם, או יותר נכון תלוי איך כל אחד מממש את העבודה עם נקודה צפה ומה אתם מצפים שתהיה התשובה…

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

הבנתי סוף כל סוף

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

הרבה זמן שאני מנסה למצוא הסבר למה אני לא מתחבר לשפת פיתון, וכל פעם יש לי איזה תירוץ אחר, אבל אני לא באמת מצליח לשים את היד ולהסביר אפילו לעצמי למה אני לא מתחבר לשפה הזו.

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

אפשר לסכם את הדברים באמצעות כתיבת השורה

import self

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