ToCode
رفتن به کانال در Telegram
1 421
مشترکین
+324 ساعت
+17 روز
+230 روز
آرشیو پست ها
1 420
# שתי דרכים ללמוד
אפשרות אחת היא לנסות לבד, לחכות שתיתקעי ואז לחפש ברשת את הפיתרון הספציפי לבעיה עליה נתקעת. קצת קופי פייסטים ובדרך כלל הכל עובד בסוף.
אפשרות שניה היא לקחת שבועיים לקרוא את התיעוד מקצה לקצה ולנסות כל מה שמופיע שם.
בדרך הראשונה תצליחו לגרום לקוד לעבוד יותר מהר בדרך שאתם חושבים שהקוד צריך לעבוד. בדרך השניה תלמדו איך באמת הקוד צריך לעבוד. וכמובן אין שום בעיה (וזה אפילו עוזר) להתחיל עם הדרך הראשונה ולהמשיך לשניה כשיש קצת זמן וכשקצת מבינים מי נגד מי.
1 420
# כמה פונקציות ששווה להכיר מהמודולים os, sys
המודולים sys ו os מהספריה הסטנדרטית של פייתון כוללים המון פונקציות ומשתנים ואני חושב שאם אתם מתכננים להיכנס לעומק של פייתון צלילה בתיעוד שלהם היא הכרחית. עד שתעשו את זה הנה כמה פונקציות ומשתנים שאני אוהב במיוחד מתוכם.
## שימוש בפרמטרים משורת הפקודה עם sys.argv
המנגנון הראשון ששווה לדבר עליו הוא המשתנה sys.argv, ותפקידו לעזור לנו לכתוב סקריפטים שירוצו מתוך שורת הפקודה. לפני שתתחילו לשחק עם sys.argv שווה לוודא שפייתון מותקן אצלכם בצורה טובה ואתם יכולים להפעיל סקריפטים משורת הפקודה באופן הבא:
1. כנסו ל cmd (או בלינוקס או מק לתוכנת המסוף המתאימה).
2. כתבו שם
python --version.
3. התוצאה צריכה לכלול מספר גירסא של פייתון ולהיראות בערך כך:
Python 3.7.0
עכשיו שזה עבד נוכל לכתוב סקריפט שבהפעלה שלו משורת הפקודה יוכל להסתכל על הפרמטרים שהעברנו בהפעלה. במילים אחרות אנחנו נפעיל את הסקריפט עם:
$ python myscript.py one two three
ואז הסקריפט יוכל להסתכל על המילים one, two ו three ולהחליט מה לעשות לפי מילים אלה.
נתחיל עם סקריפט פשוט שרק מדפיס בחזרה את המילים שהוא קיבל משורת הפקודה:
import sys
print(sys.argv)
והתוצאה כשמפעילים את השורה שכתבתי קודם תהיה:
['myscript.py', 'one', 'two', 'three']
עכשיו אפשר להשתולל עם המערך sys.argv בשביל סקריפטים יותר מקוריים, למשל הסקריפט הבא שמדפיס לכם בחזרה את המילים שנתתם לו בסדר אקראי:
import sys
import random
words = sys.argv[1:]
random.shuffle(words)
print(' '.join(words))
אגב אם אתם אוהבים לכתוב סקריפטים משורת הפקודה שווה לבדוק גם את המודול argparse של פייתון שמציע הרבה יותר אפשרויות בהשוואה לפיענוח לבד של sys.argv.
## מידע על הסביבה
דברים נוספים שאפשר לקחת מ sys כוללים מידע על הסביבה, לדוגמא:
1. המשתנה sys.executable יחזיר לכם את הנתיב לתוכנית python שעכשיו מריצה את הסקריפט שלכם.
2. המשתנה sys.path יחזיר רשימה של כל התיקיות בהן פייתון מחפש מודולים.
3. המשתנה sys.version יחזיר את גירסת הפייתון שמריצה עכשיו את הסקריפט.
## צפיה במשתני סביבה עם os.environ
המודול os כולל הרבה יכולות הקשורות לאינטרקציה בין פייתון למערכת ההפעלה. בתור התחלה הוא יאפשר לנו לגשת למשתני הסביבה של המערכת באמצעות המשתנה os.environ. משתנה זה מתנהג כמו Dictionary ובאמצעות סוגריים מרובעים אנחנו מושכים ממנו ערך של משתנה סביבה. הנה כמה דוגמאות:
>>> import os
>>> print(os.environ['HOME'])
/Users/ynonperek
>>> print(os.environ['USER'])
ynonperek
והלולאה הבאה תדפיס בצורה יפה את כל משתני הסביבה במערכת:
import os
for name, value in os.environ.items():
print(f"{name:30}: {value}")
## צפיה במידע על מערכת ההפעלה עם os.uname
המשתנה os.uname יודע לספר לנו דברים על מערכת ההפעלה הנוכחית כדי שנוכל לכתוב תוכניות שמתנהגות אחרת לפי הסביבה בה הן רצות:
>>> os.uname().sysname
'Darwin'
>>> os.uname().version
'Darwin Kernel Version 18.7.0: Thu Jun 20 18:42:21 PDT 2019; root:xnu-4903.270.47~4/RELEASE_X86_64'1 420
# היום למדתי: הפונקציה gem ב ruby
לרובי יש מנגנון חבילות שנקרא gem שמאפשר התקנת חבילות מהרשת ושילובן בפרויקט בצורה די פשוטה. הבעיה היחידה עם הג'מים היא מה קורה כשיש לנו מספר פרויקטים שכל פרויקט צריך גירסא אחרת של אותו הג'ם? הפיתרון של רובי נקרא הפונקציה gem.
## בואו נתקין שתי גירסאות של אותו ג'ם
בשביל הדוגמא נתקין שתי גירסאות של ריילס באמצעות הפקודה gem:
$ gem install rails-6.0.0
$ gem install rails-5.2.3
שתי הגירסאות מותקנות לי עכשיו על המכונה ואני יכול לראות את זה באמצעות הפקודה gem info באופן הבא:
$ gem info rails
*** LOCAL GEMS ***
rails (6.0.0, 5.2.3)
Author: David Heinemeier Hansson
Homepage: https://rubyonrails.org
License: MIT
Installed at (6.0.0): /Users/ynonperek/.rvm/gems/ruby-2.6.3
(5.2.3): /Users/ynonperek/.rvm/gems/ruby-2.6.3
Full-stack web application framework.
אנחנו רואים ב info שאני משתמש ב rvm וששתי הג'מים מותקנות באותה תיקיית רובי. נסתכל בתיקיה כדי לראות את שתי הגירסאות:
$ $ ls -1 -d /Users/ynonperek/.rvm/gems/ruby-2.6.3/gems/*rails*
/Users/ynonperek/.rvm/gems/ruby-2.6.3/gems/coffee-rails-4.2.2
/Users/ynonperek/.rvm/gems/ruby-2.6.3/gems/rails-5.2.3
/Users/ynonperek/.rvm/gems/ruby-2.6.3/gems/rails-6.0.0
/Users/ynonperek/.rvm/gems/ruby-2.6.3/gems/rails-controller-testing-1.0.4
/Users/ynonperek/.rvm/gems/ruby-2.6.3/gems/rails-dom-testing-2.0.3
/Users/ynonperek/.rvm/gems/ruby-2.6.3/gems/rails-html-sanitizer-1.0.4
/Users/ynonperek/.rvm/gems/ruby-2.6.3/gems/rails-html-sanitizer-1.2.0
/Users/ynonperek/.rvm/gems/ruby-2.6.3/gems/sass-rails-5.0.7
/Users/ynonperek/.rvm/gems/ruby-2.6.3/gems/sass-rails-5.1.0
/Users/ynonperek/.rvm/gems/ruby-2.6.3/gems/sprockets-rails-3.2.1
## איך טוענים את הגירסא החדשה ביותר
באופן רגיל הפקודה require תטען את הגירסא החדשה ביותר של הג'ם שהיא מוצאת:
> require 'rails'
=> true
> Rails.version
=> "6.0.0"
## איך טוענים גירסא ישנה יותר
עם הפקודה gem אני יכול לטעון גירסאות ישנות יותר של הג'ם:
> gem 'rails', '5.2.3'
=> true
> require 'rails'
=> true
> Rails.version
=> "5.2.3"
## גם ריילס משתמש בטריק הזה
ומסתבר שגם ריילס עצמה משתמשת בטריק הזה. קובץ ההרצה rails שיוצר תבנית פרויקט כולל את הקוד הבא:
version = ">= 0.a"
str = ARGV.first
if str
str = str.b[/\A_(.*)_\z/, 1]
if str and Gem::Version.correct?(str)
version = str
ARGV.shift
end
end
gem "railties", version
ריילס יסתכל בשורת הפקודה וינסה לחפש מספר גירסא שהעברתם לו כפרמטר. אם הוא מצא הוא ישתמש בגירסא שהעברתם. בצורה כזו אפשר לעבוד עם כמה גרסאות של ריילס שמותקנות על המכונה ובאותו ה gemset. לדוגמא מתוך שורת הפקודה אני יכול לכתוב:
$ rails _5.2.3_ --version
Rails 5.2.3
$ rails _6.0.0_ --version
Rails 6.0.0
## באנדלר
כל מה שנכתב כאן לא בא להחליף את Bundler. באנדלר מעולה ואני ממליץ לעבוד איתו. מאחר וריילס עובד כברירת מחדל עם באנדלר, ומאחר שבאנדלר הוא חלק מרובי (החל מגירסא 2.6) סיכוי טוב שכל פרויקט רובי חדש שתגיעו אליו כבר משתמש ב Bundler.
ולמרות זאת אני חושב שמעניין להכיר איך המנגנונים האלה עובדים מאחורי הקלעים, ומסתבר (כמו שלמדתי היום) שלפעמים בן אדם מוצא את עצמו מחוץ לבאנדלר וצריך למצוא פיתרון.1 420
# משתנים ופונקציות גלובאליים ברובי
רובי מבדילה בין משתנים שהם מקומיים לקובץ לבין פונקציות שהן גלובאליות ואפשר להפעיל אותן גם מקבצים אחרים - אבל האופן בו הבדל זה מיושם הוא מעניין ושונה משפות רבות אחרות שפגשתי. בואו ניקח טיול קצר ברובי כדי לראות הבדל זה.
## הגדרת משתנים ופונקציות באותו קובץ
נגדיר קובץ בשם utils.rb עם התוכן הבא:
def twice(x)
x * 2
end
value = 10
יש לנו פונקציה ומשתנה פרטי. עכשיו אפשר לעבור ל irb כדי לראות את ההבדל ביניהם כשמנסים לטעון את הקובץ ממקום אחר.
## איך פונקציות ומשתנים נראים מבחוץ
נתחיל עם המשתנה הפרטי - טעינת הקובץ וניסיון לגשת למשתנה נכשלת:
> require './utils.rb'
> value
Traceback (most recent call last):
4: from /Users/ynonperek/.rvm/rubies/ruby-2.6.3/bin/irb:23:in `<main>'
3: from /Users/ynonperek/.rvm/rubies/ruby-2.6.3/bin/irb:23:in `load'
2: from /Users/ynonperek/.rvm/rubies/ruby-2.6.3/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
1: from (irb):2
NameError (undefined local variable or method `value' for main:Object)
זה הגיוני אבל ההודעה קצת מפתיעה: לא מוגדר משתנה מקומי בשם value וגם לא מוגדרת מתודה על Object בשם זה. החלק השני רומז להתנהגות כשניגש לפונקציה.
> require './utils.rb'
> twice(10)
=> 20
וזה הצליח! אבל רגע, מה לגבי ההודעה ההיא עם המתודה של Object? נסתכל רגע על האוביקט:
> Object.private_instance_methods
=> [:DelegateClass, :twice, :irb_binding, :sprintf, :format, :Integer, :Float, :String, :Array, :Hash, :srand, :warn, :local_variables, :trap, :abort, :require, :rand, :raise, :fail, :global_variables, :__method__, :__callee__, :__dir__, :binding, :require_relative, :autoload, :autoload?, :eval, :iterator?, :block_given?, :catch, :throw, :loop, :system, :spawn, :exec, :exit!, :trace_var, :untrace_var, :at_exit, :select, :`, :Rational, :gem_original_require, :Complex, :set_trace_func, :pp, :gem, :respond_to_missing?, :test, :caller_locations, :caller, :fork, :exit, :gets, :proc, :lambda, :initialize_copy, :initialize_clone, :initialize_dup, :URI, :sleep, :load, :syscall, :open, :printf, :print, :putc, :puts, :readline, :readlines, :p, :singleton_method_added, :method_missing, :singleton_method_removed, :singleton_method_undefined, :initialize]
וכבר הפונקציה השניה ברשימה נראית מוכרת! זו twice שלנו. הפונקציה הגלובלית היא בעצם מתודה פרטית של האוביקט Object. תופעת לוואי מעניינת של מבנה זה היא שעכשיו אפשר להפעיל פונקציה זו דרך כל אוביקט רובי. הנה המשך העבודה מ irb:
> l = [10, 20, 30]
=> [10, 20, 30]
> l.send(:twice, 10)
=> 20
חיפוש שם של משתנה או פונקציה ברובי מתחיל בחיפוש משתנה מקומי בשם שכתבנו וממשיך בחיפוש מתודה בשם זה בתוך ה Scope הנוכחי, ומשם ממשיכים לחפש במעלה ה Scope Chain עד שמגיעים ל Object. בתוך מגבלה זו של השפה הדרך היחידה של פונקציה להיות "גלובאלית" היא להישתל בתור מתודה של Object, כך שמכל אוביקט אפשר יהיה להפעיל אותה.1 420
# טיפ Bash - בואו נבטל תיקיות אמצע עם find
הפקודה find מסתירה לא מעט קלפים בשרוול. הנה אחד שמצאתי מועיל לפני כמה ימים שאולי יעזור גם לכם.
קיבלתי עץ תיקיות שכלל הרבה תיקיות אמצע ריקות, דמיינו משהו שנראה כך:
.
└── a
└── b
├── c
│ └── d
│ └── e
│ ├── one.txt
│ ├── three.txt
│ └── two.txt
├── f
│ └── g
│ ├── five.txt
│ ├── four.txt
│ └── six.txt
└── y
└── seven.txt
ורציתי להיפטר מתיקיות האמצע ולהגיע למבנה כזה:
.
└── a
├── c
│ ├── one.txt
│ ├── three.txt
│ └── two.txt
├── f
│ ├── five.txt
│ ├── four.txt
│ └── six.txt
└── y
└── seven.txt
כלומר ביטול התיקיות b, d, e ו g.
דרך קלה לבטל את תיקיות האמצע תהיה להשתמש ב find:
1. נמצא את התיקיה העמוקה ביותר שיש להורה שלה רק ילד אחד (כלומר שהיא בן יחיד בעץ התיקיות).
2. נעביר את כל הקבצים שבה להורה שלה, ואז נמחק את התיקיה.
3. נחזור על התהליך עד שנסיים לרוץ על כל עץ התיקיות.
המתגים הרלוונטים של find הם לכן:
1. המתג -d שיגרום ל find להתחיל מהתיקיה העמוקה ביותר ולרוץ כלפי חוץ.
2. המתג -type d שיגרום ל find לרוץ רק על תיקיות.
3. המתג -exec שיאפשר לנו להריץ קוד במהלך החיפוש.
ופקודה ה find שחיברה את הכל נראתה כך:
$ find -d . -type d -exec bash -c '[[ $(ls {}/.. | wc -l | tr -d " ") == 1 ]] && (mv {}/* {}/..; rmdir {})' \;
בעיה יחידה שעשויה להתעורר היא שהתיקיה העליונה ביותר בעץ היא בן יחיד ולכן תבוטל. בשביל לעקוף את זה יצרתי תיקיה זמנית ברמה העליונה ביותר בעץ (ליד a) ואחרי ה find מחקתי גם אותה.
נ.ב. כן כנראה שאפשר לכתוב סקריפטים רציניים בשפות רציניות כדי להגיע לאותה תוצאה, ואם זה משהו שאתם עושים לעתים קרובות סיכוי טוב שגם כדאי לכתוב כאלה סקריפטים. בשביל משימות חד-פעמיות אני חושב ששליטה טובה ב find (ובכלל בכלי העבודה של יוניקס) יכולה לתת Productivity Boost משמעותי ושווה את ההשקעה בלימוד.1 420
# מי קורא את הקוד שלך?
ברשתות חברתיות יש אפקט שנקרא "חדרי הד", שזה אומר שאנשים נוטים להוסיף אנשים שחושבים כמוהם כחברים ואז כשאתה נכנס לפייסבוק נראה כאילו כל העולם מסכים אתך. האלגוריתמים מעודדים את זה וכך אנחנו מאבדים את היכולת לקיים דיון אמיתי ולשפר.
לפעמים אני מרגיש שדבר דומה קורה גם למתכנתים. זה לא נדיר לפגוש מתכנתים וותיקים שמושכים הרגלים רעים ישנים ולפעמים אפילו לא מודעים לזה שמבנה כזה או אחר לא ממש מקובל בתעשיה. וזה לפני שדיברנו על נושא של גירסאות ישנות וההשפעה שלהן על הקוד שאנחנו כותבים.
כאנשי מקצוע שרוצים להתקדם כדאי להניח שכל האנשים שאתם מכירים עושים את אותן טעויות שאתם עושים, ועכשיו לשאול - ממי עוד אני יכול ללמוד? מאיפה אוכל לקבל פידבקים חדשים? הנה כמה רעיונות:
1. כתיבת קוד עם אנשים חדשים (למשל בפרויקט קוד פתוח או בהאקתון).
2. כתיבת פרויקט קוד פתוח שלכם.
3. השתתפות בקורס ושיתוף הפיתרונות עם המרצה והמשתתפים האחרים.
4. העברת הרצאה וקבלת פידבק מהקהל (לא אידאלי כי במבנה של הרצאה אין המון זמן, אבל אם זה חצי יום הדרכה כן אפשר לקבל פידבק מועיל).
5. מענה על שאלות ב Stack Overflow או פורומים אחרים.
6. כתיבת פוסט בבלוג (בהנחה שיש קוראים).
ככל שבוחרים יותר אופציות כך מקבלים פידבק יותר מגוון שייתן לכם סיכוי לצאת מה Echo Chamber, ללמוד ולהשתפר.
1 420
# טיפ: קריאה לפונקציה בפייתון רק לפי שמה
בפייתון אין מבנה מסודר של switch/case אבל בעזרת שימוש נכון בתכנות מונחה עצמים אנחנו יכולים לייצר מבנה מקביל ודי נחמד: המשחק הוא להשתמש בשם הפונקציה בתור מפתח במילון או ישירות כדי לגשת למתודה באוביקט.
דוגמא? ברור - נניח שיש לכם קוד שקורא פקודות מהמשתמש ומבצע את ההוראות לפי הפקודה. בשביל שהקוד לא יהיה ארוך מדי נשתמש בפקודה inc כדי להגדיל ערך של תא בזיכרון וב show כדי להציג תא. הפעלה לדוגמא של התוכנית עשויה להיראות כך:
> show x
0
> inc x
> show x
1
> inc x
> inc x
> show x
3
> inc y
> show y
1
השורות שמתחילות בסימן חץ הן הקלט שמשתמש מכניס, והשורות בלי החץ הן פלט התוכנית. מימוש ראשוני בפייתון עשוי להיראות כך:
from collections import defaultdict
counters = defaultdict(int)
while True:
try:
next_line = input('> ')
cmd, arg = next_line.split()
if cmd == "show":
print(counters[arg])
elif cmd == "inc":
counters[arg] += 1
except Exception:
print("Invalid command. Try again")
הבעיה? ככל שיהיו יותר פקודות יהיה יותר קשה לתחזק את הקוד, וגם לא ברור איך להבדיל בין בעיה בפקודה עצמה לבין בעיה של קלט לא נכון לפקודה (למשל משתמש שכתב inc אבל שכח לכתוב את מה להגדיל).
כתיב מונחה עצמים פותח הזדמנות לפיתרון נקי יותר לאותה בעיה: נגדיר מחלקה בשם Counters, נגדיר בה פונקציה לכל סוג התנהגות ואז נקרא לפונקציה המתאימה והכל בצורה דינמית באמצעות __getattribute__. הנה הקוד:
from collections import defaultdict
class Counters:
def __init__(self):
self.counters = defaultdict(int)
def show(self, reg):
print(self.counters[reg])
def inc(self, reg):
self.counters[reg] += 1
counters = Counters()
while True:
try:
next_line = input('> ')
cmd, *args = next_line.split()
counters.__getattribute__(cmd)(*args)
except AttributeError:
print("Invalid command, try again")
except TypeError:
print("Missing required arguments to command")
except EOFError:
print("Bye bye")
break
במבנה כזה יש מספר יתרונות:
1. קל להוסיף התנהגות - פשוט מוסיפים עוד פונקציה ל Counters
2. קל לתת טיפול מתאים לבעיות שונות, בזכות השימוש בסוגי Exceptions שונים.
3. קל לבנות פעולות שמקבלות מספר שונה של ארגומנטים (או כלל לא), בזכות השימוש בכוכבית.1 420
# באגים של אחרים
במעבר שלנו למתכנתים שמשתמשים כל היום בספריות חיצוניות (npm, pip, gem, nuget) קל לראות כמה פחות עבודה אנחנו צריכים להשקיע בכתיבת קוד - כי מישהו אחר כבר כתב את זה - אבל גם קל לפספס את השעות שאנחנו מאבדים בחקירה ותיקון באגים של אחרים.
אז הבוקר שלי התחיל עם שדרוג לאחת הספריות ב npm ובגלל חוסר תשומת לב שדרגתי בטעות עוד כמה, מה שיצר package.json שנראה בערך כך:
{
"name": "myapp",
"version": "1.1.0",
"dependencies": {
"actioncable": "^5.1.1",
"babel-core": "6.26.3",
"babel-eslint": "^10.0.1",
"babel-loader": "^8.0.5",
"babel-plugin-react-transform": "^3.0.0",
"babel-polyfill": "^6.8.0",
"babel-preset-es2015": "^6.6.0",
"babel-preset-react": "^6.5.0",
"babel-preset-stage-0": "^6.5.0",
"babel-runtime": "^6.6.1",
"body-parser": "^1.15.1",
"classnames": "^2.2.5",
"create-react-class": "15.6.3",
"css-loader": "^2.1.1",
"es5-shim": "^4.5.8",
"es6-promise": "^4.1.0",
"eslint-plugin-import": "^2.3.0",
"eslint-plugin-jsx-a11y": "^6.2.1",
"exports-loader": "^0.7.0",
"expose-loader": "^0.7.1",
"github-api": "^3.0.0",
"history": "4.9.0",
"immutable": "^4.0.0-rc.12",
"imports-loader": "^0.8.0",
"isomorphic-fetch": "^2.2.1",
"jquery": "^3.2.1",
"jquery-ui": "^1.10.5",
"jquery-ujs": "^1.2.1",
"loader-utils": "^1.1.0",
"mirror-creator": "1.1.0",
"nighthawk": "2.2.0",
"null-loader": "^1.0.0",
"prop-types": "^15.7.2",
"query-string": "^6.5.0",
"react": "^16.8.6",
"react-addons-linked-state-mixin": "^15.0.2",
"react-bootstrap": "^1.0.0-beta.8",
"react-burger-menu": "^2.0.2",
"react-dom": "^16.8.6",
"react-dropzone": "^10.1.4",
"react-immutable-proptypes": "^2.1.0",
"react-on-rails": "11.2.2",
"react-quill": "^1.0.0-rc.2",
"react-redux": "^7.0.3",
"react-scroll": "^1.0.16",
"react-sticky": "^6.0.1",
"react-transition-group": "^4.0.0",
"redux": "^4.0.1",
"redux-batched-subscribe": "^0.1.6",
"redux-promise": "^0.6.0",
"redux-thunk": "^2.1.0",
"router": "^1.1.4",
"scriptjs": "^2.5.8",
"strftime": "^0.10.0",
"underscore": "^1.8.3",
"url-parse": "^1.1.1"
},
"devDependencies": {
"@babel/core": "^7.6.0",
"@babel/plugin-proposal-class-properties": "^7.5.5",
"@babel/preset-env": "^7.6.0",
"@babel/preset-react": "^7.0.0",
"bootstrap-sass": "^3.3.6",
"bootstrap-sass-loader": "^1.0.10",
"eslint": "^5.16.0",
"eslint-config-airbnb": "17.1.0",
"eslint-plugin-react": "^7.0.1",
"esprima-fb": "^15001.1001.0-dev-harmony-fb",
"express": "^4.13.4",
"file-loader": "^3.0.1",
"jade": "^1.11.0",
"jscs": "^3.0.3",
"node-sass": "^4.5.3",
"react-transform-hmr": "^1.0.4",
"sass-loader": "^7.1.0",
"speed-measure-webpack-plugin": "^1.3.1",
"style-loader": "^0.23.1",
"url-loader": "^1.1.2",
"webpack": "^4.39.3",
"webpack-bundle-analyzer": "^3.4.1",
"webpack-cli": "^3.3.8"
}
}
וכן יש פה המון ספריות. וכן בחצי מהן אני לא משתמש. וגם יש שם בעיה.
העניין שכשמשהו מתנהג לא כמו שצריך להתנהג התוצאה יכולה היתה להיות הודעה על המסך שבטעות בחרת גירסא לא נכונה של ספריה, אבל זה כמעט אף פעם לא מה שקורה. מה שקורה בדרך כלל זה שאיזה שורת קוד במקום לא קשור בכלל מחזירה תוצאה דומה-אבל-לא-בדיוק למה שהיתה אמורה להחזיר, והערך מתגלגל כמו כדור שלג עד שמתפוצץ רחוק מאוד מהמקום בו הבעיה התחילה.
לקח לי כמעט שעה להבין שהבעיה בכלל קשורה לשדרוג. מסתבר ששדרוג Minor Versions בספריה מסוימת יצר התנגשות עם ספריה אחרת ופיצוץ במקום רחוק. שנמוך גירסא של Immutable.JS בחזרה לגירסא 3.8.2 הציל את המצב, למרות שאני עדיין לא יודע להסביר למה.
כדאי לזכור שמחשבים לא משקרים, ושאם לא שיניתם שום דבר בקוד ודברים מתחילים להישבר כדאי להיות חשדנים לגבי ספריות חיצוניות וקוד של אחרים. שנמוך לגירסאות יציבות הוא לפעמים פתח המילוט המהיר ביותר.1 420
# שברת שילמת
אנשים שונים מוכנים לקחת את הסיכון ולתקן לבד דברים שונים - יש מי שתשמח להחליף לבד גלגל באוטו, ומישהו אחר שירגיש שתמיד צריך לקרוא לאיש מקצוע. הבחירה מה אנחנו מוכנים לנסות לתקן לבד וממה אנחנו מפחדים היא משהו שהתפתח אצל כל אחד ואחת מאתנו לאורך החיים וזה משהו שלא תמיד קל לשנות.
וגם כשאנחנו בוחרים עולם תוכן, ואפילו כשזה העולם המקצועי שלנו, יהיו כאלה שיבחרו לא לקחת את הסיכון של "שברת שילמת" ולקרוא לאיש מקצוע:
יש מתכנתים שיעדיפו לא לגעת בדטבייס, כי אולי בטעות אשבור משהו.
יש מתכנתים שיעדיפו להתרחק ממסוף שורת הפקודה, אי אפשר לשבור משהו.
יש מתכנתים שלא יעזו לשנות שורה באלגוריתם כי מישהו חכם חשב על זה ואולי בטעות אשבור משהו.
והרשימה רק מתארכת. וזה בסדר כשאנחנו עובדים על מערכת גדולה להיזהר ולפחד וברור שאפשר בטעות למחוק את הפרטים של כל הלקוחות וזה לא יהיה נעים לאף אחד. הבעיה שהרבה פעמים הזהירות באה על חשבון הלימודים והפחד בא על חשבון השיפור המקצועי שלכם.
אחד הזכרונות הטובים שלי מהעבודה הראשונה כמתכנת היה כשבטעות זרקתי לפח את כל בסיס הנתונים (היוש drop database). הלכתי בפאניקה לבוס אבל במקום לזרוק אותי מהקומה העשירית הוא פשוט הראה לי איפה הגיבוי. זה לקח כמה שעות למצוא איך משחזרים מגיבוי אבל בסוף הכל הסתדר. מאז הדבר הראשון שאני שואל בכל מקום חדש זה "איפה הגיבוי?"
"שברת שילמת" יכול להיות אזהרה, סוג של "אל תעז להתקרב", אבל הוא יכול להיות גם הזמנה לחקור, לשחק וכן גם לשבור:
שברת -> שילמת -> תיקנת -> למדת.
1 420
# שלוש טעויות שכבר לא תעשו ב Python אחרי שתוסיפו Type Hints
הוויכוח אם כדאי או לא כדאי להשתמש ב Type Hints בפייתון הופך לפחות ופחות רלוונטי ככל שהשימוש בהם נהיה קל יותר ומובנה יותר בשפה. היום מתכנתי פייתון שעובדים ב PyCharm יכולים כמעט בלי ללמוד תחביר חדש לעשות הרבה פחות טעויות בעבודה היום יומית. הנה שלוש דוגמאות לטעויות שאנחנו כבר לא צריכים לעשות בזכות Type Hints:
## חישוב סכום ספרות
הקוד הבא לא מצליח לחשב את סכום הספרות במספר 920:
num = 920
sum_of_digits = 0
while num > 0:
sum_of_digits += (num % 10)
num = num / 10
print(sum_of_digits)
אני מקווה שאתם יודעים למה, אבל בכל מקרה mypy יודע. הנה מה שקורה כשאני נותן ל mypy לרוץ על הקוד:
$ python3 -m mypy a.py
a.py:6: error: Incompatible types in assignment (expression has type "float", variable has type "int")
ובאמת המעבר של num ממשתנה int למשתנה float הוא זה שגם מקלקל את החישוב. החליפו את החלוקה בחלוקת מספרים שלמים ותקבלו את סכום הספרות הנכון וגם mypy ירגע.
מעניין לראות שלא הייתי צריך לציין במפורש בשום מקום שאני מצפה ש num יהיה מספר שלם. מספיק ששמתי בו מספר שלם בתור ערך התחלתי כדי ש mypy יתייחס אליו ככזה.
## המרת גיל לחודשים
התוכנית הבאה לא מצליחה לקבל מהמשתמש את גילו בשנים ולהדפיס בחזרה את גילו בחודשים:
age_in_years = input('how old are you? (in years) ')
age_in_months = age_in_years * 12
print(f"Wow that's {age_in_months} months")
אני מקווה שאתם כבר רואים למה, אבל בכל מקרה mypy עדיין לא רואה. הסיבה שמבחינת פייתון מותר לכפול מחרוזת במספר (ואם אתם לא מכירים את הטריק לכו לבדוק מה קורה. אין לחץ אני אחכה...).
בכל מקרה עם קצת עזרה נוכל לספר ל mypy שאנחנו חושבים שהגיל אמור להיות מספר והוא ישמח לעזור בחזרה ולספר ששכחנו להמיר את הקלט. תחילה התוכנית עם ה Type Hint:
age_in_years : int = input('how old are you? (in years) ')
age_in_months = age_in_years * 12
print(f"Wow that's {age_in_months} months")
והפלט של mypy:
$ python3 -m mypy a.py
a.py:1: error: Incompatible types in assignment (expression has type "str", variable has type "int")
## היפוך רשימה
הקוד הבא לא מדפיס את הרשימה one, two, three מהסוף להתחלה:
data = ['one', 'two', 'three']
reversed_data = data.reverse()
print(reversed_data)
וגם כאן mypy עדיין לא יכול לדעת שהתכוונו בכלל להפוך את הרשימה, אבל אם ניתן לו טיפ קטן הוא יתחיל לעבוד בשבילנו. התוספת הבאה לתוכנית:
from typing import List
data = ['one', 'two', 'three']
reversed_data :List[str] = data.reverse()
print(reversed_data)
ועכשיו אנחנו מקבלים:
$ python3 -m mypy a.py
a.py:4: error: "reverse" of "list" does not return a value
אז נכון הדוגמאות כאן ממוקדות מאוד וקל לראות את הבעיות בהן כשהן מופיעות לבד, אבל הכח של Type Hints מתגלה בעבודה אמיתית על תוכניות אמיתיות. עבודה עם Type Hints מאפשרת לנו לכתוב רצף של קוד ואז במכה אחת לזהות את כל המקומות בהם התבלבלתי בגלל טיפוסים ולתקן אותם לפני שמפעילים באמת את הסקריפט. כל מה שצריך זה להתרגל לכתוב את הנקודותיים והטיפוס אחרי כל הגדרת משתנה, ולדעתי זה שווה את ההשקעה.
נ.ב. יש מקרים מורכבים (בפייתון כמו בחיים) בהם בשביל לכתוב Type Hints נכונים צריך ממש להתאמץ. במקרים כאלה נוח להשתמש ב Any או לוותר על Type Hints לחלוטין וזה בסדר. המטרה היא לכתוב קוד נכון מהר יותר ולא לבזבז יום שלם בלעבוד בשביל פייתון.