Bài viết được sự cho phép của tác giả Nguyễn Việt Hưng
Các lập trình viên chuyển sang code Python từ các ngôn ngữ lập trình khác như Java, C, Golang… thường bắt đầu code bằng việc bật một cái IDE to đùng (PyCharm) lên, rồi viết chục dòng code, sau đó bấm nút “tam giác” để chạy từ trên xuống dưới. Đó là cách làm phổ biến, tiêu chuẩn khi viết code C, Java, Golang… nhưng là một cách làm rất không … Python.
Khi học Python, việc đầu tiên ta làm là bật python
từ terminal, rồi gõ trực tiếp các dòng code vào đó, enter để thấy kết quả:
$
python3
Python
3.6
.
9
(
default
,
Apr
18
2020
,
01
:
56
:
04
)
[GCC
8.4
.
0
]
on
linux
Type
"help"
,
"copyright"
,
"credits"
or
"license"
for
more
information
.
'42 is the answer of life.'
Còn khi đi làm, viết code Python? Cũng vậy!
Khả năng gõ code trực tiếp, enter thấy ngay kết quả như trên, là một tính năng cực kỳ hấp dẫn/quan trọng của Python cũng như các ngôn ngữ lập trình có REPL
như Ruby, Clojure, JavaScript, LISP, Ocaml, Elixir, F#… nó cho phép người dùng khám phá, vui chơi thoải mái với dữ liệu một cách tương tác, thấy kết quả nhanh nhất, thay vì phải ngồi tưởng tượng, đoán, chờ compile, và dựa vào IDE trợ giúp như các ngôn ngữ không có REPL.
Đây là chế độ “interactive mode” của Python interpreter, khái niệm này có cái tên khác chung hơn là: REPL.
(Chú ý: Golang có các project như gore
hay yaegi
nhưng đều rất hạn chế so với REPL của các ngôn ngữ kể trên).
REPL
REPL – Read Eval Print Loop, là môi trường nhận đầu vào từ người dùng (Read
), chạy input đó (Eval
), in kết quả ra màn hình (Print
), và cứ tiếp tục vậy (Loop
).
Khái niệm này bắt nguồn từ ngôn ngữ lập trình cổ thứ 2 thế giới: LISP.
Việc viết code khi dùng các ngôn ngữ có REPL thường theo các bước:
- bật REPL lên
- gõ code thử cho tới khi thu được kết quả mong muốn
- copy code đó vào editor/IDE
Ví dụ
# githubstars.py
from
urllib.request
import
urlopen
import
json
def
main
():
repos
=
json
.
load
(
f
)
has_stars
=
[]for
repo
in
repos
:
has_stars
.
append
((
repo
["stargazers_count"
],
repo
["html_url"
]
))
has_stars
.
sort
(
reverse
=
True
)
for
stars
,
url
in
has_stars
:
output
=
"{} - {}"
.
format
(
stars
,
url
)
(
output
)
if
__name__
==
"__main__"
:
main
()
Nếu viết theo kiểu này, rồi cho vào IDE, bấm nút tam giác để chạy, những nhược điểm sau sẽ xuất hiện:
- Mỗi lần chạy, code sẽ truy cập vào API GitHub 1 lần, việc này ngoài chậm, phụ thuộc vào mạng internet mỗi lần chạy, còn thêm nhược điểm nữa là sẽ dùng tốn “quota” hàng ngày của bạn (VD GitHub chỉ cho phép gọi API n lần 1 ngày).
- Trừ khi bạn code 1 lần chuẩn luôn, còn không thì mất khoảng 5 7 lần mới ra đoạn code trên.
- Không test từng phần (bước) của đoạn code được.
Thay vì vậy, viết lại một phần code như sau
from
urllib.request
import
urlopen
import
json
def
getrepos
():
repos
=
json
.
load
(
f
)
return
repos
def
main
():
pass
Lưu vào file github.py
, rồi vào terminal, bật python3
lên, gõ:
0
8
…
(
fmt
.
format
(
*
i
))
…
Với cách làm này, chỉ cần gọi GitHub API duy nhất 1 lần, còn sau đó thử thoải mái cho đến khi thu được kết quả mong muốn thì copy vào file cuối cùng:
import
json
from
urllib.request
import
urlopen
def
getrepos
():
repos
=
json
.
load
(
f
)
return
repos
def
has_stars
(
repo
):
def
filter_repos_have_stars
(
repos
):
return
[p
for
p
in
repos
if
has_stars
(
p
)]
def
get_star_url
(
p
):
return
(
p
["stargazers_count"
],
p
["html_url"
])
def
main
():
repos
=
getrepos
()
repos_have_stars
=
filter_repos_have_stars
(
repos
)
stars_urls
=
[get_star_url
(
p
)
for
p
in
repos_have_stars
]
stars_urls
.
sort
(
reverse
=
True
)
fmt
=
"{} - {}"
for
i
in
stars_urls
:
(
fmt
.
format
(
*
i
))
if
__name__
==
"__main__"
:
main
()
Sau này nếu code có bug, lại bật REPL lên, gọi các function để debug trực tiếp dễ dàng, từng bước một.
$
ipython
Python
3.6
.
9
(
default
,
Jul
17
2020
,
12
:
50
:
27
)
Type
'copyright'
,
'credits'
or
'license'
for
more
information
IPython
7.9
.
0
--
An
enhanced
Interactive
Python
.
Type
'?'
for
help
.
In
[1
]:
import
github
In
[2
]:
repos
=
github
.
getrepos
()
In
[3
]:
have_stars
=
github
.
filter_repos_have_stars
(
repos
)
In
[4
]:
len
(
have_stars
)
Out
[4
]:
8
In
[5
]:
[github
.
get_star_url
(
p
)
for
p
in
have_stars
]
Out
[5
]:
In
[8
]:
sorted
([
github
.
get_star_url
(
p
)
for
p
in
have_stars
],
reverse
=
True
)
Out
[8
]:
Dev với IPython
IPython (pip install ipython
) cung cấp thêm các tính năng giúp cách code này hiệu quả hơn.
IPython có màu mè, auto-indent tự thụt sau for/if giúp gõ nhanh hơn.
Magic command %hist
sẽ hiện full history những gì user đã gõ, giúp copy code để paste ra IDE/Editor dễ hơn, không bao gồm output.
Magic command %edit
sẽ mở hẳn editor ra để sửa code, sau khi đóng lại, code sẽ được chạy, các biến sẽ tồn tại trong môi trường đang code.
Ví dụ này gõ %edit
lần đầu định nghĩa list ns
, rồi gõ %edit
lần 2 để print ra list ns định nghĩa trước đó:
$
ipython
Python
3.6
.
9
(
default
,
Apr
18
2020
,
01
:
56
:
04
)
Type
'copyright'
,
'credits'
or
'license'
for
more
information
IPython
7.9
.
0
--
An
enhanced
Interactive
Python
.
Type
'?'
for
help
.
In
[1
]:
%
edit
IPython
will
make
a
temporary
file
named
:
/
tmp
/
ipython_edit_2v90rimj
/
ipython_edit_wf_rn_nc
.
py
Editing
...
done
.
Executing
edited
code
...
Out
[1
]:
'
n
ns = [1,2,3,4]
n
'
In
[2
]:
ns
Out
[2
]:
[1
,
2
,
3
,
4
]
In
[3
]:
%
edit
IPython
will
make
a
temporary
file
named
:
/
tmp
/
ipython_edit_qb43pml6
/
ipython_edit_tph7_5x6
.
py
Editing
...
done
.
Executing
edited
code
...
[1
,
2
,
3
,
4
]
Out
[3
]:
'print(ns)
n
'
Đổi editor
ra shell, gõ echo $EDITOR
xem đang đặt là gì, thay bằng câu lệnh mở editor mình muốn, ví dụ
$export
EDITOR
=
nano $ ipython
Chạy file rồi bật REPL
Python hay IPython đều hỗ trợ option -i
, sau khi chạy với 1 file code sẽ tự động vào chế độ interactive mode
Jupyter
Unittest
Trong các ngôn ngữ không có REPL, cách thử 1 đoạn code nhanh nhất là viết 1 function cần thử, rồi viết unittest, rồi chạy test thay vì chạy cả 1 chương trình ngàn dòng. Với Python, ta chỉ cần bật REPL lên, import module vào và khám phá.
Code viết theo cách mới trên vừa dễ gõ trực tiếp trong REPL, vừa dễ viết unittest, ví dụ viết nhanh unittest chạy bằng pytest
(pip install pytest
) như sau:
# test_github.py
import
github
def
test
():
bad
=
{
"stargazers_count"
:
0
,
"html_url"
:
"bad_repo"
}
good
=
{
"stargazers_count"
:
69
,
"blah"
:
"blo"
,
}
sample_repos
=
[bad
,
good
]
assert
github
.
filter_repos_have_stars
(
sample_repos
)
==
[good
]
assert
github
.
get_star_url
(
good
)
==
(
good
["stargazers_count"
],
good
["html_url"
],
)
assert
github
.
has_stars
(
bad
)
is
False
assert
github
.
has_stars
(
good
)
is
True
Viết code bằng REPL hay bằng unittest TDD đều mang tới một kết quả chung: code dễ sửa, dễ test.
Tất nhiên REPL không thay thế hoàn toàn cho unittest, nhưng nó mang lại môi trường thử nghiệm nhanh chóng tương đương như hơn nhiều unittest ở các ngôn ngữ khác.
Hành động của chúng ta
Cài ngay IPython, Jupyter rồi bật lên mỗi khi muốn code Python.
Kết luận
Đừng đọc tiếng Anh theo kiểu Tiếng Việt, đừng code Python theo kiểu Java. REPL là một phát minh có sức mạnh khủng khiếp mà các Pythonista nên vận dụng, sử dụng, và lạm dụng hết mình.